├── packages ├── .gitkeep ├── dom │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── utilities │ │ │ ├── update-html.ts │ │ │ ├── domhandler.ts │ │ │ └── index.ts │ │ │ ├── serializers.ts │ │ │ ├── dom.ts │ │ │ ├── config │ │ │ ├── slateDemo.ts │ │ │ ├── types.ts │ │ │ ├── payload.ts │ │ │ └── default.ts │ │ │ └── dom.spec.ts │ ├── docs │ │ └── nx.md │ ├── README.md │ ├── package.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── jest.config.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── project.json ├── html │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── serializers │ │ │ ├── slateToHtml │ │ │ │ ├── index.ts │ │ │ │ ├── slateDemo.spec.ts │ │ │ │ └── payload.spec.ts │ │ │ ├── htmlToSlate │ │ │ │ ├── config │ │ │ │ │ ├── payload.ts │ │ │ │ │ ├── slateDemo.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── default.ts │ │ │ │ ├── whitespace.ts │ │ │ │ ├── whitespace.spec.ts │ │ │ │ ├── slateDemo.spec.ts │ │ │ │ └── index.spec.ts │ │ │ └── snapshots │ │ │ │ └── elementTags.spec.ts │ │ │ ├── tests │ │ │ ├── slateToHtml │ │ │ │ └── configuration │ │ │ │ │ ├── markMap.spec.ts │ │ │ │ │ ├── markTransforms.spec.ts │ │ │ │ │ ├── elementTransforms.spec.ts │ │ │ │ │ └── elementMap.spec.ts │ │ │ └── htmlToSlate │ │ │ │ └── configuration │ │ │ │ ├── textTagsVselementTags.spec.ts │ │ │ │ ├── textTags.spec.ts │ │ │ │ ├── elementAttributeTransform.spec.ts │ │ │ │ ├── convertBrToLineBreak.spec.ts │ │ │ │ └── elementTags.spec.ts │ │ │ ├── html.ts │ │ │ └── utilities │ │ │ └── blocks.ts │ ├── package.json │ ├── docs │ │ └── nx.md │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── jest.config.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── project.json ├── react │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── config │ │ │ ├── slateDemo.tsx │ │ │ ├── types.ts │ │ │ ├── default.tsx │ │ │ └── payload.tsx │ │ │ ├── react.spec.tsx │ │ │ ├── react.tsx │ │ │ ├── serializers.tsx │ │ │ └── __tests__ │ │ │ ├── default.spec.tsx │ │ │ └── payload-crowdin-sync-dev │ │ │ └── payload-internal-link.spec.tsx │ ├── package.json │ ├── README.md │ ├── .babelrc │ ├── CHANGELOG.md │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ └── project.json ├── tests │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── react │ │ │ ├── testComponents │ │ │ │ └── Button.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── style-object.spec.tsx.snap │ │ │ │ └── combined.spec.tsx.snap │ │ │ ├── style-object.spec.tsx │ │ │ ├── combined.spec.tsx │ │ │ ├── combined-payload.spec.tsx │ │ │ └── components.spec.tsx │ │ │ ├── tests.ts │ │ │ ├── fixtures │ │ │ ├── styles-in-leaf.ts │ │ │ ├── style-object.ts │ │ │ ├── textTags.ts │ │ │ └── elementTags.ts │ │ │ ├── template │ │ │ ├── combined-payload.spec.ts │ │ │ └── __snapshots__ │ │ │ │ └── combined-payload.spec.ts.snap │ │ │ └── html │ │ │ ├── sameHtmlSlateBothWays.spec.ts │ │ │ ├── withStylesInLeaf.spec.ts │ │ │ ├── expectedDifferencesHtmlSlateBothWays.spec.ts │ │ │ └── withStyleObject.spec.ts │ ├── package.json │ ├── .babelrc │ ├── README.md │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── CHANGELOG.md │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── tsconfig.json │ └── project.json ├── template │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── config │ │ │ ├── default.ts │ │ │ ├── slateDemo.ts │ │ │ ├── payload.ts │ │ │ └── types.ts │ │ │ ├── template.ts │ │ │ ├── serializers.ts │ │ │ └── __tests__ │ │ │ └── default.spec.ts │ ├── package.json │ ├── CHANGELOG.md │ ├── docs │ │ └── nx.md │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── jest.config.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── project.json │ └── README.md ├── slate-serializers │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── slate-serializers.ts │ │ │ └── slate-serializers.spec.ts │ ├── package.json │ ├── CHANGELOG.md │ ├── tsconfig.lib.json │ ├── docs │ │ └── nx.md │ ├── tsconfig.spec.json │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── tsconfig.json │ ├── README.md │ └── project.json └── utilities │ ├── src │ ├── index.ts │ └── lib │ │ ├── style-object.ts │ │ └── utilities.ts │ ├── package.json │ ├── README.md │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── jest.config.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── project.json ├── .eslintignore ├── .prettierrc ├── .npmrc ├── .commitlintrc.json ├── jest.preset.js ├── .prettierignore ├── jest.config.ts ├── .vscode └── extensions.json ├── .env ├── .release-please-manifest.json ├── .editorconfig ├── tools ├── tsconfig.tools.json ├── sync-versions.js └── scripts │ └── publish.mjs ├── .github └── workflows │ ├── release-please.js.yml │ └── node.js.yml ├── project.json ├── .verdaccio └── config.yml ├── .gitignore ├── release-please-config.json ├── tsconfig.base.json ├── LICENSE.md ├── .eslintrc.json ├── nx.json ├── docs ├── nx.md └── config │ ├── slateToDom.md │ └── htmlToSlate.md ├── README.md ├── migrations.json └── package.json /packages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /packages/dom/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/dom'; 2 | -------------------------------------------------------------------------------- /packages/html/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/html'; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${SS_NPM_TOKEN} 2 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/react'; 2 | -------------------------------------------------------------------------------- /packages/tests/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/tests'; 2 | -------------------------------------------------------------------------------- /packages/template/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/template'; 2 | -------------------------------------------------------------------------------- /packages/slate-serializers/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/slate-serializers'; 2 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/react", 3 | "version": "2.3.0" 4 | } -------------------------------------------------------------------------------- /packages/utilities/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/style-object'; 2 | export * from './lib/utilities'; -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/workspace-data -------------------------------------------------------------------------------- /packages/html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/html", 3 | "version": "2.3.0", 4 | "type": "commonjs" 5 | } -------------------------------------------------------------------------------- /packages/tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/tests", 3 | "version": "2.3.0", 4 | "type": "commonjs" 5 | } -------------------------------------------------------------------------------- /packages/slate-serializers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slate-serializers", 3 | "version": "2.3.0", 4 | "type": "commonjs" 5 | } -------------------------------------------------------------------------------- /packages/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/template", 3 | "version": "2.3.0", 4 | "type": "commonjs" 5 | } -------------------------------------------------------------------------------- /packages/utilities/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/utilities", 3 | "version": "2.3.0", 4 | "type": "commonjs" 5 | } -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjectsAsync } from '@nx/jest'; 2 | 3 | export default async () => ({ 4 | projects: await getJestProjectsAsync(), 5 | }); 6 | -------------------------------------------------------------------------------- /packages/template/src/lib/config/default.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './types' 2 | import { slateToDomConfig } from '@slate-serializers/dom' 3 | 4 | export const config: Config = slateToDomConfig 5 | -------------------------------------------------------------------------------- /packages/react/src/lib/config/slateDemo.tsx: -------------------------------------------------------------------------------- 1 | import { Config } from './types' 2 | import { config as defaultConfig } from './default' 3 | 4 | export const config: Config = { 5 | ...defaultConfig, 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | # react 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test react` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /packages/template/src/lib/config/slateDemo.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './types' 2 | import { slateDemoSlateToDomConfig } from '@slate-serializers/dom' 3 | 4 | export const config: Config = slateDemoSlateToDomConfig 5 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Nx 18 enables using plugins to infer targets by default 2 | # This is disabled for existing workspaces to maintain compatibility 3 | # For more info, see: https://nx.dev/concepts/inferred-tasks 4 | NX_ADD_PLUGINS=false -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | {".":"2.3.0","packages/dom":"2.3.0","packages/html":"2.3.0","packages/react":"2.3.0","packages/slate-serializers":"2.3.0","packages/template":"2.3.0","packages/tests":"2.3.0","packages/utilities":"2.3.0"} 2 | -------------------------------------------------------------------------------- /packages/react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nx/react/babel", 5 | { 6 | "runtime": "automatic", 7 | "useBuiltIns": "usage" 8 | } 9 | ] 10 | ], 11 | "plugins": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/tests/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nx/react/babel", 5 | { 6 | "runtime": "automatic", 7 | "useBuiltIns": "usage" 8 | } 9 | ] 10 | ], 11 | "plugins": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/template/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/template-v2.2.3...template-v2.3.0) (2025-05-16) 4 | 5 | 6 | ### Miscellaneous Chores 7 | 8 | * **template:** Synchronize all versions 9 | -------------------------------------------------------------------------------- /packages/dom/docs/nx.md: -------------------------------------------------------------------------------- 1 | # dom 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build dom` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test dom` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/html/docs/nx.md: -------------------------------------------------------------------------------- 1 | # html 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build html` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test html` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/slate-serializers/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/slate-serializers-v2.2.3...slate-serializers-v2.3.0) (2025-05-16) 4 | 5 | 6 | ### Miscellaneous Chores 7 | 8 | * **slate-serializers:** Synchronize all versions 9 | -------------------------------------------------------------------------------- /packages/tests/README.md: -------------------------------------------------------------------------------- 1 | # tests 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build tests` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test tests` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/dom/README.md: -------------------------------------------------------------------------------- 1 | # @slate-serializers/dom 2 | 3 | `slateToDom` is used by `slateToHtml` in [`@slate-serializers/html`](packages/html/README.md) before serializing to HTML. 4 | 5 | It is made available as a separate serializer for cases where DOM manipulation is desired before serializing to HTML. 6 | -------------------------------------------------------------------------------- /packages/utilities/README.md: -------------------------------------------------------------------------------- 1 | # utilities 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build utilities` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test utilities` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/template/docs/nx.md: -------------------------------------------------------------------------------- 1 | # NX documentation 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build template` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test template` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/dom", 3 | "version": "2.3.0", 4 | "description": "Serialize Slate JSON objects to the DOM. Can be used with `htmlparser2` and associated utilities to modify the DOM and generate HTML. Used by other serializers in this monorepo.", 5 | "type": "commonjs" 6 | } -------------------------------------------------------------------------------- /packages/dom/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/html/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/tests/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/template/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/utilities/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/slate-serializers/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/template/src/lib/config/payload.ts: -------------------------------------------------------------------------------- 1 | import { payloadSlateToDomConfig } from '@slate-serializers/dom' 2 | import { Config as SlateToTemplateConfig } from './types' 3 | 4 | /** 5 | * Configuration for Payload CMS 6 | * 7 | * Tested for v1.1.21 8 | */ 9 | 10 | export const config: SlateToTemplateConfig = payloadSlateToDomConfig -------------------------------------------------------------------------------- /packages/slate-serializers/docs/nx.md: -------------------------------------------------------------------------------- 1 | # slate-serializers 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build slate-serializers` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test slate-serializers` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /packages/dom/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/html/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/react/src/lib/react.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | 3 | import { SlateToReact } from '../lib/react'; 4 | 5 | describe('React', () => { 6 | it('should render successfully', () => { 7 | const { baseElement } = render(); 8 | expect(baseElement).toBeTruthy(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/tests/src/lib/react/testComponents/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from 'react' 2 | 3 | interface IButton { 4 | onClick?: () => void 5 | children?: ReactNode 6 | } 7 | 8 | const Button: FC = ({ onClick, children }) => { 9 | return 10 | } 11 | 12 | export default Button 13 | -------------------------------------------------------------------------------- /packages/template/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/tests/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/utilities/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/slate-serializers/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/react-v2.2.3...react-v2.3.0) (2025-05-16) 4 | 5 | 6 | ### Features 7 | 8 | * **react:** flatten config ([#191](https://github.com/thompsonsj/slate-serializers/issues/191)) ([761c651](https://github.com/thompsonsj/slate-serializers/commit/761c651ad49fda360b416ab561b9aac72ed7aa8d)) 9 | -------------------------------------------------------------------------------- /packages/tests/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/tests-v2.2.3...tests-v2.3.0) (2025-05-16) 4 | 5 | 6 | ### Features 7 | 8 | * **react:** flatten config ([#191](https://github.com/thompsonsj/slate-serializers/issues/191)) ([761c651](https://github.com/thompsonsj/slate-serializers/commit/761c651ad49fda360b416ab561b9aac72ed7aa8d)) 9 | -------------------------------------------------------------------------------- /packages/tests/src/lib/tests.ts: -------------------------------------------------------------------------------- 1 | export { fixtures as combinedFixtures } from './fixtures/combined' 2 | export { fixtures as elementFixtures } from './fixtures/elementTags' 3 | export { fixtures as textFixtures } from './fixtures/textTags' 4 | export { fixtures as styleObjectFixtures } from './fixtures/style-object' 5 | export { fixtures as stylesMixedInFixtures } from './fixtures/styles-in-leaf' 6 | -------------------------------------------------------------------------------- /packages/dom/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'dom', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/packages/dom', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/html/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'html', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/packages/html', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/template/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'template', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/packages/template', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/utilities/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'utilities', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/packages/utilities', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dom/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/html/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react/src/lib/react.tsx: -------------------------------------------------------------------------------- 1 | // Serializers 2 | export { SlateToReact } from './serializers' 3 | 4 | // Configuration objects 5 | export type { Config as SlateToReactConfig } from './config/types' 6 | export { config as slateToReactConfig } from './config/default' 7 | export { config as payloadSlateToReactConfig } from './config/payload' 8 | export { config as slateDemoSlateToReactConfig } from './config/slateDemo' 9 | -------------------------------------------------------------------------------- /packages/tests/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/template/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/utilities/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/slate-serializers/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/slate-serializers/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'slate-serializers', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/packages/slate-serializers', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dom/src/lib/utilities/update-html.ts: -------------------------------------------------------------------------------- 1 | import { getChildren } from 'domutils' 2 | import { Element } from 'domhandler' 3 | import { HtmlUpdaterFunctionMap } from '../config/types' 4 | 5 | export const renameTag = (tagName: string, replacementTagName: string): HtmlUpdaterFunctionMap => ({ 6 | [tagName]: (element: Element) => { 7 | return new Element(replacementTagName, element.attribs, getChildren(element)) 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/react/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'react', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/packages/react', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/tests/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'react', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/packages/react', 11 | }; 12 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "allowJs": false, 5 | "esModuleInterop": false, 6 | "allowSyntheticDefaultImports": true, 7 | "strict": true 8 | }, 9 | "files": [], 10 | "include": [], 11 | "references": [ 12 | { 13 | "path": "./tsconfig.lib.json" 14 | }, 15 | { 16 | "path": "./tsconfig.spec.json" 17 | } 18 | ], 19 | "extends": "../../tsconfig.base.json" 20 | } 21 | -------------------------------------------------------------------------------- /packages/template/src/lib/config/types.ts: -------------------------------------------------------------------------------- 1 | import { SlateToDomConfig } from '@slate-serializers/dom' 2 | 3 | export type ElementSerializer = 4 | ({ 5 | node, 6 | }: { 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | node?: any 9 | }) => unknown 10 | 11 | 12 | interface ElementSerializers { 13 | [key: string]: ElementSerializer 14 | } 15 | 16 | export interface Config extends SlateToDomConfig { 17 | customElementSerializers?: ElementSerializers 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/release-please.js.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: googleapis/release-please-action@v4 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | config-file: release-please-config.json 20 | manifest-file: .release-please-manifest.json -------------------------------------------------------------------------------- /packages/react/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.test.tsx", 13 | "src/**/*.spec.tsx", 14 | "src/**/*.test.js", 15 | "src/**/*.spec.js", 16 | "src/**/*.test.jsx", 17 | "src/**/*.spec.jsx", 18 | "src/**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/template/src/lib/template.ts: -------------------------------------------------------------------------------- 1 | // Serializers 2 | export { slateToTemplate } from './serializers' 3 | 4 | // Configuration objects 5 | // slateToDom 6 | export type { Config as SlateToTemplateConfig } from './config/types' 7 | export { config as slateToTemplateConfig } from './config/default' 8 | export { config as payloadSlateToTemplateConfig } from './config/payload' 9 | export { config as slateDemoSlateToTemplateConfig } from './config/slateDemo' 10 | 11 | // Useful types 12 | export type { ElementSerializer } from './config/types' 13 | -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slate-serializers/source", 3 | "$schema": "node_modules/nx/schemas/project-schema.json", 4 | "targets": { 5 | "local-registry": { 6 | "executor": "@nx/js:verdaccio", 7 | "options": { 8 | "port": 4873, 9 | "config": ".verdaccio/config.yml", 10 | "storage": "tmp/local-registry/storage" 11 | } 12 | }, 13 | "version": { 14 | "executor": "@jscutlery/semver:version", 15 | "options": { 16 | "preset": "conventional" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/slateToHtml/index.ts: -------------------------------------------------------------------------------- 1 | import serializer from 'dom-serializer' 2 | 3 | import { slateToDom, slateToDomConfig as defaultConfig, type SlateToDomConfig } from '@slate-serializers/dom' 4 | 5 | type SlateToHtml = (node: any[], config?: SlateToDomConfig) => string 6 | 7 | export const slateToHtml: SlateToHtml = (node: any[], config = defaultConfig) => { 8 | const document = slateToDom(node, config) 9 | return serializer(document, { 10 | encodeEntities: 'encodeEntities' in config ? config.encodeEntities : false, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /packages/dom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/html/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/utilities/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/slate-serializers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/utilities/src/lib/style-object.ts: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss' 2 | import { parse as parser, objectify } from 'postcss-js' 3 | 4 | export const transformStyleObjectToString = (style: { [key: string]: any }) => { 5 | const postcssOptions = { 6 | parser, 7 | from: undefined, 8 | } 9 | return postcss() 10 | .process(style, postcssOptions as any) 11 | .css.replace(/(\r\n|\n|\r)/gm, ' ') 12 | .replace(/\s\s+/g, ' ') 13 | } 14 | 15 | export const transformStyleStringToObject = (style: string) => { 16 | const root = postcss.parse(style) 17 | return objectify(root) 18 | } 19 | -------------------------------------------------------------------------------- /packages/react/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [ 6 | "node", 7 | 8 | "@nx/react/typings/cssmodule.d.ts", 9 | "@nx/react/typings/image.d.ts" 10 | ] 11 | }, 12 | "exclude": [ 13 | "jest.config.ts", 14 | "src/**/*.spec.ts", 15 | "src/**/*.test.ts", 16 | "src/**/*.spec.tsx", 17 | "src/**/*.test.tsx", 18 | "src/**/*.spec.js", 19 | "src/**/*.test.js", 20 | "src/**/*.spec.jsx", 21 | "src/**/*.test.jsx" 22 | ], 23 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/react/src/lib/config/types.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { BaseConfig } from '@slate-serializers/dom' 3 | 4 | interface MarkTagTransform { 5 | [key: string]: ({ node, attribs }: { node?: any; attribs?: { [key: string]: string } }) => ReactNode 6 | } 7 | 8 | interface ElementTagTransform { 9 | [key: string]: ({ 10 | node, 11 | attribs, 12 | children, 13 | }: { 14 | node?: any 15 | attribs?: { [key: string]: string } 16 | children?: ReactNode 17 | }) => ReactNode 18 | } 19 | 20 | export interface Config extends BaseConfig { 21 | markTransforms?: MarkTagTransform 22 | elementTransforms: ElementTagTransform 23 | } 24 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/htmlToSlate/config/payload.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './types' 2 | import { config as defaultConfig } from './default' 3 | import { getAttributeValue } from 'domutils' 4 | 5 | /** 6 | * Configuration for Payload CMS 7 | * 8 | * Tested for v1.1.21 9 | */ 10 | 11 | export const config: Config = { 12 | ...defaultConfig, 13 | elementTags: { 14 | ...defaultConfig.elementTags, 15 | a: (el) => ({ 16 | type: 'link', 17 | linkType: el && getAttributeValue(el, 'data-link-type'), 18 | newTab: el && getAttributeValue(el, 'target') === '_blank', 19 | url: el && getAttributeValue(el, 'href'), 20 | }), 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /tools/sync-versions.js: -------------------------------------------------------------------------------- 1 | const mainPackageJson = require('../package.json'); 2 | const glob = require('glob'); 3 | const fs = require('fs'); 4 | 5 | glob.sync('./packages/**/package.json') 6 | .forEach(location => { 7 | const packageJson = JSON.parse(fs.readFileSync(location)) 8 | fs.writeFileSync(location, JSON.stringify({ 9 | ...packageJson, 10 | version: mainPackageJson.version, 11 | ...(packageJson.dependencies?.['@slate-serializers/dom'] && { 12 | dependencies: { 13 | ...packageJson.dependencies, 14 | "@slate-serializers/dom": mainPackageJson.version 15 | } 16 | }), 17 | }, null, 3)) 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /packages/dom/src/lib/serializers.ts: -------------------------------------------------------------------------------- 1 | import { AnyNode, Document } from 'domhandler' 2 | 3 | import { config as defaultConfig } from './config/default' 4 | import { Config } from './config/types' 5 | import { convertSlate } from './utilities/convert-slate' 6 | 7 | type SlateToDom = (node: any[], config?: Config) => AnyNode | ArrayLike 8 | 9 | export const slateToDom: SlateToDom = (node: any[], config = defaultConfig) => { 10 | if (!Array.isArray(node)) { 11 | return new Document([]) 12 | } 13 | const document = node.map((n, index) => 14 | convertSlate({ 15 | node: n, 16 | config, 17 | isLastNodeInDocument: index === node.length - 1, 18 | }), 19 | ) 20 | return document 21 | } 22 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/htmlToSlate/config/slateDemo.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './types' 2 | import { config as defaultConfig } from './default' 3 | 4 | export const config: Config = { 5 | ...defaultConfig, 6 | elementTags: { 7 | blockquote: () => ({ 8 | type: 'block-quote', 9 | }), 10 | h1: () => ({ 11 | type: 'heading-one', 12 | }), 13 | h2: () => ({ 14 | type: 'heading-two', 15 | }), 16 | li: () => ({ 17 | type: 'list-item', 18 | }), 19 | ol: () => ({ 20 | type: 'numbered-list', 21 | }), 22 | ul: () => ({ 23 | type: 'bulleted-list', 24 | }), 25 | p: () => ({ 26 | type: 'paragraph', 27 | }), 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /packages/html/src/lib/tests/slateToHtml/configuration/markMap.spec.ts: -------------------------------------------------------------------------------- 1 | import { slateToHtml, slateToHtmlConfig } from '@slate-serializers/html' 2 | 3 | describe("slateToHtml markMap", () => { 4 | it('processes a mark map value', () => { 5 | const html = '

Subscript text

' 6 | const slate = [ 7 | { 8 | type: 'p', 9 | children: [ 10 | { 11 | text: 'Subscript text', 12 | subScript: true, 13 | }, 14 | ], 15 | }, 16 | ] 17 | const config = { 18 | ...slateToHtmlConfig, 19 | markMap: { 20 | subScript: ['sub'], 21 | }, 22 | } 23 | expect(slateToHtml(slate, config)).toEqual(html) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /.verdaccio/config.yml: -------------------------------------------------------------------------------- 1 | # path to a directory with all packages 2 | storage: ../tmp/local-registry/storage 3 | 4 | # a list of other known repositories we can talk to 5 | uplinks: 6 | npmjs: 7 | url: https://registry.npmjs.org/ 8 | maxage: 60m 9 | 10 | packages: 11 | '**': 12 | # give all users (including non-authenticated users) full access 13 | # because it is a local registry 14 | access: $all 15 | publish: $all 16 | unpublish: $all 17 | 18 | # if package is not available locally, proxy requests to npm registry 19 | proxy: npmjs 20 | 21 | # log settings 22 | logs: 23 | type: stdout 24 | format: pretty 25 | level: warn 26 | 27 | publish: 28 | allow_offline: true # set offline to true to allow publish offline 29 | -------------------------------------------------------------------------------- /packages/dom/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | // Serializers 2 | export { slateToDom } from './serializers' 3 | 4 | // Configuration objects 5 | // slateToDom 6 | export type { BaseConfig, Config as SlateToDomConfig } from './config/types' 7 | export { config as slateToDomConfig } from './config/default' 8 | export { config as payloadSlateToDomConfig } from './config/payload' 9 | export { config as slateDemoSlateToDomConfig } from './config/slateDemo' 10 | 11 | // Useful types 12 | export type { ElementTransform, MarkTransform } from './config/types' 13 | 14 | // Slate to DOM utilities 15 | export { convertSlate } from './utilities/convert-slate' 16 | export { extractCssFromStyle } from './utilities/domhandler' 17 | export { isEmptyObject, styleMapToAttribs } from './utilities' 18 | -------------------------------------------------------------------------------- /packages/utilities/src/lib/utilities.ts: -------------------------------------------------------------------------------- 1 | export const hasLineBreak = (str: string) => str.match(/[\r\n]+/) !== null 2 | 3 | export const prependSpace = (str: string) => str && ` ${str.trim()}` 4 | 5 | export const isEmptyObject = (obj: any) => 6 | obj && Object.keys(obj).length === 0 && Object.getPrototypeOf(obj) === Object.prototype 7 | 8 | export const removeEmpty = (obj: {}): {} => { 9 | return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) 10 | } 11 | 12 | /** 13 | * 14 | * @param obj an object of any dimension 15 | * @param args property list to check 16 | * @returns undefined or property value 17 | */ 18 | export const getNested = (obj: any, ...args: string[]) => { 19 | return args.reduce((o, level) => o && o[level], obj) 20 | } 21 | -------------------------------------------------------------------------------- /packages/template/src/lib/serializers.ts: -------------------------------------------------------------------------------- 1 | import { config as slateToTemplateConfig } from './config/default' 2 | import type { Config as SlateToTemplateConfig } from './config/types' 3 | import { slateToHtml } from '@slate-serializers/html' 4 | 5 | export const slateToTemplate = (node: any[], config: SlateToTemplateConfig = slateToTemplateConfig) => { 6 | if (!Array.isArray(node)) { 7 | return 8 | } 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | return node.map((child: any) => { 11 | const type = child.type 12 | if (type && config.customElementSerializers?.[type]) { 13 | return config.customElementSerializers?.[type]({ node: child }) 14 | } else { 15 | return slateToHtml([child], config) 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /packages/html/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). 4 | 5 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/html-v2.2.3...html-v2.3.0) (2025-05-16) 6 | 7 | 8 | ### Miscellaneous Chores 9 | 10 | * **html:** Synchronize all versions 11 | 12 | ## 0.1.0 (2023-07-19) 13 | 14 | 15 | ### Features 16 | 17 | * version management, better exports and types ([c8f049a](https://github.com/thompsonsj/slate-serializers/commit/c8f049ad24b4fefa07b71f091d202dd6e72ce10b)) 18 | 19 | 20 | ### Code Refactoring 21 | 22 | * migrate to nx integrated monorepo ([#81](https://github.com/thompsonsj/slate-serializers/issues/81)) ([e089f7c](https://github.com/thompsonsj/slate-serializers/commit/e089f7cfc6e4616f209189807404ae84bc691eba)) 23 | -------------------------------------------------------------------------------- /packages/utilities/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). 4 | 5 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/utilities-v2.2.3...utilities-v2.3.0) (2025-05-16) 6 | 7 | 8 | ### Miscellaneous Chores 9 | 10 | * **utilities:** Synchronize all versions 11 | 12 | ## 0.1.0 (2023-07-19) 13 | 14 | 15 | ### Features 16 | 17 | * version management, better exports and types ([c8f049a](https://github.com/thompsonsj/slate-serializers/commit/c8f049ad24b4fefa07b71f091d202dd6e72ce10b)) 18 | 19 | 20 | ### Code Refactoring 21 | 22 | * migrate to nx integrated monorepo ([#81](https://github.com/thompsonsj/slate-serializers/issues/81)) ([e089f7c](https://github.com/thompsonsj/slate-serializers/commit/e089f7cfc6e4616f209189807404ae84bc691eba)) 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Additions 42 | .nx/* 43 | 44 | vite.config.*.timestamp* 45 | vitest.config.*.timestamp* 46 | .cursor/rules/nx-rules.mdc 47 | .github/instructions/nx.instructions.md 48 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap-sha": "24e1311e8fab931be3f003511dd58da710d9cc63", 3 | "packages": { 4 | ".": {}, 5 | "packages/dom": { 6 | "component": "dom" 7 | }, 8 | "packages/html": { 9 | "component": "html" 10 | }, 11 | "packages/react": { 12 | "component": "react" 13 | }, 14 | "packages/slate-serializers": { 15 | "component": "slate-serializers" 16 | }, 17 | "packages/template": { 18 | "component": "template" 19 | }, 20 | "packages/tests": { 21 | "component": "tests" 22 | }, 23 | "packages/utilities": { 24 | "component": "utilities" 25 | } 26 | }, 27 | "plugins": [ 28 | { 29 | "type": "linked-versions", 30 | "groupName": "all", 31 | "components": ["dom", "html", "react", "slate-serializers", "template", "utilities"] 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/tests/src/lib/react/__snapshots__/style-object.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`style attribute css transforms with postcss mark transform 1`] = ` 4 |
5 |

6 | 7 | Paragraph 8 | 9 |

10 |
11 | `; 12 | 13 | exports[`style attribute css transforms with postcss mark transforms on multiple marks 1`] = ` 14 |
15 |

16 | This is editable 17 | 18 | rich 19 | 20 | text, 21 | 22 | much 23 | 24 | better than a 25 |

26 |       <textarea>
27 |     
28 | ! 29 |

30 |
31 | `; 32 | 33 | exports[`style attribute css transforms with postcss p tag element transform 1`] = ` 34 |
35 |

36 | 37 | Paragraph 38 | 39 |

40 |
41 | `; 42 | -------------------------------------------------------------------------------- /packages/tests/src/lib/fixtures/styles-in-leaf.ts: -------------------------------------------------------------------------------- 1 | interface Ifixture { 2 | name: string 3 | html: string 4 | slate: object[] 5 | } 6 | 7 | export const fixtures: Ifixture[] = [ 8 | { 9 | name: 'p tag element transform', 10 | html: '

some title!

', 11 | slate: [ 12 | { 13 | type: 'p', 14 | children: [ 15 | { 16 | backgroundColor: '#38761D', 17 | color: '#FE9900', 18 | fontFamily: "arial narrow", 19 | fontSize: "20px", 20 | text: "some title!", 21 | bold: true, 22 | italic: true, 23 | underline: true, 24 | }, 25 | ] 26 | } 27 | ] 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /packages/slate-serializers/src/lib/slate-serializers.ts: -------------------------------------------------------------------------------- 1 | // Serializers 2 | export { htmlToSlate, slateToHtml } from '@slate-serializers/html' 3 | export { slateToDom } from '@slate-serializers/dom' 4 | 5 | // Configuration objects 6 | // slateToDom 7 | export { SlateToDomConfig } from '@slate-serializers/dom' 8 | export { slateToDomConfig } from '@slate-serializers/dom' 9 | export { payloadSlateToDomConfig } from '@slate-serializers/dom' 10 | export { slateDemoSlateToDomConfig } from '@slate-serializers/dom' 11 | 12 | // htmlToSlate 13 | export { HtmlToSlateConfig } from '@slate-serializers/html' 14 | export { htmlToSlateConfig } from '@slate-serializers/html' 15 | export { payloadHtmlToSlateConfig } from '@slate-serializers/html' 16 | export { slateDemoHtmlToSlateConfig } from '@slate-serializers/html' 17 | 18 | // Useful types 19 | export { ElementTransform as ElementTagTransformFunction } from '@slate-serializers/dom' 20 | -------------------------------------------------------------------------------- /packages/tests/src/lib/react/style-object.spec.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import { SlateToReact, slateToReactConfig as defaultReactConfig, SlateToReactConfig } from '@slate-serializers/react' 4 | import { styleObjectFixtures } from './../tests' 5 | import { transformStyleObjectToString } from '@slate-serializers/utilities' 6 | 7 | const reactConfig: SlateToReactConfig = { 8 | ...defaultReactConfig, 9 | elementAttributeTransform: ({ node }) => { 10 | const style = transformStyleObjectToString(node.style) 11 | return style ? { style } : undefined 12 | } 13 | } 14 | 15 | describe('style attribute css transforms with postcss', () => { 16 | for (const fixture of styleObjectFixtures) { 17 | it(`${fixture.name}`, () => { 18 | const tree = render() 19 | expect(tree.container).toMatchSnapshot() 20 | }) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [23.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /packages/dom/src/lib/config/slateDemo.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './types' 2 | import { isEmptyObject, styleMapToAttribs } from '../utilities' 3 | 4 | const ELEMENT_NAME_TAG_MAP = { 5 | ['block-quote']: 'blockquote', 6 | ['heading-one']: 'h1', 7 | ['heading-two']: 'h2', 8 | ['list-item']: 'li', 9 | ['numbered-list']: 'ol', 10 | ['bulleted-list']: 'ul', 11 | paragraph: 'p', 12 | } 13 | 14 | const MARK_ELEMENT_TAG_MAP = { 15 | strikethrough: ['s'], 16 | bold: ['strong'], 17 | underline: ['u'], 18 | italic: ['i'], 19 | code: ['pre', 'code'], 20 | } 21 | 22 | export const config: Config = { 23 | markMap: MARK_ELEMENT_TAG_MAP, 24 | elementMap: ELEMENT_NAME_TAG_MAP, 25 | elementTransforms: {}, 26 | elementAttributeTransform: ({ node }) => { 27 | const elementStyleMap: { [key: string]: string } = { 28 | align: 'textAlign', 29 | } 30 | const attribs = styleMapToAttribs({elementStyleMap, node}) 31 | return isEmptyObject(attribs) ? {} : attribs 32 | }, 33 | encodeEntities: true, 34 | } 35 | -------------------------------------------------------------------------------- /packages/html/src/lib/tests/slateToHtml/configuration/markTransforms.spec.ts: -------------------------------------------------------------------------------- 1 | import { Element } from 'domhandler' 2 | import { slateToHtml, slateToHtmlConfig, SlateToHtmlConfig } from "@slate-serializers/html" 3 | 4 | describe("slateToHtml markTransforms", () => { 5 | it('processes a mark transform', () => { 6 | const html = '

Paragraph

' 7 | const slate = [ 8 | { 9 | type: 'p', 10 | children: [ 11 | { 12 | bold: true, 13 | fontSize: '96px', 14 | text: 'Paragraph', 15 | }, 16 | ], 17 | }, 18 | ] 19 | const config: SlateToHtmlConfig = { 20 | ...slateToHtmlConfig, 21 | markTransforms: { 22 | ...slateToHtmlConfig.markTransforms, 23 | fontSize: ({ node }) => { 24 | return new Element('span', { 25 | style: `font-size:${node.fontSize};`, 26 | }) 27 | }, 28 | }, 29 | } 30 | expect(slateToHtml(slate, config)).toEqual(html) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2020", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@slate-serializers/dom": ["packages/dom/src/index.ts"], 19 | "@slate-serializers/html": ["packages/html/src/index.ts"], 20 | "@slate-serializers/react": ["packages/react/src/index.ts"], 21 | "@slate-serializers/template": ["packages/template/src/index.ts"], 22 | "@slate-serializers/tests": ["packages/tests/src/index.ts"], 23 | "@slate-serializers/utilities": ["packages/utilities/src/index.ts"], 24 | "slate-serializers": ["packages/slate-serializers/src/index.ts"] 25 | } 26 | }, 27 | "exclude": ["node_modules", "tmp"] 28 | } 29 | -------------------------------------------------------------------------------- /packages/html/src/lib/tests/slateToHtml/configuration/elementTransforms.spec.ts: -------------------------------------------------------------------------------- 1 | import { Element } from 'domhandler' 2 | import { slateToHtml, slateToHtmlConfig } from "@slate-serializers/html" 3 | 4 | describe("slateToHtml elementTransforms", () => { 5 | it('processes an element transform', () => { 6 | const html = '

Paragraph

' 7 | const slate = [ 8 | { 9 | type: 'p', 10 | children: [ 11 | { 12 | text: 'Paragraph', 13 | }, 14 | ], 15 | }, 16 | { 17 | type: 'image', 18 | url: 'https://picsum.photos/id/237/200/300', 19 | }, 20 | ] 21 | const config = { 22 | ...slateToHtmlConfig, 23 | elementTransforms: { 24 | ...slateToHtmlConfig.elementTransforms, 25 | image: ({ node }: { node?: any }) => { 26 | return new Element('img', { 27 | src: node.url, 28 | }) 29 | }, 30 | }, 31 | } 32 | expect(slateToHtml(slate, config)).toEqual(html) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/html/src/lib/html.ts: -------------------------------------------------------------------------------- 1 | // Serializers 2 | export { htmlToSlate } from './serializers/htmlToSlate' 3 | export { slateToHtml } from './serializers/slateToHtml' 4 | 5 | // Configuration objects 6 | // slateToDom - renamed to slateToHtml 7 | // slateToHtml uses the same config as slateToDom but it makes sense to rename the config objects in case we want to add a slateToHtml specific config in the future 8 | export { 9 | type SlateToDomConfig as SlateToHtmlConfig, 10 | slateToDomConfig as slateToHtmlConfig, 11 | payloadSlateToDomConfig as payloadSlateToHtmlConfig, 12 | slateDemoSlateToDomConfig as slateDemoSlateToHtmlConfig, 13 | type ElementTransform, 14 | type MarkTransform, 15 | } from '@slate-serializers/dom' 16 | 17 | // htmlToSlate 18 | export type { Config as HtmlToSlateConfig } from './serializers/htmlToSlate/config/types' 19 | export { config as htmlToSlateConfig } from './serializers/htmlToSlate/config/default' 20 | export { config as payloadHtmlToSlateConfig } from './serializers/htmlToSlate/config/payload' 21 | export { config as slateDemoHtmlToSlateConfig } from './serializers/htmlToSlate/config/slateDemo' 22 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/slateToHtml/slateDemo.spec.ts: -------------------------------------------------------------------------------- 1 | import { slateToHtml } from '.' 2 | import { slateDemoSlateToDomConfig } from '@slate-serializers/dom' 3 | 4 | describe('slateToHtml expected behaviour', () => { 5 | it('adds the `text-align:right;` css property/value to style', () => { 6 | const html = '

This is a right aligned paragraph.

' 7 | const slate = [ 8 | { 9 | align: 'right', 10 | children: [ 11 | { 12 | text: 'This is a right aligned paragraph.', 13 | }, 14 | ], 15 | type: 'paragraph', 16 | }, 17 | ] 18 | expect(slateToHtml(slate, slateDemoSlateToDomConfig)).toEqual(html) 19 | }) 20 | 21 | it('does not add empty style attributes', () => { 22 | const html = '

This is a right aligned paragraph.

' 23 | const slate = [ 24 | { 25 | children: [ 26 | { 27 | text: 'This is a right aligned paragraph.', 28 | }, 29 | ], 30 | type: 'paragraph', 31 | }, 32 | ] 33 | expect(slateToHtml(slate, slateDemoSlateToDomConfig)).toEqual(html) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 [Steven Thompson](https://github.com/thompsonsj) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/tests/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/packages/tests", 12 | "main": "packages/tests/src/index.ts", 13 | "tsConfig": "packages/tests/tsconfig.lib.json", 14 | "assets": ["packages/tests/*.md"] 15 | } 16 | }, 17 | "lint": { 18 | "executor": "@nx/linter:eslint", 19 | "outputs": ["{options.outputFile}"], 20 | "options": { 21 | "lintFilePatterns": ["packages/tests/**/*.ts"] 22 | } 23 | }, 24 | "test": { 25 | "executor": "@nx/jest:jest", 26 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 27 | "options": { 28 | "jestConfig": "packages/tests/jest.config.ts", 29 | "passWithNoTests": true 30 | }, 31 | "configurations": { 32 | "ci": { 33 | "ci": true, 34 | "codeCoverage": true 35 | } 36 | } 37 | } 38 | }, 39 | "tags": [] 40 | } 41 | -------------------------------------------------------------------------------- /packages/tests/src/lib/template/combined-payload.spec.ts: -------------------------------------------------------------------------------- 1 | import { combinedFixtures, elementFixtures, textFixtures } from '../tests' 2 | import { payloadSlateToTemplateConfig, slateToTemplate } from '@slate-serializers/template' 3 | 4 | describe('Slate JSON to React transforms', () => { 5 | describe('Element tags', () => { 6 | const fixtures = elementFixtures 7 | for (const fixture of fixtures) { 8 | it(`${fixture.name}`, () => { 9 | const tree = slateToTemplate(fixture.slate, payloadSlateToTemplateConfig) 10 | expect(tree).toMatchSnapshot() 11 | }) 12 | } 13 | }) 14 | describe('Text tags', () => { 15 | const fixtures = textFixtures 16 | for (const fixture of fixtures) { 17 | it(`${fixture.name}`, () => { 18 | const tree = slateToTemplate(fixture.slate, payloadSlateToTemplateConfig) 19 | expect(tree).toMatchSnapshot() 20 | }) 21 | } 22 | }) 23 | describe('Combined', () => { 24 | const fixtures = combinedFixtures 25 | for (const fixture of fixtures) { 26 | it(`${fixture.name}`, () => { 27 | const tree = slateToTemplate(fixture.slateOriginal, payloadSlateToTemplateConfig) 28 | expect(tree).toMatchSnapshot() 29 | }) 30 | } 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": { 28 | "@typescript-eslint/no-extra-semi": "error", 29 | "no-extra-semi": "off" 30 | } 31 | }, 32 | { 33 | "files": ["*.js", "*.jsx"], 34 | "extends": ["plugin:@nx/javascript"], 35 | "rules": { 36 | "@typescript-eslint/no-extra-semi": "error", 37 | "no-extra-semi": "off" 38 | } 39 | }, 40 | { 41 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 42 | "env": { 43 | "jest": true 44 | }, 45 | "rules": {} 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/tests/src/lib/react/combined.spec.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import { combinedFixtures, elementFixtures, textFixtures } from '../tests' 4 | import { SlateToReact } from '@slate-serializers/react' 5 | 6 | describe('Slate JSON to React transforms', () => { 7 | describe('Element tags', () => { 8 | const fixtures = elementFixtures 9 | for (const fixture of fixtures) { 10 | it(`${fixture.name}`, () => { 11 | const tree = render() 12 | expect(tree.container).toMatchSnapshot() 13 | }) 14 | } 15 | }) 16 | describe('Text tags', () => { 17 | const fixtures = textFixtures 18 | for (const fixture of fixtures) { 19 | it(`${fixture.name}`, () => { 20 | const tree = render() 21 | expect(tree.container).toMatchSnapshot() 22 | }) 23 | } 24 | }) 25 | describe('Combined', () => { 26 | const fixtures = combinedFixtures 27 | for (const fixture of fixtures) { 28 | it(`${fixture.name}`, () => { 29 | const tree = render() 30 | expect(tree.container).toMatchSnapshot() 31 | }) 32 | } 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/react/src/lib/config/default.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import { Config } from './types' 3 | import { ulid } from 'ulidx' 4 | import { slateToDomConfig } from '@slate-serializers/dom' 5 | 6 | const BlockQuote = ({ children }: { children: ReactNode }) => ( 7 |
8 |

{children}

9 |
10 | ) 11 | 12 | export const config: Config = { 13 | markMap: slateToDomConfig.markMap, 14 | elementMap: slateToDomConfig.elementMap, 15 | elementAttributeTransform: slateToDomConfig.elementAttributeTransform, 16 | defaultTag: slateToDomConfig.defaultTag, 17 | encodeEntities: slateToDomConfig.encodeEntities, 18 | alwaysEncodeBreakingEntities: slateToDomConfig.alwaysEncodeBreakingEntities, 19 | alwaysEncodeCodeEntities: slateToDomConfig.alwaysEncodeCodeEntities, 20 | convertLineBreakToBr: slateToDomConfig.convertLineBreakToBr, 21 | elementTransforms: { 22 | quote: ({ children }) => { 23 | return
{children}
24 | }, 25 | link: ({ node, children = [] }) => { 26 | const attrs: {[key: string]: string} = {} 27 | if (node.newTab) { 28 | attrs.target = '_blank' 29 | } 30 | return ( 31 | 32 | {children} 33 | 34 | ) 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /packages/slate-serializers/README.md: -------------------------------------------------------------------------------- 1 | # slate-serializers 2 | 3 | This package has been split into separate packages. 4 | 5 | There is no need to change - `slate-serializers` will continue to be maintained alongside the new serializers. However, upgrading will keep the dependency tree minimal for your project. 6 | 7 | Original docs available at [packages/slate-serializers/docs/original.md](https://github.com/thompsonsj/slate-serializers/blob/main/packages/slate-serializers/docs/original.md). 8 | 9 | ## New packages 10 | 11 | An overview is available at [README.md](https://github.com/thompsonsj/slate-serializers/blob/main/README.md). 12 | 13 | ## Upgrade 14 | 15 | ### HTML serializers 16 | 17 | Change import from `slate-serializers` to `@slate-serializers/html`. 18 | 19 | If importing configuration objects, change the name as follows. 20 | - `slateToDomConfig` to `slateToHtmlConfig`. 21 | - `payloadSlateToDomConfig` to `payloadSlateToHtmlConfig`. 22 | - `slateDemoSlateToDomConfig` to `slateDemoSlateToHtmlConfig` 23 | 24 | ### DOM serializer 25 | 26 | Change import from `slate-serializers` to `@slate-serializers/dom`. 27 | 28 | ## Rationale for splitting the packages 29 | 30 | As more serializers are introduced, the number of dependencies increase. It makes sense to maintain separate packages to keep the serializers as efficient as possible for their desired use case. 31 | -------------------------------------------------------------------------------- /packages/dom/src/lib/utilities/domhandler.ts: -------------------------------------------------------------------------------- 1 | import { getAttributeValue } from 'domutils' 2 | import { Element, Text } from 'domhandler' 3 | import serializer from 'dom-serializer' 4 | import { parseStyleCssText } from '.' 5 | 6 | /** 7 | * Generate nested mark elements 8 | * 9 | * nestedMarkElements should be recursive, but it works 10 | * so leaving it for now. Can handle a maximum of 5 11 | * elements. Really shouldn't be any more than that! 12 | */ 13 | 14 | export const nestedMarkElementsString = (els: Element[], text: string) => { 15 | return serializer(nestedMarkElements(els, new Text(text))) 16 | } 17 | 18 | export const nestedMarkElements = (els: Element[], element: Element | Text) => { 19 | while (els && els.length > 0) { 20 | const el = els.pop() 21 | if (el) { 22 | el.children = [element] 23 | element = el 24 | } 25 | } 26 | return element 27 | } 28 | 29 | /** 30 | * Extract css value from style attribute 31 | * @param el domhandler Element 32 | * @param attribute css attribute in camelCase 33 | * @returns css value or null 34 | */ 35 | export const extractCssFromStyle = (el: Element, attribute: string): string | null => { 36 | const cssText = el && getAttributeValue(el, 'style') 37 | if (cssText) { 38 | const css = parseStyleCssText(cssText) 39 | if (css[attribute]) { 40 | return css[attribute] 41 | } 42 | } 43 | return null 44 | } 45 | -------------------------------------------------------------------------------- /packages/tests/src/lib/react/combined-payload.spec.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import { combinedFixtures, elementFixtures, textFixtures } from '../tests' 4 | import { SlateToReact, payloadSlateToReactConfig } from '@slate-serializers/react' 5 | 6 | describe('Slate JSON to React transforms', () => { 7 | describe('Element tags', () => { 8 | const fixtures = elementFixtures 9 | for (const fixture of fixtures) { 10 | it(`${fixture.name}`, () => { 11 | const tree = render() 12 | expect(tree.container).toMatchSnapshot() 13 | }) 14 | } 15 | }) 16 | describe('Text tags', () => { 17 | const fixtures = textFixtures 18 | for (const fixture of fixtures) { 19 | it(`${fixture.name}`, () => { 20 | const tree = render() 21 | expect(tree.container).toMatchSnapshot() 22 | }) 23 | } 24 | }) 25 | describe('Combined', () => { 26 | const fixtures = combinedFixtures 27 | for (const fixture of fixtures) { 28 | it(`${fixture.name}`, () => { 29 | const tree = render() 30 | expect(tree.container).toMatchSnapshot() 31 | }) 32 | } 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/react/src/lib/config/payload.tsx: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @nx/enforce-module-boundaries 2 | import { config as slateToReactConfig } from './default' 3 | import { payloadSlateToDomConfig } from '@slate-serializers/dom' 4 | import { Config as SlateToReactConfig } from './types' 5 | import { ulid } from 'ulidx' 6 | 7 | /** 8 | * Configuration for Payload CMS 9 | * 10 | * Tested for v1.1.21 11 | */ 12 | export const config: SlateToReactConfig = { 13 | markMap: payloadSlateToDomConfig.markMap, 14 | elementMap: payloadSlateToDomConfig.elementMap, 15 | elementAttributeTransform: payloadSlateToDomConfig.elementAttributeTransform, 16 | defaultTag: payloadSlateToDomConfig.defaultTag, 17 | encodeEntities: payloadSlateToDomConfig.encodeEntities, 18 | alwaysEncodeBreakingEntities: payloadSlateToDomConfig.alwaysEncodeBreakingEntities, 19 | alwaysEncodeCodeEntities: payloadSlateToDomConfig.alwaysEncodeCodeEntities, 20 | convertLineBreakToBr: payloadSlateToDomConfig.convertLineBreakToBr, 21 | elementTransforms: { 22 | ...slateToReactConfig.elementTransforms, 23 | link: ({ node, children = [] }) => { 24 | const attrs: {[key: string]: string} = {} 25 | if (node.linkType) { 26 | attrs['data-link-type'] = node.linkType 27 | } 28 | if (node.newTab) { 29 | attrs.target = '_blank' 30 | } 31 | return ( 32 | 33 | {children} 34 | 35 | ) 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /packages/dom/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). 4 | 5 | ## [2.3.0](https://github.com/thompsonsj/slate-serializers/compare/dom-v2.2.3...dom-v2.3.0) (2025-05-16) 6 | 7 | 8 | ### Features 9 | 10 | * **react:** flatten config ([#191](https://github.com/thompsonsj/slate-serializers/issues/191)) ([761c651](https://github.com/thompsonsj/slate-serializers/commit/761c651ad49fda360b416ab561b9aac72ed7aa8d)) 11 | 12 | ## 0.1.0 (2023-07-19) 13 | 14 | 15 | ### Features 16 | 17 | * version management, better exports and types ([#82](https://github.com/thompsonsj/slate-serializers/issues/82)) ([fc3c828](https://github.com/thompsonsj/slate-serializers/commit/fc3c828b13dae411acdb985753986e451d114f1d)) 18 | 19 | 20 | ### Code Refactoring 21 | 22 | * migrate to nx integrated monorepo ([#81](https://github.com/thompsonsj/slate-serializers/issues/81)) ([e089f7c](https://github.com/thompsonsj/slate-serializers/commit/e089f7cfc6e4616f209189807404ae84bc691eba)) 23 | 24 | ## 0.1.0 (2023-07-19) 25 | 26 | 27 | ### Features 28 | 29 | * version management, better exports and types ([c8f049a](https://github.com/thompsonsj/slate-serializers/commit/c8f049ad24b4fefa07b71f091d202dd6e72ce10b)) 30 | 31 | 32 | ### Code Refactoring 33 | 34 | * migrate to nx integrated monorepo ([#81](https://github.com/thompsonsj/slate-serializers/issues/81)) ([e089f7c](https://github.com/thompsonsj/slate-serializers/commit/e089f7cfc6e4616f209189807404ae84bc691eba)) 35 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/htmlToSlate/config/types.ts: -------------------------------------------------------------------------------- 1 | import { Element } from 'domhandler' 2 | 3 | interface ItagMap { 4 | [key: string]: (a?: Element) => object 5 | } 6 | 7 | export type AttributeTransform = ({ 8 | el, 9 | }: { 10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 11 | el: Element 12 | }) => { [key: string]: unknown } | undefined 13 | 14 | /** 15 | * For details on configuration options: 16 | * @see /docs/config/htmlToSlate.md 17 | */ 18 | export interface Config { 19 | /* Use a custom function to generate Slate node attributes based on every Element passed through the serializer */ 20 | elementAttributeTransform?: AttributeTransform 21 | /* Map HTML element tags to Slate JSON object attributes */ 22 | elementTags: ItagMap 23 | /* Map HTML text tags to Slate JSON object attributes */ 24 | textTags: ItagMap 25 | /* Perform string operations on the html string before it is parsed to a DOM Document Object Model */ 26 | htmlPreProcessString?: (html: string) => string 27 | /* Pass updater functions to modify HTML */ 28 | htmlUpdaterMap?: HtmlUpdaterFunctionMap 29 | /* Remove whitespace that does not contribute meaning */ 30 | filterWhitespaceNodes: boolean 31 | /* Convert br tags to a new line character (\n) */ 32 | convertBrToLineBreak?: boolean 33 | /* Replace multiple whitespace characters with a single space. */ 34 | trimWhiteSpace?: boolean 35 | } 36 | 37 | type UpdaterFunction = (el: Element) => Element | string 38 | 39 | export type HtmlUpdaterFunctionMap = Record 40 | -------------------------------------------------------------------------------- /packages/dom/src/lib/dom.spec.ts: -------------------------------------------------------------------------------- 1 | import { slateToDom } from './dom' 2 | import { selectAll } from 'css-select' 3 | import { parseDocument } from 'htmlparser2' 4 | import { replaceElement } from 'domutils' 5 | import { AnyNode, Element, Node } from 'domhandler' 6 | import serializer from 'dom-serializer' 7 | 8 | describe('slateToDom expected behaviour', () => { 9 | it('can modify resulting DOM document object', () => { 10 | const html = `

Heading 2

Paragraph.

Second Heading 2

` 11 | const slate = [ 12 | { 13 | children: [ 14 | { 15 | text: 'Heading 2', 16 | }, 17 | ], 18 | type: 'h2', 19 | }, 20 | { 21 | children: [ 22 | { 23 | text: 'Paragraph.', 24 | }, 25 | ], 26 | type: 'p', 27 | }, 28 | { 29 | children: [ 30 | { 31 | text: 'Second Heading 2', 32 | }, 33 | ], 34 | type: 'h2', 35 | }, 36 | ] 37 | // serializer and parseDocument are required operations before 38 | // replaceElement will take effect. This negates the usefulness 39 | // of slateToDom in this use case. 40 | const document = parseDocument(serializer(slateToDom(slate))) 41 | selectAll('h2', document as any).forEach((element: any) => { 42 | replaceElement(element, new Element('h2', { ...element.attribs, class: 'heading-two' }, element.children)) 43 | }) 44 | expect(serializer(document as AnyNode)).toEqual(html) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /packages/tests/src/lib/html/sameHtmlSlateBothWays.spec.ts: -------------------------------------------------------------------------------- 1 | import { htmlToSlate, slateToHtml } from '@slate-serializers/html' 2 | import { elementFixtures, textFixtures } from '../tests' 3 | 4 | /** 5 | * Run tests both ways by using the same fixtures 6 | * 7 | * * textFixtures 8 | * * elementFixtures 9 | * 10 | * Test we get expected results in all fixtures using 11 | * * htmlToSlate 12 | * * slateToHtml 13 | */ 14 | 15 | describe('HTML to Slate JSON transforms', () => { 16 | describe('Element tags', () => { 17 | const fixtures = elementFixtures 18 | for (const fixture of fixtures) { 19 | it(`${fixture.name}`, () => { 20 | expect(htmlToSlate(fixture.html)).toEqual(fixture.slate) 21 | }) 22 | } 23 | }) 24 | describe('Text tags', () => { 25 | const fixtures = textFixtures 26 | for (const fixture of fixtures) { 27 | it(`${fixture.name}`, () => { 28 | expect(htmlToSlate(fixture.html)).toEqual(fixture.slate) 29 | }) 30 | } 31 | }) 32 | }) 33 | 34 | describe('Slate JSON to HTML transforms', () => { 35 | describe('Element tags', () => { 36 | const fixtures = elementFixtures 37 | for (const fixture of fixtures) { 38 | it(`${fixture.name}`, () => { 39 | expect(slateToHtml(fixture.slate)).toEqual(fixture.htmlFromSlate || fixture.html) 40 | }) 41 | } 42 | }) 43 | describe('Text tags', () => { 44 | const fixtures = textFixtures 45 | for (const fixture of fixtures) { 46 | it(`${fixture.name}`, () => { 47 | expect(slateToHtml(fixture.slate)).toEqual(fixture.html) 48 | }) 49 | } 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /packages/dom/src/lib/config/types.ts: -------------------------------------------------------------------------------- 1 | import { ChildNode, Element } from 'domhandler' 2 | 3 | export type MarkTransform = ({ node, attribs }: { 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | node?: any; 6 | attribs?: { [key: string]: string } 7 | }) => Element | undefined 8 | 9 | interface MarkTransforms { 10 | [key: string]: MarkTransform 11 | } 12 | 13 | export type ElementTransform = ({ 14 | node, 15 | attribs, 16 | children, 17 | }: { 18 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 19 | node?: any 20 | attribs?: { [key: string]: string } 21 | children?: ChildNode[] 22 | }) => Element | undefined 23 | 24 | export type AttributeTransform = ({ 25 | node, 26 | }: { 27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 | node: any 29 | }) => { [key: string]: string } | undefined 30 | 31 | interface ElementTransforms { 32 | [key: string]: ElementTransform 33 | } 34 | 35 | export interface BaseConfig { 36 | markMap: { [key: string]: string[] } 37 | elementMap: { [key: string]: string } 38 | // markAttributeTransform?: AttributeTransform 39 | elementAttributeTransform?: AttributeTransform 40 | defaultTag?: string 41 | encodeEntities?: boolean 42 | alwaysEncodeBreakingEntities?: boolean 43 | alwaysEncodeCodeEntities?: boolean 44 | convertLineBreakToBr?: boolean 45 | } 46 | 47 | export interface Config extends BaseConfig { 48 | markTransforms?: MarkTransforms 49 | elementTransforms: ElementTransforms 50 | } 51 | 52 | type UpdaterFunction = (el: Element) => Element | string 53 | 54 | export type HtmlUpdaterFunctionMap = Record 55 | -------------------------------------------------------------------------------- /packages/react/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/react/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "deploy": { 9 | "executor": "ngx-deploy-npm:deploy", 10 | "options": { 11 | "distFolderPath": "dist/packages/react", 12 | "access": "public" 13 | }, 14 | "dependsOn": ["build"] 15 | }, 16 | "lint": { 17 | "executor": "@nx/eslint:lint", 18 | "outputs": ["{options.outputFile}"] 19 | }, 20 | "build": { 21 | "executor": "@nx/rollup:rollup", 22 | "outputs": ["{options.outputPath}"], 23 | "options": { 24 | "outputPath": "dist/packages/react", 25 | "tsConfig": "packages/react/tsconfig.lib.json", 26 | "project": "packages/react/package.json", 27 | "entryFile": "packages/react/src/index.ts", 28 | "external": ["react", "react-dom", "react/jsx-runtime"], 29 | "rollupConfig": "@nx/react/plugins/bundle-rollup", 30 | "compiler": "babel", 31 | "assets": [ 32 | { 33 | "glob": "packages/react/README.md", 34 | "input": ".", 35 | "output": "." 36 | } 37 | ], 38 | "updateBuildableProjectDepsInPackageJson": true, 39 | "buildableProjectDepsInPackageJsonType": "dependencies" 40 | } 41 | }, 42 | "test": { 43 | "executor": "@nx/jest:jest", 44 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 45 | "options": { 46 | "jestConfig": "packages/react/jest.config.ts" 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/dom/src/lib/config/payload.ts: -------------------------------------------------------------------------------- 1 | import { Element, Text } from 'domhandler' 2 | import { config as defaultConfig } from './default' 3 | import { SlateToDomConfig } from '../..' 4 | import { styleToString } from '../utilities'; 5 | 6 | /** 7 | * Configuration for Payload CMS 8 | * 9 | * Tested for v1.1.21 10 | */ 11 | 12 | export const config: SlateToDomConfig = { 13 | ...defaultConfig, 14 | elementAttributeTransform: ({ node }) => { 15 | if (node.align || node.textAlign) { 16 | return { 17 | style: styleToString({ 18 | ['text-align']: node.align || node.textAlign, 19 | }) 20 | } 21 | } 22 | return 23 | }, 24 | elementTransforms: { 25 | ...defaultConfig.elementTransforms, 26 | link: ({ node, children = [] }) => { 27 | const attrs: {[key: string]: string} = {} 28 | if (node.linkType) { 29 | attrs['data-link-type'] = node.linkType 30 | } 31 | if (node.newTab) { 32 | attrs['target'] = '_blank' 33 | } 34 | return new Element( 35 | 'a', 36 | { 37 | href: node.url, 38 | ...attrs, 39 | }, 40 | children, 41 | ) 42 | }, 43 | upload: ({ node }) => { 44 | if (node.value?.mimeType && node.value?.url) { 45 | if (node.value?.mimeType.match(/^image/)) { 46 | return new Element('img', { 47 | src: node.value?.url, 48 | }) 49 | } 50 | return new Element( 51 | 'a', 52 | { 53 | href: node.value?.url, 54 | }, 55 | [new Text(node.value?.filename)], 56 | ) 57 | } 58 | return 59 | }, 60 | }, 61 | defaultTag: 'p', 62 | } 63 | -------------------------------------------------------------------------------- /packages/template/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "template", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/template/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/template", 13 | "main": "packages/template/src/index.ts", 14 | "tsConfig": "packages/template/tsconfig.lib.json", 15 | "assets": ["packages/template/*.md"], 16 | "updateBuildableProjectDepsInPackageJson": true, 17 | "buildableProjectDepsInPackageJsonType": "dependencies" 18 | } 19 | }, 20 | "publish": { 21 | "command": "node tools/scripts/publish.mjs template {args.ver} {args.tag}", 22 | "dependsOn": ["build"] 23 | }, 24 | "lint": { 25 | "executor": "@nx/linter:eslint", 26 | "outputs": ["{options.outputFile}"], 27 | "options": { 28 | "lintFilePatterns": ["packages/template/**/*.ts"] 29 | } 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/template/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | }, 45 | "deploy": { 46 | "executor": "ngx-deploy-npm:deploy", 47 | "options": { 48 | "distFolderPath": "dist/packages/template", 49 | "access": "public" 50 | }, 51 | "dependsOn": ["build"] 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/utilities/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utilities", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/utilities/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/utilities", 13 | "main": "packages/utilities/src/index.ts", 14 | "tsConfig": "packages/utilities/tsconfig.lib.json", 15 | "assets": ["packages/utilities/*.md"], 16 | "updateBuildableProjectDepsInPackageJson": true, 17 | "buildableProjectDepsInPackageJsonType": "dependencies" 18 | } 19 | }, 20 | "lint": { 21 | "executor": "@nx/linter:eslint", 22 | "outputs": ["{options.outputFile}"], 23 | "options": { 24 | "lintFilePatterns": ["packages/utilities/**/*.ts"] 25 | } 26 | }, 27 | "test": { 28 | "executor": "@nx/jest:jest", 29 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 30 | "options": { 31 | "jestConfig": "packages/utilities/jest.config.ts", 32 | "passWithNoTests": true 33 | }, 34 | "configurations": { 35 | "ci": { 36 | "ci": true, 37 | "codeCoverage": true 38 | } 39 | } 40 | }, 41 | "deploy": { 42 | "executor": "ngx-deploy-npm:deploy", 43 | "options": { 44 | "distFolderPath": "dist/packages/utilities", 45 | "access": "public" 46 | }, 47 | "dependsOn": ["build"] 48 | }, 49 | "version": { 50 | "executor": "@jscutlery/semver:version", 51 | "options": { 52 | "preset": "conventional" 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/dom/src/lib/config/default.ts: -------------------------------------------------------------------------------- 1 | import { Element } from 'domhandler' 2 | import { Config } from './types' 3 | import { styleToString } from '../utilities' 4 | 5 | // Map Slate element names to HTML tag names 6 | // Staightforward transform - no attributes are considered 7 | // Use transforms instead for more complex operations 8 | const ELEMENT_NAME_TAG_MAP = { 9 | p: 'p', 10 | paragraph: 'p', 11 | h1: 'h1', 12 | h2: 'h2', 13 | h3: 'h3', 14 | h4: 'h4', 15 | h5: 'h5', 16 | h6: 'h6', 17 | ul: 'ul', 18 | ol: 'ol', 19 | li: 'li', 20 | blockquote: 'blockquote', 21 | } 22 | 23 | const MARK_ELEMENT_TAG_MAP = { 24 | strikethrough: ['s'], 25 | bold: ['strong'], 26 | underline: ['u'], 27 | italic: ['i'], 28 | code: ['pre', 'code'], 29 | } 30 | 31 | export const config: Config = { 32 | markMap: MARK_ELEMENT_TAG_MAP, 33 | elementMap: ELEMENT_NAME_TAG_MAP, 34 | elementAttributeTransform: ({ node }) => { 35 | if (node.align) { 36 | return { 37 | style: styleToString({ 38 | ['text-align']: node.align, 39 | }) 40 | } 41 | } 42 | return 43 | }, 44 | elementTransforms: { 45 | quote: ({ children = [] }) => { 46 | const p = [new Element('p', {}, children)] 47 | return new Element('blockquote', {}, p) 48 | }, 49 | link: ({ node, children = [] }) => { 50 | const attrs: any = {} 51 | if (node.newTab) { 52 | attrs.target = '_blank' 53 | } 54 | return new Element( 55 | 'a', 56 | { 57 | href: node.url, 58 | ...attrs, 59 | }, 60 | children, 61 | ) 62 | }, 63 | }, 64 | encodeEntities: false, 65 | alwaysEncodeBreakingEntities: true, 66 | alwaysEncodeCodeEntities: false, 67 | convertLineBreakToBr: false, 68 | } 69 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "tasksRunnerOptions": { 4 | "default": { 5 | "runner": "nx-cloud", 6 | "options": { 7 | "cacheableOperations": ["build", "lint", "test", "e2e"], 8 | "accessToken": "MzFjMDBlNWQtZDQ0YS00NzljLTlhNzEtZDA5NTI4NGUzNjcwfHJlYWQtd3JpdGU=" 9 | } 10 | } 11 | }, 12 | "targetDefaults": { 13 | "build": { 14 | "dependsOn": ["^build"], 15 | "inputs": ["production", "^production"] 16 | }, 17 | "lint": { 18 | "inputs": [ 19 | "default", 20 | "{workspaceRoot}/.eslintrc.json", 21 | "{workspaceRoot}/.eslintignore" 22 | ] 23 | }, 24 | "test": { 25 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"] 26 | }, 27 | "@nx/eslint:lint": { 28 | "inputs": [ 29 | "default", 30 | "{workspaceRoot}/.eslintrc.json", 31 | "{workspaceRoot}/.eslintignore" 32 | ], 33 | "cache": true 34 | } 35 | }, 36 | "namedInputs": { 37 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 38 | "production": [ 39 | "default", 40 | "!{projectRoot}/.eslintrc.json", 41 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 42 | "!{projectRoot}/tsconfig.spec.json", 43 | "!{projectRoot}/jest.config.[jt]s", 44 | "!{projectRoot}/src/test-setup.[jt]s" 45 | ], 46 | "sharedGlobals": [] 47 | }, 48 | "workspaceLayout": { 49 | "appsDir": "packages", 50 | "libsDir": "packages" 51 | }, 52 | "generators": { 53 | "@nx/react": { 54 | "application": { 55 | "babel": true 56 | }, 57 | "library": { 58 | "unitTestRunner": "jest" 59 | } 60 | } 61 | }, 62 | "pluginsConfig": { 63 | "@nx/js": { 64 | "analyzeSourceFiles": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/dom/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dom", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/dom/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/dom", 13 | "main": "packages/dom/src/index.ts", 14 | "tsConfig": "packages/dom/tsconfig.lib.json", 15 | "assets": ["packages/dom/*.md"], 16 | "updateBuildableProjectDepsInPackageJson": true, 17 | "buildableProjectDepsInPackageJsonType": "dependencies" 18 | } 19 | }, 20 | "publish": { 21 | "command": "node tools/scripts/publish.mjs dom {args.ver} {args.tag}", 22 | "dependsOn": ["build"] 23 | }, 24 | "lint": { 25 | "executor": "@nx/linter:eslint", 26 | "outputs": ["{options.outputFile}"], 27 | "options": { 28 | "lintFilePatterns": ["packages/dom/**/*.ts"] 29 | } 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/dom/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | }, 45 | "deploy": { 46 | "executor": "ngx-deploy-npm:deploy", 47 | "options": { 48 | "distFolderPath": "dist/packages/dom", 49 | "access": "public" 50 | }, 51 | "dependsOn": ["build"] 52 | }, 53 | "version": { 54 | "executor": "@jscutlery/semver:version", 55 | "options": { 56 | "preset": "conventional" 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/html/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/html/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/html", 13 | "main": "packages/html/src/index.ts", 14 | "tsConfig": "packages/html/tsconfig.lib.json", 15 | "assets": ["packages/html/*.md"], 16 | "updateBuildableProjectDepsInPackageJson": true, 17 | "buildableProjectDepsInPackageJsonType": "dependencies" 18 | } 19 | }, 20 | "publish": { 21 | "command": "node tools/scripts/publish.mjs html {args.ver} {args.tag}", 22 | "dependsOn": ["build"] 23 | }, 24 | "lint": { 25 | "executor": "@nx/linter:eslint", 26 | "outputs": ["{options.outputFile}"], 27 | "options": { 28 | "lintFilePatterns": ["packages/html/**/*.ts"] 29 | } 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/html/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | }, 45 | "deploy": { 46 | "executor": "ngx-deploy-npm:deploy", 47 | "options": { 48 | "distFolderPath": "dist/packages/html", 49 | "access": "public" 50 | }, 51 | "dependsOn": ["build"] 52 | }, 53 | "version": { 54 | "executor": "@jscutlery/semver:version", 55 | "options": { 56 | "preset": "conventional" 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/template/README.md: -------------------------------------------------------------------------------- 1 | # @slate-serializers/template 2 | 3 | Render Slate JSON as a combination of HTML and custom serializers that you pass to the configuration. 4 | 5 | Returns an array. By default, each array element is the output of `@slate-serializers/html`. If you pass a custom serializers, your serialized output will be included in this array. These serializers match on top-level Slate nodes only using the `type` attribute. 6 | 7 | ## Usage 8 | 9 | ### slateToTemplate 10 | 11 | ```ts 12 | import { slateToTemplate } from '@slate-serializers/template' 13 | 14 | const slate = [ 15 | { 16 | children: [ 17 | { 18 | text: 'Heading 1', 19 | }, 20 | ], 21 | type: 'h1', 22 | }, 23 | { 24 | children: [ 25 | { 26 | text: 'Paragraph 1', 27 | }, 28 | ], 29 | type: 'p', 30 | }, 31 | ] 32 | 33 | const serializedToArray = slateToTemplate(slate) 34 | // output 35 | // ["

Heading 1

", "

Paragraph 1

"] 36 | ``` 37 | 38 | ### Configuration 39 | 40 | Define a custom serializer to include any output for top-level Slate nodes of a given type. 41 | 42 | ```ts 43 | const slate = [ 44 | { 45 | children: [ 46 | { 47 | text: 'Paragraph', 48 | }, 49 | ], 50 | type: 'p', 51 | }, 52 | { 53 | children: [ 54 | { 55 | buttonType: 'primary', 56 | text: 'Button', 57 | }, 58 | ], 59 | type: 'button', 60 | }, 61 | ]; 62 | 63 | const config: SlateToTemplateConfig = { 64 | ...defaultTemplateConfig, 65 | customElementSerializers: { 66 | button: ({ node }) => { 67 | return () => 68 | ``; 69 | }, 70 | }, 71 | }; 72 | 73 | const serializedToArray = slateToTemplate(slate) 74 | // output 75 | // ["

Paragraph

", [Function]] 76 | ``` 77 | -------------------------------------------------------------------------------- /packages/html/src/lib/tests/htmlToSlate/configuration/textTagsVselementTags.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | htmlToSlate, 3 | HtmlToSlateConfig, 4 | htmlToSlateConfig, 5 | } from '@slate-serializers/html'; 6 | import { getAttributeValue } from 'domutils'; 7 | 8 | describe('htmlToSlate configuration: textTags & elementTags', () => { 9 | it('converts element/text tags and their attributes to Slate nodes/node attributes using a custom configuration', () => { 10 | const html = 11 | '

Published:

'; 12 | const config: HtmlToSlateConfig = { 13 | ...htmlToSlateConfig, 14 | textTags: { 15 | ...htmlToSlateConfig.textTags, 16 | time: (el) => ({ 17 | ...(el && { 18 | datetime: getAttributeValue(el, 'datetime'), 19 | }), 20 | time: true, 21 | }), 22 | }, 23 | elementTags: { 24 | ...htmlToSlateConfig.elementTags, 25 | article: (el) => ({ 26 | ...(el && { 27 | id: getAttributeValue(el, 'id'), 28 | }), 29 | type: 'article', 30 | }), 31 | }, 32 | }; 33 | expect(htmlToSlate(html, config)).toMatchInlineSnapshot(` 34 | [ 35 | { 36 | "children": [ 37 | { 38 | "children": [ 39 | { 40 | "text": "Published: ", 41 | }, 42 | { 43 | "bold": true, 44 | "datetime": "2016-01-20", 45 | "text": "20 January 2016", 46 | "time": true, 47 | }, 48 | ], 49 | "type": "p", 50 | }, 51 | ], 52 | "id": "main", 53 | "type": "article", 54 | }, 55 | ] 56 | `); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/htmlToSlate/config/default.ts: -------------------------------------------------------------------------------- 1 | import { getAttributeValue } from 'domutils' 2 | import { Config } from './types' 3 | import { extractCssFromStyle } from '@slate-serializers/dom' 4 | 5 | export const config: Config = { 6 | elementAttributeTransform: ({ el }) => { 7 | const attrs: { [key: string]: string } = {} 8 | const elementStyleMap: { [key: string]: string } = { 9 | align: 'textAlign', 10 | } 11 | Object.keys(elementStyleMap).forEach((slateKey) => { 12 | const cssProperty = elementStyleMap[slateKey] 13 | const cssValue = extractCssFromStyle(el, cssProperty) 14 | if (cssValue) { 15 | attrs[slateKey] = cssValue 16 | } 17 | }) 18 | return attrs 19 | }, 20 | elementTags: { 21 | a: (el) => ({ 22 | type: 'link', 23 | newTab: el && getAttributeValue(el, 'target') === '_blank', 24 | url: el && getAttributeValue(el, 'href'), 25 | }), 26 | blockquote: () => ({ type: 'blockquote' }), 27 | h1: () => ({ type: 'h1' }), 28 | h2: () => ({ type: 'h2' }), 29 | h3: () => ({ type: 'h3' }), 30 | h4: () => ({ type: 'h4' }), 31 | h5: () => ({ type: 'h5' }), 32 | h6: () => ({ type: 'h6' }), 33 | li: () => ({ type: 'li' }), 34 | ol: () => ({ type: 'ol' }), 35 | p: () => ({ type: 'p' }), 36 | ul: () => ({ type: 'ul' }), 37 | }, 38 | textTags: { 39 | code: () => ({ code: true }), 40 | pre: () => ({ code: true }), 41 | del: () => ({ strikethrough: true }), 42 | em: () => ({ italic: true }), 43 | i: () => ({ italic: true }), 44 | s: () => ({ strikethrough: true }), 45 | strong: () => ({ bold: true }), 46 | u: () => ({ underline: true }), 47 | }, 48 | htmlPreProcessString: (html) => html.replace(/]*>/g, '').replace(/<\/pre>/g, ''), 49 | filterWhitespaceNodes: true, 50 | convertBrToLineBreak: true, 51 | trimWhiteSpace: true, 52 | } 53 | -------------------------------------------------------------------------------- /packages/tests/src/lib/html/withStylesInLeaf.spec.ts: -------------------------------------------------------------------------------- 1 | import { slateToHtml, htmlToSlateConfig, HtmlToSlateConfig, htmlToSlate } from '@slate-serializers/html' 2 | import { Element } from 'domhandler' 3 | import { SlateToDomConfig, slateToDomConfig } from '@slate-serializers/dom' 4 | import { transformStyleStringToObject } from '@slate-serializers/utilities' 5 | import { stylesMixedInFixtures as fixtures } from '../tests' 6 | 7 | export const slateToDomConfigStyleObject: SlateToDomConfig = { 8 | ...slateToDomConfig, 9 | markTransforms: { 10 | ...slateToDomConfig.markTransforms, 11 | backgroundColor: ({ node }) => { 12 | return new Element('span', { 13 | style: `background-color:${node.backgroundColor};` 14 | }) 15 | }, 16 | color: ({ node }) => { 17 | return new Element('span', { 18 | style: `color:${node.color};` 19 | }) 20 | }, 21 | fontFamily: ({ node }) => { 22 | return new Element('span', { 23 | style: `font-family:${node.fontFamily};` 24 | }) 25 | }, 26 | fontSize: ({ node }) => { 27 | return new Element('span', { 28 | style: `font-size:${node.fontSize};` 29 | }) 30 | } 31 | }, 32 | defaultTag: 'p', 33 | } 34 | 35 | export const htmlToSlateConfigStyleObject: HtmlToSlateConfig = { 36 | ...htmlToSlateConfig, 37 | textTags: { 38 | ...htmlToSlateConfig.textTags, 39 | span: (el) => { 40 | const style = el?.attribs && transformStyleStringToObject(el.attribs['style'] || ``) 41 | return { 42 | ...(style && style) 43 | } 44 | } 45 | }, 46 | } 47 | 48 | describe('styles as attributes on a leaf', () => { 49 | for (const fixture of fixtures) { 50 | it(`${fixture.name}`, () => { 51 | expect(slateToHtml(fixture.slate, slateToDomConfigStyleObject )).toEqual(fixture.html) 52 | expect(htmlToSlate(fixture.html, htmlToSlateConfigStyleObject)).toEqual(fixture.slate) 53 | }) 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /packages/tests/src/lib/html/expectedDifferencesHtmlSlateBothWays.spec.ts: -------------------------------------------------------------------------------- 1 | import { htmlToSlate, slateToHtml, payloadHtmlToSlateConfig } from '@slate-serializers/html' 2 | 3 | import { combinedFixtures } from './../tests' 4 | 5 | import { slateToDomConfig, payloadSlateToDomConfig } from '@slate-serializers/dom' 6 | 7 | describe('HTML to Slate JSON transforms', () => { 8 | describe('Combined', () => { 9 | const fixtures = combinedFixtures 10 | for (const fixture of fixtures) { 11 | it(`${fixture.name}`, () => { 12 | expect(slateToHtml(fixture.slateOriginal, { ...slateToDomConfig, defaultTag: 'p' })).toEqual(fixture.html) 13 | expect(htmlToSlate(fixture.html)).toEqual(fixture.slateReserialized) 14 | expect(slateToHtml(fixture.slateReserialized)).toEqual(fixture.html) 15 | }) 16 | } 17 | }) 18 | }) 19 | 20 | describe('attribute mapping', () => { 21 | const slate = [ 22 | { 23 | children: [ 24 | { 25 | text: 'Some text before an inline link ', 26 | }, 27 | { 28 | type: 'link', 29 | linkType: 'custom', 30 | url: 'https://github.com/thompsonsj/slate-serializers', 31 | newTab: true, 32 | children: [ 33 | { 34 | text: 'slate-serializers | GitHub', 35 | }, 36 | ], 37 | }, 38 | { 39 | text: '.', 40 | }, 41 | ], 42 | type: 'p', 43 | }, 44 | ] 45 | const html = 46 | '

Some text before an inline link slate-serializers | GitHub.

' 47 | 48 | it('slateToHtml adds a custom data attribute', () => { 49 | expect(slateToHtml(slate, payloadSlateToDomConfig)).toEqual(html) 50 | }) 51 | 52 | it('htmlToSlate adds a custom data attribute', () => { 53 | expect(htmlToSlate(html, payloadHtmlToSlateConfig)).toEqual(slate) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/slate-serializers/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slate-serializers", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/slate-serializers/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/slate-serializers", 13 | "main": "packages/slate-serializers/src/index.ts", 14 | "tsConfig": "packages/slate-serializers/tsconfig.lib.json", 15 | "assets": ["packages/slate-serializers/*.md"], 16 | "updateBuildableProjectDepsInPackageJson": true, 17 | "buildableProjectDepsInPackageJsonType": "dependencies" 18 | } 19 | }, 20 | "publish": { 21 | "command": "node tools/scripts/publish.mjs slate-serializers {args.ver} {args.tag}", 22 | "dependsOn": ["build"] 23 | }, 24 | "lint": { 25 | "executor": "@nx/linter:eslint", 26 | "outputs": ["{options.outputFile}"], 27 | "options": { 28 | "lintFilePatterns": ["packages/slate-serializers/**/*.ts"] 29 | } 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/slate-serializers/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | }, 45 | "deploy": { 46 | "executor": "ngx-deploy-npm:deploy", 47 | "options": { 48 | "distFolderPath": "dist/packages/slate-serializers", 49 | "access": "public" 50 | }, 51 | "dependsOn": ["build"] 52 | }, 53 | "version": { 54 | "executor": "@jscutlery/semver:version", 55 | "options": { 56 | "preset": "conventional" 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/htmlToSlate/whitespace.ts: -------------------------------------------------------------------------------- 1 | export type Context = 'preserve' | 'block' | 'inline' | '' 2 | 3 | import { isBlock } from "./../../utilities/blocks" 4 | 5 | interface IprocessTextValue { 6 | text: string 7 | context?: Context 8 | isInlineStart?: boolean 9 | isInlineEnd?: boolean 10 | isNextSiblingBlock?: boolean 11 | shouldTrimWhiteSpace?: boolean 12 | } 13 | 14 | export const processTextValue = ({ 15 | text, 16 | context = '', 17 | isInlineStart = false, 18 | isInlineEnd = false, 19 | isNextSiblingBlock = false, 20 | shouldTrimWhiteSpace = true 21 | }: IprocessTextValue): string => { 22 | let parsed = text 23 | if (context === 'preserve') { 24 | return parsed 25 | } 26 | parsed = minifyText(parsed, shouldTrimWhiteSpace) 27 | if (context === 'block') { 28 | // is this the start of inline content after a block element? 29 | if (isInlineStart) { 30 | parsed = parsed.trimStart() 31 | } 32 | // is this the end of inline content in a block element? 33 | if (isInlineEnd || isNextSiblingBlock) { 34 | parsed = parsed.trimEnd() 35 | } 36 | } 37 | return parsed 38 | } 39 | 40 | export const minifyText = (str: string, shouldTrimWhiteSpace: boolean) => { 41 | return shouldTrimWhiteSpace ? reduceToSingleSpaces(replaceNewlines(str)) : replaceNewlines(str) 42 | } 43 | 44 | const replaceNewlines = (str: string) => { 45 | return str.replace(/(?:\r\n|\r|\n)/g, ' ') 46 | } 47 | 48 | const reduceToSingleSpaces = (str: string) => { 49 | return str.replace(/ +(?= )/g, '') 50 | } 51 | 52 | export const isAllWhitespace = (str: string) => { 53 | return !/[^\t\n\r ]/.test(str) 54 | } 55 | 56 | export const getContext = (tagName: string): Context => { 57 | if (!tagName || tagName.trim() === '') { 58 | return '' 59 | } 60 | if (preserveWhitespace(tagName)) { 61 | return 'preserve' 62 | } 63 | if (isBlock(tagName)) { 64 | return 'block' 65 | } 66 | return 'inline' 67 | } 68 | 69 | const preserveWhitespace = (tagName: string) => { 70 | return ['code', 'pre', 'xmp'].includes(tagName) 71 | } 72 | -------------------------------------------------------------------------------- /packages/tests/src/lib/react/components.spec.tsx: -------------------------------------------------------------------------------- 1 | import Button from './testComponents/Button' 2 | import { render, fireEvent, cleanup } from '@testing-library/react' 3 | import { SlateToReact, slateToReactConfig as defaultReactConfig, SlateToReactConfig } from '@slate-serializers/react' 4 | 5 | afterEach(cleanup) 6 | 7 | const defaultProps = { 8 | onClick: jest.fn(), 9 | children: <>Submit, 10 | } 11 | 12 | describe('simple button tests', () => { 13 | test('button renders with correct text', () => { 14 | const { queryByText, rerender } = render( 58 | }, 59 | }, 60 | } 61 | 62 | const { getByText } = render() 63 | fireEvent.click(getByText('Submit')) 64 | expect(onClick).toHaveBeenCalled() 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /packages/tests/src/lib/html/withStyleObject.spec.ts: -------------------------------------------------------------------------------- 1 | import { htmlToSlate, slateToHtml, htmlToSlateConfig } from '@slate-serializers/html' 2 | import { ChildNode, Element } from 'domhandler' 3 | import { slateToDomConfig } from '@slate-serializers/dom' 4 | import { transformStyleObjectToString, transformStyleStringToObject } from '@slate-serializers/utilities' 5 | import { styleObjectFixtures as fixtures } from '../tests' 6 | 7 | export const slateToDomConfigStyleObject = { 8 | ...slateToDomConfig, 9 | elementTransforms: { 10 | ...slateToDomConfig.elementTransforms, 11 | p: ({ node, children }: { node?: any; children?: ChildNode[] }) => { 12 | const style = transformStyleObjectToString(node.style) 13 | return new Element( 14 | 'p', 15 | { 16 | ...(style && { style }), 17 | }, 18 | children, 19 | ) 20 | }, 21 | }, 22 | markTransforms: { 23 | ...slateToDomConfig.markTransforms, 24 | style: ({ node }: { node?: any }) => { 25 | return new Element('span', { 26 | style: transformStyleObjectToString(node.style), 27 | }) 28 | }, 29 | }, 30 | } 31 | 32 | export const htmlToSlateConfigStyleObject = { 33 | ...htmlToSlateConfig, 34 | elementTags: { 35 | ...htmlToSlateConfig.elementTags, 36 | p: (el: any) => { 37 | const style = el.attribs.style && transformStyleStringToObject(el.attribs.style) 38 | return { 39 | type: 'p', 40 | ...(style && { style }) 41 | } 42 | } 43 | }, 44 | textTags: { 45 | ...htmlToSlateConfig.textTags, 46 | span: (el: any) => { 47 | const style = el.attribs.style && transformStyleStringToObject(el.attribs.style) 48 | return (style && { style }) || {} 49 | } 50 | }, 51 | } 52 | 53 | describe('style attribute css transforms with postcss', () => { 54 | for (const fixture of fixtures) { 55 | it(`${fixture.name}`, () => { 56 | expect(slateToHtml(fixture.slate, slateToDomConfigStyleObject )).toEqual(fixture.html) 57 | expect(htmlToSlate(fixture.html, htmlToSlateConfigStyleObject)).toEqual(fixture.slate) 58 | }) 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /tools/scripts/publish.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a minimal script to publish your package to "npm". 3 | * This is meant to be used as-is or customize as you see fit. 4 | * 5 | * This script is executed on "dist/path/to/library" as "cwd" by default. 6 | * 7 | * You might need to authenticate with NPM before running this script. 8 | */ 9 | 10 | import { execSync } from 'child_process'; 11 | import { readFileSync, writeFileSync } from 'fs'; 12 | 13 | import devkit from '@nx/devkit'; 14 | const { readCachedProjectGraph } = devkit; 15 | 16 | function invariant(condition, message) { 17 | if (!condition) { 18 | console.error(message); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | // Executing publish script: node path/to/publish.mjs {name} --version {version} --tag {tag} 24 | // Default "tag" to "next" so we won't publish the "latest" tag by accident. 25 | const [, , name, version, tag = 'next'] = process.argv; 26 | 27 | // A simple SemVer validation to validate the version 28 | const validVersion = /^\d+\.\d+\.\d+(-\w+\.\d+)?/; 29 | invariant( 30 | version && validVersion.test(version), 31 | `No version provided or version did not match Semantic Versioning, expected: #.#.#-tag.# or #.#.#, got ${version}.` 32 | ); 33 | 34 | const graph = readCachedProjectGraph(); 35 | const project = graph.nodes[name]; 36 | 37 | invariant( 38 | project, 39 | `Could not find project "${name}" in the workspace. Is the project.json configured correctly?` 40 | ); 41 | 42 | const outputPath = project.data?.targets?.build?.options?.outputPath; 43 | invariant( 44 | outputPath, 45 | `Could not find "build.options.outputPath" of project "${name}". Is project.json configured correctly?` 46 | ); 47 | 48 | process.chdir(outputPath); 49 | 50 | // Updating the version in "package.json" before publishing 51 | try { 52 | const json = JSON.parse(readFileSync(`package.json`).toString()); 53 | json.version = version; 54 | writeFileSync(`package.json`, JSON.stringify(json, null, 2)); 55 | } catch (e) { 56 | console.error(`Error reading package.json file from library build output.`); 57 | } 58 | 59 | // Execute "npm publish" to publish 60 | execSync(`npm publish --access public --tag ${tag}`); 61 | -------------------------------------------------------------------------------- /docs/nx.md: -------------------------------------------------------------------------------- 1 | # NX generated documentation 2 | 3 | 4 | 5 | ✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨ 6 | 7 | ## Generate code 8 | 9 | If you happen to use Nx plugins, you can leverage code generators that might come with it. 10 | 11 | Run `nx list` to get a list of available plugins and whether they have generators. Then run `nx list ` to see what generators are available. 12 | 13 | Learn more about [Nx generators on the docs](https://nx.dev/plugin-features/use-code-generators). 14 | 15 | ## Running tasks 16 | 17 | To execute tasks with Nx use the following syntax: 18 | 19 | ``` 20 | nx <...options> 21 | ``` 22 | 23 | You can also run multiple targets: 24 | 25 | ``` 26 | nx run-many -t 27 | ``` 28 | 29 | ..or add `-p` to filter specific projects 30 | 31 | ``` 32 | nx run-many -t -p 33 | ``` 34 | 35 | Targets can be defined in the `package.json` or `projects.json`. Learn more [in the docs](https://nx.dev/core-features/run-tasks). 36 | 37 | ## Want better Editor Integration? 38 | 39 | Have a look at the [Nx Console extensions](https://nx.dev/nx-console). It provides autocomplete support, a UI for exploring and running tasks & generators, and more! Available for VSCode, IntelliJ and comes with a LSP for Vim users. 40 | 41 | ## Ready to deploy? 42 | 43 | Just run `nx build demoapp` to build the application. The build artifacts will be stored in the `dist/` directory, ready to be deployed. 44 | 45 | ## Set up CI! 46 | 47 | Nx comes with local caching already built-in (check your `nx.json`). On CI you might want to go a step further. 48 | 49 | - [Set up remote caching](https://nx.dev/core-features/share-your-cache) 50 | - [Set up task distribution across multiple machines](https://nx.dev/core-features/distribute-task-execution) 51 | - [Learn more how to setup CI](https://nx.dev/recipes/ci) 52 | 53 | ## Connect with us! 54 | 55 | - [Join the community](https://nx.dev/community) 56 | - [Subscribe to the Nx Youtube Channel](https://www.youtube.com/@nxdevtools) 57 | - [Follow us on Twitter](https://twitter.com/nxdevtools) 58 | -------------------------------------------------------------------------------- /packages/html/src/lib/utilities/blocks.ts: -------------------------------------------------------------------------------- 1 | // See: 2 | // From: https://www.npmjs.com/package/rehype-minify-whitespace 3 | const blocks = [ 4 | 'address', // Flow content. 5 | 'article', // Sections and headings. 6 | 'aside', // Sections and headings. 7 | 'blockquote', // Flow content. 8 | 'body', // Page. 9 | 'br', // Contribute whitespace intrinsically. 10 | 'caption', // Similar to block. 11 | 'center', // Flow content, legacy. 12 | 'col', // Similar to block. 13 | 'colgroup', // Similar to block. 14 | 'dd', // Lists. 15 | 'details', // Similar to block. 16 | 'dialog', // Flow content. 17 | 'dir', // Lists, legacy. 18 | 'div', // Flow content. 19 | 'dl', // Lists. 20 | 'dt', // Lists. 21 | 'figcaption', // Flow content. 22 | 'figure', // Flow content. 23 | 'footer', // Flow content. 24 | 'form', // Flow content. 25 | 'h1', // Sections and headings. 26 | 'h2', // Sections and headings. 27 | 'h3', // Sections and headings. 28 | 'h4', // Sections and headings. 29 | 'h5', // Sections and headings. 30 | 'h6', // Sections and headings. 31 | 'head', // Page. 32 | 'header', // Flow content. 33 | 'hgroup', // Sections and headings. 34 | 'hr', // Flow content. 35 | 'html', // Page. 36 | 'legend', // Flow content. 37 | 'li', // Block-like. 38 | 'li', // Similar to block. 39 | 'listing', // Flow content, legacy 40 | 'main', // Flow content. 41 | 'menu', // Lists. 42 | 'nav', // Sections and headings. 43 | 'ol', // Lists. 44 | 'optgroup', // Similar to block. 45 | 'option', // Similar to block. 46 | 'p', // Flow content. 47 | 'plaintext', // Flow content, legacy 48 | 'pre', // Flow content. 49 | 'section', // Sections and headings. 50 | 'summary', // Similar to block. 51 | 'table', // Similar to block. 52 | 'tbody', // Similar to block. 53 | 'td', // Block-like. 54 | 'td', // Similar to block. 55 | 'tfoot', // Similar to block. 56 | 'th', // Block-like. 57 | 'th', // Similar to block. 58 | 'thead', // Similar to block. 59 | 'tr', // Similar to block. 60 | 'ul', // Lists. 61 | 'wbr', // Contribute whitespace intrinsically. 62 | 'xmp', // Flow content, legacy 63 | ] 64 | 65 | export const isBlock = (tagName: string) => { 66 | return blocks.includes(tagName) 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slate-serializers 2 | 3 | Docs/Demo: [https://thompsonsj.github.io/slate-serializers-demo](https://thompsonsj.github.io/slate-serializers-demo). 4 | 5 | A collection of serializers to convert [Slate](https://www.npmjs.com/package/slate) JSON objects to various formats and vice versa. Designed to work in both Node.js and browser environments. 6 | 7 | ## Convert Slate to DOM 8 | 9 | - Convert Slate to a DOM object with [`slateToDom`](https://github.com/thompsonsj/slate-serializers/tree/main/packages/dom/README.md). 10 | - Use the suite of DOM manipulation tools for [`htmlparser2`](https://github.com/fb55/htmlparser2) before serializing to HTML. 11 | - NPM: [https://www.npmjs.com/package/@slate-serializers/dom](https://www.npmjs.com/package/@slate-serializers/dom). 12 | 13 | ## Convert Slate to HTML and vice versa 14 | 15 | - Convert Slate to HTML and vice versa with [`htmlToSlate` and `slateToHtml`](https://github.com/thompsonsj/slate-serializers/tree/main/packages/html/README.md). 16 | - NPM: [https://www.npmjs.com/package/@slate-serializers/html](https://www.npmjs.com/package/@slate-serializers/html). 17 | 18 | ## Include framework-agnostic components 19 | 20 | - Include top-level custom components with [`slateToTemplate`](https://github.com/thompsonsj/slate-serializers/tree/main/packages/template/README.md). Framework agnostic - e.g. return Astro, React or Vue components alongside regular HTML nodes. 21 | - NPM: [https://www.npmjs.com/package/@slate-serializers/template](https://www.npmjs.com/package/@slate-serializers/template) 22 | 23 | ## Convert Slate to React 24 | 25 | - Convert Slate to React with [`slateToReact`](https://github.com/thompsonsj/slate-serializers/tree/main/packages/react/README.md). 26 | - NPM: [https://www.npmjs.com/package/@slate-serializers/react](https://www.npmjs.com/package/@slate-serializers/react) 27 | 28 | ## More information 29 | 30 | - For engineering decisions, see [Engineering](https://github.com/thompsonsj/slate-serializers/blob/main/docs/engineering.md). 31 | - Review generated NX documentation at [NX](https://github.com/thompsonsj/slate-serializers/blob/main/docs/nx.md). 32 | - This repository is an NX monorepo. Review the docs at [https://nx.dev/](https://nx.dev/). 33 | - Related repository: https://github.com/thompsonsj/slate-serializers-demo. [Demo/docs site](https://thompsonsj.github.io/slate-serializers-demo). Not included in this monorepo to replicate repositories that use these libraries more effectively. 34 | -------------------------------------------------------------------------------- /packages/react/src/lib/serializers.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement, JSXElementConstructor } from 'react' 2 | import { Element, isTag, Text } from 'domhandler' 3 | import { getName, textContent } from 'domutils' 4 | import { ulid } from 'ulidx' 5 | 6 | import { convertSlate } from '@slate-serializers/dom' 7 | import { config as slateToReactConfig } from './config/default' 8 | import type { Config as SlateToReactConfig } from './config/types' 9 | 10 | interface ISlateToReact { 11 | node: any[] 12 | config?: SlateToReactConfig 13 | } 14 | 15 | export const SlateToReact = ({ node, config = slateToReactConfig }: ISlateToReact) => { 16 | if (!Array.isArray(node)) { 17 | return <> 18 | } 19 | const document = node.map((n, index) => 20 | convertSlate({ 21 | node: n, 22 | config: { 23 | ...config, 24 | elementTransforms: {}, 25 | markTransforms: {}, 26 | }, 27 | isLastNodeInDocument: index === node.length - 1, 28 | customElementTransforms: config.elementTransforms, 29 | transformText: (text) => transformText(text), 30 | transformElement: (element) => { 31 | return domElementToReactElement(element) 32 | }, 33 | wrapChildren: (children) => children, 34 | }), 35 | ) 36 | return document as any 37 | } 38 | 39 | const transformText = (text: Text | Element) => { 40 | if (isTag(text as Element)) { 41 | return React.createElement(getName(text as Element), {}, textContent(text as Element)) 42 | } 43 | return <>{textContent(text as Text)} 44 | } 45 | 46 | const domElementToReactElement = (element: Element): ReactElement> => { 47 | const style = element.attribs?.style && typeof element.attribs.style === 'object' && { style: element.attribs?.style } 48 | return React.createElement( 49 | getName(element), 50 | { 51 | /* Ensure all React elements have a unique key - is there a better way to do this? */ 52 | key: ulid(), 53 | /* Provide all attributes as props */ 54 | ...element.attribs, 55 | /* Convert key names for JSX compatibility */ 56 | ...(element.attribs?.class && { className: element.attribs?.class }), 57 | /* Validate style (can convert to React style object using elementAttributeTransform or other transform functions, but it is still possible that a string will be passed) */ 58 | style: style ? style : undefined, 59 | }, 60 | element.children as any, 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /packages/html/src/lib/serializers/snapshots/elementTags.spec.ts: -------------------------------------------------------------------------------- 1 | import { getAttributeValue } from 'domutils'; 2 | import { 3 | htmlToSlate, 4 | payloadHtmlToSlateConfig, 5 | type HtmlToSlateConfig, 6 | } from '../../html'; 7 | 8 | const fixture = 9 | '

Introduce MacBook Air 2017 MQD42

\n

\n'; 10 | 11 | describe('issue #120', () => { 12 | it('generates expected htmlToSlate output', () => { 13 | const slate = htmlToSlate(fixture, payloadHtmlToSlateConfig); 14 | expect(slate).toMatchInlineSnapshot(` 15 | [ 16 | { 17 | "align": "center", 18 | "children": [ 19 | { 20 | "text": "Introduce MacBook Air 2017 MQD42", 21 | }, 22 | ], 23 | "type": "h2", 24 | }, 25 | { 26 | "children": [ 27 | { 28 | "text": "", 29 | }, 30 | ], 31 | "type": "p", 32 | }, 33 | ] 34 | `); 35 | }); 36 | 37 | it('generates expected htmlToSlate output with an elementTransform', () => { 38 | const config: HtmlToSlateConfig = { 39 | ...payloadHtmlToSlateConfig, 40 | elementTags: { 41 | ...payloadHtmlToSlateConfig.elementTags, 42 | img: (el) => ({ 43 | type: 'image', 44 | src: el && getAttributeValue(el, 'src'), 45 | width: el && getAttributeValue(el, 'width'), 46 | height: el && getAttributeValue(el, 'height'), 47 | }), 48 | }, 49 | }; 50 | const slate = htmlToSlate(fixture, config); 51 | expect(slate).toMatchInlineSnapshot(` 52 | [ 53 | { 54 | "align": "center", 55 | "children": [ 56 | { 57 | "text": "Introduce MacBook Air 2017 MQD42", 58 | }, 59 | ], 60 | "type": "h2", 61 | }, 62 | { 63 | "children": [ 64 | { 65 | "children": [ 66 | { 67 | "text": "", 68 | }, 69 | ], 70 | "height": "577", 71 | "src": "https://example.com/media/macbook-air-2017-mqd42-1.jpg", 72 | "type": "image", 73 | "width": "900", 74 | }, 75 | ], 76 | "type": "p", 77 | }, 78 | ] 79 | `); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "migrations": [ 3 | { 4 | "version": "21.0.0-beta.8", 5 | "description": "Removes the legacy cache configuration from nx.json", 6 | "implementation": "./src/migrations/update-21-0-0/remove-legacy-cache", 7 | "package": "nx", 8 | "name": "remove-legacy-cache" 9 | }, 10 | { 11 | "version": "21.0.0-beta.8", 12 | "description": "Removes the legacy cache configuration from nx.json", 13 | "implementation": "./src/migrations/update-21-0-0/remove-custom-tasks-runner", 14 | "package": "nx", 15 | "name": "remove-custom-tasks-runner" 16 | }, 17 | { 18 | "version": "21.0.0-beta.11", 19 | "description": "Updates release version config based on the breaking changes in Nx v21", 20 | "implementation": "./src/migrations/update-21-0-0/release-version-config-changes", 21 | "package": "nx", 22 | "name": "release-version-config-changes" 23 | }, 24 | { 25 | "version": "21.0.0-beta.11", 26 | "description": "Updates release changelog config based on the breaking changes in Nx v21", 27 | "implementation": "./src/migrations/update-21-0-0/release-changelog-config-changes", 28 | "package": "nx", 29 | "name": "release-changelog-config-changes" 30 | }, 31 | { 32 | "version": "21.1.0-beta.2", 33 | "description": "Adds **/nx-rules.mdc and **/nx.instructions.md to .gitignore if not present", 34 | "implementation": "./src/migrations/update-21-1-0/add-gitignore-entry", 35 | "package": "nx", 36 | "name": "21-1-0-add-ignore-entries-for-nx-rule-files" 37 | }, 38 | { 39 | "cli": "nx", 40 | "version": "21.0.0-beta.9", 41 | "description": "Replace usage of `getJestProjects` with `getJestProjectsAsync`.", 42 | "implementation": "./src/migrations/update-21-0-0/replace-getJestProjects-with-getJestProjectsAsync", 43 | "package": "@nx/jest", 44 | "name": "replace-getJestProjects-with-getJestProjectsAsync-v21" 45 | }, 46 | { 47 | "version": "21.0.0-beta.10", 48 | "description": "Remove the previously deprecated and unused `tsConfig` option from the `@nx/jest:jest` executor.", 49 | "implementation": "./src/migrations/update-21-0-0/remove-tsconfig-option-from-jest-executor", 50 | "package": "@nx/jest", 51 | "name": "remove-tsconfig-option-from-jest-executor" 52 | }, 53 | { 54 | "cli": "nx", 55 | "version": "21.0.0-beta.11", 56 | "description": "Replaces `classProperties.loose` option with `loose`.", 57 | "factory": "./src/migrations/update-21-0-0/update-babel-loose", 58 | "package": "@nx/react", 59 | "name": "update-21-0-0-update-babel-loose" 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /docs/config/slateToDom.md: -------------------------------------------------------------------------------- 1 | # slateToDom configuration 2 | 3 | ## elementMap 4 | 5 | Describe how Slate JSON **node types** are mapped to HTML element tags. 6 | 7 | In the following simple example, Slate JSON nodes with a `type` of `heading-one` are mapped to `h1` HTML elements. 8 | 9 | ```ts 10 | import { SlateToDomConfig } from 'slate-serializers' 11 | 12 | const config: SlateToDomConfig = { 13 | // ... 14 | elementMap: { 15 | ['heading-one']: 'h1', 16 | }, 17 | // ... 18 | } 19 | ``` 20 | 21 | ## elementTransforms 22 | 23 | For more complex transformations, use `elementTransforms`. 24 | 25 | ```ts 26 | import { Element } from 'domhandler' 27 | 28 | const config: SlateToDomConfig = { 29 | // ... 30 | elementTransforms: { 31 | image: ({ node }: { node?: any }) => { 32 | return new Element('img', { 33 | src: node.url, 34 | }) 35 | }, 36 | }, 37 | // ... 38 | } 39 | ``` 40 | 41 | The Slate JS node is passed into this function. A node of type `Element` from `domhandler` must be returned. Combine this with [utilities from `domutils`](https://domutils.js.org/) to perform further manipulation. 42 | 43 | In this case, the `src` attribute is set using a `url` value on the Slate JS node. 44 | 45 | ## markMap 46 | 47 | Describe how Slate JSON **node attributes** are mapped to HTML formatting elements. 48 | 49 | In the following simple example, Slate JSON nodes with an attribute of `subScript: true` include a `sub` HTML formatting element. 50 | 51 | ```ts 52 | import { SlateToDomConfig } from 'slate-serializers' 53 | 54 | const config: SlateToDomConfig = { 55 | // ... 56 | markMap: { 57 | subScript: ['sub'], 58 | }, 59 | // ... 60 | } 61 | ``` 62 | 63 | Note that the config value is an array of strings. This allows multiple formatting elements to be mapped to a single Slate JSON node attribute. 64 | 65 | ## markTransforms 66 | 67 | For more complex transformations, use `markTransforms`. 68 | 69 | ```ts 70 | import { Element } from 'domhandler' 71 | 72 | const config: SlateToDomConfig = { 73 | // ... 74 | markTransforms: { 75 | fontSize: ({ node }: { node?: any }) => { 76 | return new Element('span', { 77 | style: `font-size:${node.fontSize};`, 78 | }) 79 | }, 80 | }, 81 | // ... 82 | } 83 | ``` 84 | 85 | **Keys should map keys on the Slate object**. This is different to `elementTransform` which uses the value of the Slate JSON `type` property only. 86 | 87 | The Slate JS node is passed into this function. A node of type `Element` from `domhandler` must be returned. Combine this with [utilities from `domutils`](https://domutils.js.org/) to perform further manipulation. 88 | 89 | In this case, the `style` attribute is set with a CSS string using a `fontSize` value on the Slate JS node. 90 | -------------------------------------------------------------------------------- /packages/react/src/lib/__tests__/default.spec.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | // eslint-disable-next-line @nx/enforce-module-boundaries 4 | import { SlateToReact } from '@slate-serializers/react'; 5 | import { config as defaultReactConfig } from './../config/default'; 6 | 7 | describe('React conversion', () => { 8 | test('convert domhandler element to React element', async () => { 9 | const slate = [ 10 | { 11 | children: [ 12 | { 13 | text: 'Paragraph', 14 | }, 15 | ], 16 | type: 'p', 17 | }, 18 | ]; 19 | 20 | const tree = render(); 21 | expect(tree.container).toMatchInlineSnapshot(` 22 |
23 |

24 | Paragraph 25 |

26 |
27 | `); 28 | }); 29 | 30 | it('can handle inline code tags', () => { 31 | const slate = [ 32 | { 33 | type: 'p', 34 | children: [ 35 | { 36 | text: 'This is editable ', 37 | }, 38 | { 39 | text: 'rich', 40 | bold: true, 41 | }, 42 | { 43 | text: ' text, ', 44 | }, 45 | { 46 | text: 'much', 47 | italic: true, 48 | }, 49 | { 50 | text: ' better than a ', 51 | }, 52 | { 53 | text: '