Skip to content

Commit

Permalink
Merge pull request #2 from 20minutes/feature/validate-html
Browse files Browse the repository at this point in the history
Add a function to allow a custom HTML validation
  • Loading branch information
j0k3r authored Mar 22, 2024
2 parents cb49d5c + 87a7902 commit 1b6fd3f
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 3 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Node.js Package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- run: yarn install
- run: yarn test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org/
- run: yarn install
- run: yarn publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@20minutes/draft-convert
===============

[![Node CI](https://github.com/20minutes/draft-convert/actions/workflows/ci.yml/badge.svg)](https://github.com/20minutes/draft-convert/actions/workflows/ci.yml)
[![npm version](https://badge.fury.io/js/@20minutes%2Fdraft-convert.svg)](https://badge.fury.io/js/@20minutes%2Fdraft-convert)
[![npm downloads](https://img.shields.io/npm/dt/@20minutes%2Fdraft-convert.svg?style=flat)](https://www.npmjs.com/package/@20minutes%2Fdraft-convert)

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "@20minutes/draft-convert",
"version": "2.1.13",
"version": "3.0.0",
"description": "Extensibly serialize & deserialize Draft.js ContentState",
"main": "lib/index.js",
"types": "types/index.d.ts",
"module": "esm/index.js",
"repository": "20minutes/draft-convert",
"scripts": {
Expand All @@ -17,6 +18,7 @@
"lint": "eslint src/ test/"
},
"files": [
"types",
"dist",
"lib",
"esm"
Expand Down
16 changes: 14 additions & 2 deletions src/convertToHTML.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import getNestedBlockTags from './util/getNestedBlockTags'
import defaultBlockHTML from './default/defaultBlockHTML'

const defaultEntityToHTML = (entity, originalText) => originalText
const defaultValidateHTML = (html) => true

const convertToHTML =
({ styleToHTML = {}, blockToHTML = {}, entityToHTML = defaultEntityToHTML }) =>
({
styleToHTML = {},
blockToHTML = {},
entityToHTML = defaultEntityToHTML,
validateHTML = defaultValidateHTML,
}) =>
(contentState) => {
invariant(
contentState !== null && contentState !== undefined,
Expand Down Expand Up @@ -99,7 +105,13 @@ const convertToHTML =
}
}

return closeNestTags + openNestTags + html
const finalHtml = closeNestTags + openNestTags + html

if (!validateHTML(finalHtml)) {
return ''
}

return finalHtml
})
.join('')

Expand Down
36 changes: 36 additions & 0 deletions test/spec/convertToHTML-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1099,4 +1099,40 @@ describe('convertToHTML', () => {

expect(() => convertToHTML(contentState)).toThrowError(/missing HTML definition/)
})

it('validate final HTML', () => {
const contentState = buildContentState(
[
{
type: 'unstyled',
text: 'test',
entityRanges: [
{
key: 0,
offset: 3,
length: 4,
},
],
},
],
{
0: {
type: 'LINK',
mutability: 'IMMUTABLE',
},
}
)

const result = convertToHTML({
entityToHTML: (entity, originalText) => {
if (entity.type === 'LINK') {
return `<a>${originalText}</a>`
}

return originalText
},
validateHTML: (html) => false,
})(contentState)
expect(result).toBe('')
})
})
114 changes: 114 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Based on: https://github.com/HubSpot/draft-convert/issues/107#issuecomment-488581709 by <https://github.com/sbusch>
// Grabbed from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/draft-convert/index.d.ts

declare module "@20minutes/draft-convert" {
import {
ContentState,
DraftBlockType,
DraftEntityMutability,
DraftInlineStyleType,
Entity,
RawDraftContentBlock,
RawDraftEntity,
} from "draft-js";
import { ReactNode } from "react";

type RawDraftContentBlockWithCustomType<T> = Omit<RawDraftContentBlock, "type"> & {
type: T;
};

type ExtendedHTMLElement<T> = (HTMLElement | HTMLLinkElement) & T;

interface IConvertToHTMLConfig<
S = DraftInlineStyleType,
B extends DraftBlockType = DraftBlockType,
E extends RawDraftEntity = RawDraftEntity,
> {
// Inline styles:
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
styleToHTML?: ((style: S) => Tag | void) | undefined;

// Block styles:
blockToHTML?: ((block: RawDraftContentBlockWithCustomType<B>) => Tag) | undefined;

// Entity styling:
entityToHTML?: ((entity: E, originalText: string) => Tag) | undefined;

// HTML validation:
validateHTML?: ((html: string) => boolean) | undefined;
}

type EntityKey = string;

interface IConvertFromHTMLConfig<
S extends {
[name: string]: unknown;
} = DOMStringMap,
B extends DraftBlockType = DraftBlockType,
E extends RawDraftEntity = RawDraftEntity,
> {
// Inline styles:
htmlToStyle?:
| ((nodeName: string, node: ExtendedHTMLElement<S>, currentStyle: Set<string>) => Set<string>)
| undefined;

// Block styles:
htmlToBlock?:
| ((
nodeName: string,
node: ExtendedHTMLElement<S>,
) => B | { type: B; data: object } | false | undefined)
| undefined;

// Html entities
htmlToEntity?:
| ((
nodeName: string,
node: ExtendedHTMLElement<S>,
createEntity: (type: E["type"], mutability: DraftEntityMutability, data: E["data"]) => EntityKey,
getEntity: (key: EntityKey) => Entity,
mergeEntityData: (key: string, data: object) => void,
replaceEntityData: (key: string, data: object) => void,
) => EntityKey | undefined)
| undefined;

// Text entities
textToEntity?:
| ((
text: string,
createEntity: (type: E["type"], mutability: DraftEntityMutability, data: E["data"]) => EntityKey,
getEntity: (key: EntityKey) => Entity,
mergeEntityData: (key: string, data: object) => void,
replaceEntityData: (key: string, data: object) => void,
) => Array<{
entity: EntityKey;
offset: number;
length: number;
result?: string | undefined;
}>)
| undefined;
}

type ContentStateConverter = (contentState: ContentState) => string;
type HTMLConverter = (html: string) => ContentState;

type Tag = ReactNode | { start: string; end: string; empty?: string | undefined } | {
element: ReactNode;
empty?: ReactNode | undefined;
};

export function convertToHTML<
S = DraftInlineStyleType,
B extends DraftBlockType = DraftBlockType,
E extends RawDraftEntity = RawDraftEntity,
>(config: IConvertToHTMLConfig<S, B, E>): ContentStateConverter;
export function convertToHTML(contentState: ContentState): string;
export function convertFromHTML<
S extends {
[name: string]: unknown;
} = DOMStringMap,
B extends DraftBlockType = DraftBlockType,
E extends RawDraftEntity = RawDraftEntity,
>(config: IConvertFromHTMLConfig<S, B, E>): HTMLConverter;
export function convertFromHTML(html: string): ContentState;
}

0 comments on commit 1b6fd3f

Please sign in to comment.