From 3609f3a704d6f06f00702f9a6b6df83b7fca523c Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Fri, 2 Sep 2022 13:18:42 +0300 Subject: [PATCH 01/35] Add config + change files to .ts --- packages/rich-text/package.json | 5 +++-- packages/rich-text/src/{context.js => context.ts} | 0 .../src/{customConstants.js => customConstants.ts} | 0 .../rich-text/src/{customExport.js => customExport.ts} | 0 .../rich-text/src/{customImport.js => customImport.ts} | 0 .../{customInlineDisplay.js => customInlineDisplay.ts} | 0 packages/rich-text/src/{draftUtils.js => draftUtils.ts} | 0 packages/rich-text/src/{editor.js => editor.tsx} | 0 .../rich-text/src/{fauxSelection.js => fauxSelection.ts} | 0 packages/rich-text/src/formatters/{color.js => color.ts} | 0 .../rich-text/src/formatters/{convert.js => convert.ts} | 0 packages/rich-text/src/formatters/{index.js => index.ts} | 0 .../rich-text/src/formatters/{italic.js => italic.ts} | 0 .../formatters/{letterSpacing.js => letterSpacing.ts} | 0 .../src/formatters/{underline.js => underline.ts} | 0 .../src/formatters/{uppercase.js => uppercase.ts} | 0 packages/rich-text/src/formatters/{util.js => util.ts} | 0 .../rich-text/src/formatters/{weight.js => weight.ts} | 0 .../src/{getFontVariants.js => getFontVariants.ts} | 0 .../src/{getPastedBlocks.js => getPastedBlocks.ts} | 0 .../rich-text/src/{getStateInfo.js => getStateInfo.ts} | 0 .../src/{htmlManipulation.js => htmlManipulation.ts} | 0 packages/rich-text/src/{index.js => index.ts} | 0 packages/rich-text/src/{provider.js => provider.tsx} | 0 .../src/{styleManipulation.js => styleManipulation.ts} | 0 packages/rich-text/src/types.ts | 0 .../{useHandlePastedText.js => useHandlePastedText.ts} | 0 .../{usePasteTextContent.js => usePasteTextContent.ts} | 0 .../rich-text/src/{useRichText.js => useRichText.ts} | 0 ...ectionManipulation.js => useSelectionManipulation.ts} | 0 packages/rich-text/src/{util.js => util.ts} | 0 ...rOffsetWithin.js => getCaretCharacterOffsetWithin.ts} | 0 .../src/utils/{getValidHTML.js => getValidHTML.ts} | 0 packages/rich-text/tsconfig.json | 9 +++++++++ 34 files changed, 12 insertions(+), 2 deletions(-) rename packages/rich-text/src/{context.js => context.ts} (100%) rename packages/rich-text/src/{customConstants.js => customConstants.ts} (100%) rename packages/rich-text/src/{customExport.js => customExport.ts} (100%) rename packages/rich-text/src/{customImport.js => customImport.ts} (100%) rename packages/rich-text/src/{customInlineDisplay.js => customInlineDisplay.ts} (100%) rename packages/rich-text/src/{draftUtils.js => draftUtils.ts} (100%) rename packages/rich-text/src/{editor.js => editor.tsx} (100%) rename packages/rich-text/src/{fauxSelection.js => fauxSelection.ts} (100%) rename packages/rich-text/src/formatters/{color.js => color.ts} (100%) rename packages/rich-text/src/formatters/{convert.js => convert.ts} (100%) rename packages/rich-text/src/formatters/{index.js => index.ts} (100%) rename packages/rich-text/src/formatters/{italic.js => italic.ts} (100%) rename packages/rich-text/src/formatters/{letterSpacing.js => letterSpacing.ts} (100%) rename packages/rich-text/src/formatters/{underline.js => underline.ts} (100%) rename packages/rich-text/src/formatters/{uppercase.js => uppercase.ts} (100%) rename packages/rich-text/src/formatters/{util.js => util.ts} (100%) rename packages/rich-text/src/formatters/{weight.js => weight.ts} (100%) rename packages/rich-text/src/{getFontVariants.js => getFontVariants.ts} (100%) rename packages/rich-text/src/{getPastedBlocks.js => getPastedBlocks.ts} (100%) rename packages/rich-text/src/{getStateInfo.js => getStateInfo.ts} (100%) rename packages/rich-text/src/{htmlManipulation.js => htmlManipulation.ts} (100%) rename packages/rich-text/src/{index.js => index.ts} (100%) rename packages/rich-text/src/{provider.js => provider.tsx} (100%) rename packages/rich-text/src/{styleManipulation.js => styleManipulation.ts} (100%) create mode 100644 packages/rich-text/src/types.ts rename packages/rich-text/src/{useHandlePastedText.js => useHandlePastedText.ts} (100%) rename packages/rich-text/src/{usePasteTextContent.js => usePasteTextContent.ts} (100%) rename packages/rich-text/src/{useRichText.js => useRichText.ts} (100%) rename packages/rich-text/src/{useSelectionManipulation.js => useSelectionManipulation.ts} (100%) rename packages/rich-text/src/{util.js => util.ts} (100%) rename packages/rich-text/src/utils/{getCaretCharacterOffsetWithin.js => getCaretCharacterOffsetWithin.ts} (100%) rename packages/rich-text/src/utils/{getValidHTML.js => getValidHTML.ts} (100%) create mode 100644 packages/rich-text/tsconfig.json diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 4b4131a89091..ff9a0a181b37 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -26,12 +26,13 @@ }, "customExports": { ".": { - "default": "./src/index.js" + "default": "./src/index.ts" } }, "main": "dist/index.js", "module": "dist-module/index.js", - "source": "src/index.js", + "types": "dist-types/index.d.ts", + "source": "src/index.ts", "publishConfig": { "access": "public" }, diff --git a/packages/rich-text/src/context.js b/packages/rich-text/src/context.ts similarity index 100% rename from packages/rich-text/src/context.js rename to packages/rich-text/src/context.ts diff --git a/packages/rich-text/src/customConstants.js b/packages/rich-text/src/customConstants.ts similarity index 100% rename from packages/rich-text/src/customConstants.js rename to packages/rich-text/src/customConstants.ts diff --git a/packages/rich-text/src/customExport.js b/packages/rich-text/src/customExport.ts similarity index 100% rename from packages/rich-text/src/customExport.js rename to packages/rich-text/src/customExport.ts diff --git a/packages/rich-text/src/customImport.js b/packages/rich-text/src/customImport.ts similarity index 100% rename from packages/rich-text/src/customImport.js rename to packages/rich-text/src/customImport.ts diff --git a/packages/rich-text/src/customInlineDisplay.js b/packages/rich-text/src/customInlineDisplay.ts similarity index 100% rename from packages/rich-text/src/customInlineDisplay.js rename to packages/rich-text/src/customInlineDisplay.ts diff --git a/packages/rich-text/src/draftUtils.js b/packages/rich-text/src/draftUtils.ts similarity index 100% rename from packages/rich-text/src/draftUtils.js rename to packages/rich-text/src/draftUtils.ts diff --git a/packages/rich-text/src/editor.js b/packages/rich-text/src/editor.tsx similarity index 100% rename from packages/rich-text/src/editor.js rename to packages/rich-text/src/editor.tsx diff --git a/packages/rich-text/src/fauxSelection.js b/packages/rich-text/src/fauxSelection.ts similarity index 100% rename from packages/rich-text/src/fauxSelection.js rename to packages/rich-text/src/fauxSelection.ts diff --git a/packages/rich-text/src/formatters/color.js b/packages/rich-text/src/formatters/color.ts similarity index 100% rename from packages/rich-text/src/formatters/color.js rename to packages/rich-text/src/formatters/color.ts diff --git a/packages/rich-text/src/formatters/convert.js b/packages/rich-text/src/formatters/convert.ts similarity index 100% rename from packages/rich-text/src/formatters/convert.js rename to packages/rich-text/src/formatters/convert.ts diff --git a/packages/rich-text/src/formatters/index.js b/packages/rich-text/src/formatters/index.ts similarity index 100% rename from packages/rich-text/src/formatters/index.js rename to packages/rich-text/src/formatters/index.ts diff --git a/packages/rich-text/src/formatters/italic.js b/packages/rich-text/src/formatters/italic.ts similarity index 100% rename from packages/rich-text/src/formatters/italic.js rename to packages/rich-text/src/formatters/italic.ts diff --git a/packages/rich-text/src/formatters/letterSpacing.js b/packages/rich-text/src/formatters/letterSpacing.ts similarity index 100% rename from packages/rich-text/src/formatters/letterSpacing.js rename to packages/rich-text/src/formatters/letterSpacing.ts diff --git a/packages/rich-text/src/formatters/underline.js b/packages/rich-text/src/formatters/underline.ts similarity index 100% rename from packages/rich-text/src/formatters/underline.js rename to packages/rich-text/src/formatters/underline.ts diff --git a/packages/rich-text/src/formatters/uppercase.js b/packages/rich-text/src/formatters/uppercase.ts similarity index 100% rename from packages/rich-text/src/formatters/uppercase.js rename to packages/rich-text/src/formatters/uppercase.ts diff --git a/packages/rich-text/src/formatters/util.js b/packages/rich-text/src/formatters/util.ts similarity index 100% rename from packages/rich-text/src/formatters/util.js rename to packages/rich-text/src/formatters/util.ts diff --git a/packages/rich-text/src/formatters/weight.js b/packages/rich-text/src/formatters/weight.ts similarity index 100% rename from packages/rich-text/src/formatters/weight.js rename to packages/rich-text/src/formatters/weight.ts diff --git a/packages/rich-text/src/getFontVariants.js b/packages/rich-text/src/getFontVariants.ts similarity index 100% rename from packages/rich-text/src/getFontVariants.js rename to packages/rich-text/src/getFontVariants.ts diff --git a/packages/rich-text/src/getPastedBlocks.js b/packages/rich-text/src/getPastedBlocks.ts similarity index 100% rename from packages/rich-text/src/getPastedBlocks.js rename to packages/rich-text/src/getPastedBlocks.ts diff --git a/packages/rich-text/src/getStateInfo.js b/packages/rich-text/src/getStateInfo.ts similarity index 100% rename from packages/rich-text/src/getStateInfo.js rename to packages/rich-text/src/getStateInfo.ts diff --git a/packages/rich-text/src/htmlManipulation.js b/packages/rich-text/src/htmlManipulation.ts similarity index 100% rename from packages/rich-text/src/htmlManipulation.js rename to packages/rich-text/src/htmlManipulation.ts diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.ts similarity index 100% rename from packages/rich-text/src/index.js rename to packages/rich-text/src/index.ts diff --git a/packages/rich-text/src/provider.js b/packages/rich-text/src/provider.tsx similarity index 100% rename from packages/rich-text/src/provider.js rename to packages/rich-text/src/provider.tsx diff --git a/packages/rich-text/src/styleManipulation.js b/packages/rich-text/src/styleManipulation.ts similarity index 100% rename from packages/rich-text/src/styleManipulation.js rename to packages/rich-text/src/styleManipulation.ts diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/rich-text/src/useHandlePastedText.js b/packages/rich-text/src/useHandlePastedText.ts similarity index 100% rename from packages/rich-text/src/useHandlePastedText.js rename to packages/rich-text/src/useHandlePastedText.ts diff --git a/packages/rich-text/src/usePasteTextContent.js b/packages/rich-text/src/usePasteTextContent.ts similarity index 100% rename from packages/rich-text/src/usePasteTextContent.js rename to packages/rich-text/src/usePasteTextContent.ts diff --git a/packages/rich-text/src/useRichText.js b/packages/rich-text/src/useRichText.ts similarity index 100% rename from packages/rich-text/src/useRichText.js rename to packages/rich-text/src/useRichText.ts diff --git a/packages/rich-text/src/useSelectionManipulation.js b/packages/rich-text/src/useSelectionManipulation.ts similarity index 100% rename from packages/rich-text/src/useSelectionManipulation.js rename to packages/rich-text/src/useSelectionManipulation.ts diff --git a/packages/rich-text/src/util.js b/packages/rich-text/src/util.ts similarity index 100% rename from packages/rich-text/src/util.js rename to packages/rich-text/src/util.ts diff --git a/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.js b/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts similarity index 100% rename from packages/rich-text/src/utils/getCaretCharacterOffsetWithin.js rename to packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts diff --git a/packages/rich-text/src/utils/getValidHTML.js b/packages/rich-text/src/utils/getValidHTML.ts similarity index 100% rename from packages/rich-text/src/utils/getValidHTML.js rename to packages/rich-text/src/utils/getValidHTML.ts diff --git a/packages/rich-text/tsconfig.json b/packages/rich-text/tsconfig.json new file mode 100644 index 000000000000..409e9ab59df8 --- /dev/null +++ b/packages/rich-text/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.shared.json", + "compilerOptions": { + "rootDir": "src", + "declarationDir": "dist-types" + }, + "references": [{ "path": "../react" }], + "include": ["src/**/*"] +} From be63c461f748c97e09573d40fa6308fbee7f0071 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Fri, 2 Sep 2022 16:43:27 +0300 Subject: [PATCH 02/35] Commit missing change --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 2c85ca01793e..cc00f3d030ea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ { "path": "packages/media" }, { "path": "packages/moveable" }, { "path": "packages/react" }, + { "path": "packages/rich-text" }, { "path": "packages/tracking" }, { "path": "packages/units" } ], From 6c8e09b52cb3fcd5bfcf3cfcb04224c040d3b397 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 5 Sep 2022 17:30:53 +0300 Subject: [PATCH 03/35] util.ts --- packages/rich-text/src/formatters/util.ts | 11 ++++++----- packages/rich-text/tsconfig.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/rich-text/src/formatters/util.ts b/packages/rich-text/src/formatters/util.ts index 90d0d0245d6a..ffe6e057d5ab 100644 --- a/packages/rich-text/src/formatters/util.ts +++ b/packages/rich-text/src/formatters/util.ts @@ -14,19 +14,20 @@ * limitations under the License. */ -export const isStyle = (style, prefix) => - typeof style === 'string' && style.startsWith(prefix); +export const isStyle = (style: string, prefix: string) => + style.startsWith(prefix); -export const getVariable = (style, prefix) => style.slice(prefix.length + 1); +export const getVariable = (style: string, prefix: string) => + style.slice(prefix.length + 1); /* * Numerics use PREFIX-123 for the number 123 * and PREFIX-N123 for the number -123. */ -export const numericToStyle = (prefix, num) => +export const numericToStyle = (prefix: string, num: number) => `${prefix}-${num < 0 ? 'N' : ''}${Math.abs(num)}`; -export const styleToNumeric = (prefix, style) => { +export const styleToNumeric = (prefix: string, style: string) => { const raw = getVariable(style, prefix); // Negative numbers are prefixed with an N: if (raw.charAt(0) === 'N') { diff --git a/packages/rich-text/tsconfig.json b/packages/rich-text/tsconfig.json index 409e9ab59df8..5c12f003848c 100644 --- a/packages/rich-text/tsconfig.json +++ b/packages/rich-text/tsconfig.json @@ -4,6 +4,6 @@ "rootDir": "src", "declarationDir": "dist-types" }, - "references": [{ "path": "../react" }], + "references": [{ "path": "../patterns" }], "include": ["src/**/*"] } From 3e726362356bc271e9518237dfed3575176bba83 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 5 Sep 2022 19:07:02 +0300 Subject: [PATCH 04/35] Formatters --- package-lock.json | 24 ++++++++++++++- packages/rich-text/package.json | 3 +- packages/rich-text/src/formatters/color.ts | 18 ++++++----- packages/rich-text/src/formatters/convert.ts | 30 ++++++++++++------- packages/rich-text/src/formatters/italic.ts | 16 +++++++--- .../rich-text/src/formatters/letterSpacing.ts | 18 +++++++---- .../rich-text/src/formatters/underline.ts | 13 +++++--- .../rich-text/src/formatters/uppercase.ts | 13 +++++--- packages/rich-text/src/formatters/weight.ts | 28 ++++++++++------- packages/rich-text/src/styleManipulation.ts | 21 ++++++++----- 10 files changed, 129 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index b27bd2f21de4..8c0d6178401d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14162,6 +14162,16 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, + "node_modules/@types/draft-js": { + "version": "0.11.9", + "resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.9.tgz", + "integrity": "sha512-cQJBZjjIlGaPA1tOY+wGz2KhlPtAAZOIXpUvGPxPRw5uzZ2tcj8m6Yu1QDV9YgP36+cqE3cUvgkARBzgUiuI/Q==", + "dev": true, + "dependencies": { + "@types/react": "*", + "immutable": "~3.7.4" + } + }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -48324,7 +48334,8 @@ "prop-types": "^15.7.2" }, "devDependencies": { - "@testing-library/react": "^12.1.5" + "@testing-library/react": "^12.1.5", + "@types/draft-js": "^0.11.9" }, "engines": { "node": ">= 12 || >= 14 || >= 16", @@ -50864,6 +50875,7 @@ "@googleforcreators/patterns": "*", "@googleforcreators/react": "*", "@testing-library/react": "^12.1.5", + "@types/draft-js": "^0.11.9", "draft-js": "^0.11.7", "draft-js-export-html": "^1.4.1", "draft-js-import-html": "^1.4.1", @@ -59529,6 +59541,16 @@ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" }, + "@types/draft-js": { + "version": "0.11.9", + "resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.9.tgz", + "integrity": "sha512-cQJBZjjIlGaPA1tOY+wGz2KhlPtAAZOIXpUvGPxPRw5uzZ2tcj8m6Yu1QDV9YgP36+cqE3cUvgkARBzgUiuI/Q==", + "dev": true, + "requires": { + "@types/react": "*", + "immutable": "~3.7.4" + } + }, "@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index ff9a0a181b37..8ab4c495b951 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -47,6 +47,7 @@ "prop-types": "^15.7.2" }, "devDependencies": { - "@testing-library/react": "^12.1.5" + "@testing-library/react": "^12.1.5", + "@types/draft-js": "^0.11.9" } } diff --git a/packages/rich-text/src/formatters/color.ts b/packages/rich-text/src/formatters/color.ts index 0c4b6f8f0ee4..6a0c0929e2e0 100644 --- a/packages/rich-text/src/formatters/color.ts +++ b/packages/rich-text/src/formatters/color.ts @@ -25,6 +25,8 @@ import { isPatternEqual, createSolidFromString, } from '@googleforcreators/patterns'; +import type { Pattern, Solid } from '@googleforcreators/patterns'; +import type { EditorState } from 'draft-js'; /** * Internal dependencies @@ -40,11 +42,13 @@ import { isStyle, getVariable } from './util'; * Color uses PREFIX-XXXXXXXX where XXXXXXXX is the 8 digit * hex represenation of the RGBA color. */ -const styleToColor = (style) => getSolidFromHex(getVariable(style, COLOR)); +const styleToColor = (style: string): Pattern => + getSolidFromHex(getVariable(style, COLOR)); -const colorToStyle = (color) => `${COLOR}-${getHexFromSolid(color)}`; +const colorToStyle = (color: Solid): string => + `${COLOR}-${getHexFromSolid(color)}`; -function elementToStyle(element) { +function elementToStyle(element: HTMLElement): string | null { const isSpan = element.tagName.toLowerCase() === 'span'; const rawColor = element.style.color; const hasColor = Boolean(rawColor); @@ -56,12 +60,12 @@ function elementToStyle(element) { return null; } -function stylesToCSS(styles) { +function stylesToCSS(styles: string[]): null | Record { const style = styles.find((someStyle) => isStyle(someStyle, COLOR)); if (!style) { return null; } - let color; + let color: Pattern; try { color = styleToColor(style); } catch (e) { @@ -71,7 +75,7 @@ function stylesToCSS(styles) { return generatePatternStyles(color, 'color'); } -function getColor(editorState) { +function getColor(editorState: EditorState): Pattern | '((MULTIPLE))' { const styles = getPrefixStylesInSelection(editorState, COLOR); if (styles.length > 1) { return MULTIPLE_VALUE; @@ -83,7 +87,7 @@ function getColor(editorState) { return styleToColor(colorStyle); } -function setColor(editorState, color) { +function setColor(editorState: EditorState, color: Solid) { // opaque black is default, and isn't necessary to set const isBlack = isPatternEqual(createSolid(0, 0, 0), color); const shouldSetStyle = () => !isBlack; diff --git a/packages/rich-text/src/formatters/convert.ts b/packages/rich-text/src/formatters/convert.ts index 37ea8542caf9..62033b4121e7 100644 --- a/packages/rich-text/src/formatters/convert.ts +++ b/packages/rich-text/src/formatters/convert.ts @@ -17,7 +17,13 @@ /** * External dependencies */ -import { Modifier } from 'draft-js'; +import { + Modifier, + ContentBlock, + SelectionState, + ContentState, + CharacterMetadata, +} from 'draft-js'; /** * Internal dependencies @@ -25,17 +31,21 @@ import { Modifier } from 'draft-js'; import { ITALIC, UNDERLINE } from '../customConstants'; import { weightToStyle } from './weight'; -function convertStyles(contentBlock, blockSelection, contentState) { - let updatedContentState = contentState; - let lastMetadata = null; +function convertStyles( + contentBlock: ContentBlock, + blockSelection: SelectionState, + contentState: ContentState +): ContentState { + let updatedContentState: ContentState = contentState; + let lastMetadata: CharacterMetadata | null = null; contentBlock.findStyleRanges( - (metadata) => { + (metadata: CharacterMetadata) => { lastMetadata = metadata; return true; }, (start, end) => { // Get new list of styles for this range - const oldStyles = lastMetadata.getStyle().toArray(); + const oldStyles = lastMetadata?.getStyle().toArray() ?? []; const newStyles = getNewStyles(oldStyles); // Create a selection for this range const rangeSelection = blockSelection.merge({ @@ -62,15 +72,15 @@ function convertStyles(contentBlock, blockSelection, contentState) { return updatedContentState; } -const styleMap = { - BOLD: weightToStyle('700'), +const styleMap: Record = { + BOLD: weightToStyle(700), ITALIC: ITALIC, UNDERLINE: UNDERLINE, }; -function getNewStyles(oldStyles) { +function getNewStyles(oldStyles: string[]): string[] { return oldStyles - .map((oldStyle) => (oldStyle in styleMap ? styleMap[oldStyle] : null)) + .map((oldStyle) => styleMap[oldStyle] ?? null) .filter(Boolean); } diff --git a/packages/rich-text/src/formatters/italic.ts b/packages/rich-text/src/formatters/italic.ts index e829bb1aff42..7720cdf83263 100644 --- a/packages/rich-text/src/formatters/italic.ts +++ b/packages/rich-text/src/formatters/italic.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /** * Internal dependencies */ @@ -23,7 +28,7 @@ import { getPrefixStylesInSelection, } from '../styleManipulation'; -function elementToStyle(element) { +function elementToStyle(element: HTMLElement) { const isSpan = element.tagName.toLowerCase() === 'span'; const isItalicFontStyle = element.style.fontStyle === 'italic'; if (isSpan && isItalicFontStyle) { @@ -33,7 +38,7 @@ function elementToStyle(element) { return null; } -function stylesToCSS(styles) { +function stylesToCSS(styles: string[]) { const hasItalic = styles.includes(ITALIC); if (!hasItalic) { return null; @@ -41,12 +46,15 @@ function stylesToCSS(styles) { return { fontStyle: 'italic' }; } -function isItalic(editorState) { +function isItalic(editorState: EditorState) { const styles = getPrefixStylesInSelection(editorState, ITALIC); return !styles.includes(NONE); } -function toggleItalic(editorState, flag) { +function toggleItalic( + editorState: EditorState, + flag: undefined | boolean +): EditorState { if (typeof flag === 'boolean') { return togglePrefixStyle(editorState, ITALIC, () => flag); } diff --git a/packages/rich-text/src/formatters/letterSpacing.ts b/packages/rich-text/src/formatters/letterSpacing.ts index 6b8ed7ad19c4..d73fb0c29705 100644 --- a/packages/rich-text/src/formatters/letterSpacing.ts +++ b/packages/rich-text/src/formatters/letterSpacing.ts @@ -13,6 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /** * Internal dependencies */ @@ -23,15 +29,15 @@ import { } from '../styleManipulation'; import { isStyle, numericToStyle, styleToNumeric } from './util'; -function letterSpacingToStyle(weight) { +function letterSpacingToStyle(weight: number) { return numericToStyle(LETTERSPACING, weight); } -function styleToLetterSpacing(style) { +function styleToLetterSpacing(style: string) { return styleToNumeric(LETTERSPACING, style); } -function elementToStyle(element) { +function elementToStyle(element: HTMLElement) { const isSpan = element.tagName.toLowerCase() === 'span'; // This will implicitly strip any trailing unit from the value - it's assumed to be em const letterSpacing = parseFloat(element.style.letterSpacing); @@ -43,7 +49,7 @@ function elementToStyle(element) { return null; } -function stylesToCSS(styles) { +function stylesToCSS(styles: string[]) { const style = styles.find((someStyle) => isStyle(someStyle, LETTERSPACING)); if (!style) { return null; @@ -55,7 +61,7 @@ function stylesToCSS(styles) { return { letterSpacing: `${letterSpacing / 100}em` }; } -function getLetterSpacing(editorState) { +function getLetterSpacing(editorState: EditorState) { const styles = getPrefixStylesInSelection(editorState, LETTERSPACING); if (styles.length > 1) { return MULTIPLE_VALUE; @@ -67,7 +73,7 @@ function getLetterSpacing(editorState) { return styleToLetterSpacing(spacingStyle); } -function setLetterSpacing(editorState, letterSpacing) { +function setLetterSpacing(editorState: EditorState, letterSpacing: number) { // if the spacing to set to non-0, set a style const shouldSetStyle = () => letterSpacing !== 0; diff --git a/packages/rich-text/src/formatters/underline.ts b/packages/rich-text/src/formatters/underline.ts index 07102776e120..5cae763719ac 100644 --- a/packages/rich-text/src/formatters/underline.ts +++ b/packages/rich-text/src/formatters/underline.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /** * Internal dependencies */ @@ -23,7 +28,7 @@ import { getPrefixStylesInSelection, } from '../styleManipulation'; -function elementToStyle(element) { +function elementToStyle(element: HTMLElement) { const isSpan = element.tagName.toLowerCase() === 'span'; const isUnderlineDecoration = element.style.textDecoration === 'underline'; if (isSpan && isUnderlineDecoration) { @@ -33,7 +38,7 @@ function elementToStyle(element) { return null; } -function stylesToCSS(styles) { +function stylesToCSS(styles: string[]) { const hasUnderline = styles.includes(UNDERLINE); if (!hasUnderline) { return null; @@ -41,12 +46,12 @@ function stylesToCSS(styles) { return { textDecoration: 'underline' }; } -function isUnderline(editorState) { +function isUnderline(editorState: EditorState) { const styles = getPrefixStylesInSelection(editorState, UNDERLINE); return !styles.includes(NONE); } -function toggleUnderline(editorState, flag) { +function toggleUnderline(editorState: EditorState, flag: undefined | boolean) { if (typeof flag === 'boolean') { return togglePrefixStyle(editorState, UNDERLINE, () => flag); } diff --git a/packages/rich-text/src/formatters/uppercase.ts b/packages/rich-text/src/formatters/uppercase.ts index f5030d4f3f88..f078041a65cc 100644 --- a/packages/rich-text/src/formatters/uppercase.ts +++ b/packages/rich-text/src/formatters/uppercase.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /** * Internal dependencies */ @@ -23,7 +28,7 @@ import { getPrefixStylesInSelection, } from '../styleManipulation'; -function elementToStyle(element) { +function elementToStyle(element: HTMLElement) { const isSpan = element.tagName.toLowerCase() === 'span'; const isUppercaseTransform = element.style.textTransform === 'uppercase'; if (isSpan && isUppercaseTransform) { @@ -33,7 +38,7 @@ function elementToStyle(element) { return null; } -function stylesToCSS(styles) { +function stylesToCSS(styles: string[]) { const hasUppercase = styles.includes(UPPERCASE); if (!hasUppercase) { return null; @@ -41,12 +46,12 @@ function stylesToCSS(styles) { return { textTransform: 'uppercase' }; } -function isUppercase(editorState) { +function isUppercase(editorState: EditorState) { const styles = getPrefixStylesInSelection(editorState, UPPERCASE); return !styles.includes(NONE); } -function toggleUppercase(editorState, flag) { +function toggleUppercase(editorState: EditorState, flag: undefined | boolean) { if (typeof flag === 'boolean') { return togglePrefixStyle(editorState, UPPERCASE, () => flag); } diff --git a/packages/rich-text/src/formatters/weight.ts b/packages/rich-text/src/formatters/weight.ts index 1ce71fc8db12..cdcc207d574b 100644 --- a/packages/rich-text/src/formatters/weight.ts +++ b/packages/rich-text/src/formatters/weight.ts @@ -13,6 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /** * Internal dependencies */ @@ -27,15 +33,15 @@ const NORMAL_WEIGHT = 400; const SMALLEST_BOLD = 600; const DEFAULT_BOLD = 700; -export function weightToStyle(weight) { +export function weightToStyle(weight: number) { return numericToStyle(WEIGHT, weight); } -function styleToWeight(style) { +function styleToWeight(style: string) { return styleToNumeric(WEIGHT, style); } -function elementToStyle(element) { +function elementToStyle(element: HTMLElement) { const isSpan = element.tagName.toLowerCase() === 'span'; const fontWeight = parseInt(element.style.fontWeight); const hasFontWeight = fontWeight && !isNaN(fontWeight); @@ -46,7 +52,7 @@ function elementToStyle(element) { return null; } -function stylesToCSS(styles) { +function stylesToCSS(styles: string[]) { const style = styles.find((someStyle) => isStyle(someStyle, WEIGHT)); if (!style) { return null; @@ -59,20 +65,20 @@ function stylesToCSS(styles) { } // convert a set of weight styles to a set of weights -function getWeights(styles) { +function getWeights(styles: string[]) { return styles.map((style) => style === NONE ? NORMAL_WEIGHT : styleToWeight(style) ); } -function isBold(editorState) { +function isBold(editorState: EditorState) { const styles = getPrefixStylesInSelection(editorState, WEIGHT); const weights = getWeights(styles); const allIsBold = weights.every((w) => w >= SMALLEST_BOLD); return allIsBold; } -function toggleBold(editorState, flag) { +function toggleBold(editorState: EditorState, flag: undefined | boolean) { if (typeof flag === 'boolean') { if (flag) { const getDefault = () => weightToStyle(DEFAULT_BOLD); @@ -87,18 +93,18 @@ function toggleBold(editorState, flag) { // if any character has weight less than SMALLEST_BOLD, // everything should be bolded - const shouldSetBold = (styles) => + const shouldSetBold = (styles: string[]) => getWeights(styles).some((w) => w < SMALLEST_BOLD); // if setting a bold, it should be the boldest current weight, // though at least DEFAULT_BOLD - const getBoldToSet = (styles) => + const getBoldToSet = (styles: string[]) => weightToStyle(Math.max(...[DEFAULT_BOLD].concat(getWeights(styles)))); return togglePrefixStyle(editorState, WEIGHT, shouldSetBold, getBoldToSet); } -function getFontWeight(editorState) { +function getFontWeight(editorState: EditorState) { const styles = getPrefixStylesInSelection(editorState, WEIGHT); const weights = getWeights(styles); if (weights.length > 1) { @@ -107,7 +113,7 @@ function getFontWeight(editorState) { return weights[0]; } -function setFontWeight(editorState, weight) { +function setFontWeight(editorState: EditorState, weight: number) { // if the weight to set is non-400, set a style // (if 400 is target, all other weights are just removed, and we're good) const shouldSetStyle = () => weight !== 400; diff --git a/packages/rich-text/src/styleManipulation.ts b/packages/rich-text/src/styleManipulation.ts index f2c9ebf045b7..84df1275f1ee 100644 --- a/packages/rich-text/src/styleManipulation.ts +++ b/packages/rich-text/src/styleManipulation.ts @@ -86,11 +86,14 @@ export function getPrefixStyleForCharacter(styles, prefix) { * // styles are now: ['NONE'] * * - * @param {Object} editorState Current editor state - * @param {string} prefix Prefix to test styles for - * @return {Array.} Deduped array of all matching styles + * @param editorState Current editor state + * @param prefix Prefix to test styles for + * @return Deduped array of all matching styles */ -export function getPrefixStylesInSelection(editorState, prefix) { +export function getPrefixStylesInSelection( + editorState: EditorState, + prefix: string +): string[] { const selection = editorState.getSelection(); const styleSets = getAllStyleSetsInSelection(editorState); if (selection.isCollapsed() || styleSets.length === 0) { @@ -99,7 +102,7 @@ export function getPrefixStylesInSelection(editorState, prefix) { ]; } - const styles = new Set(); + const styles = new Set(); styleSets.forEach((styleSet) => styles.add(getPrefixStyleForCharacter(styleSet, prefix)) ); @@ -125,11 +128,15 @@ function applyContent(editorState, contentState) { * should be added * @return {Object} New editor state */ + +type SetStyleCallback = (styles: string[]) => unknown; +type StyleGetter = (styles: string[]) => unknown; + export function togglePrefixStyle( editorState, prefix, - shouldSetStyle = null, - getStyleToSet = null + shouldSetStyle: SetStyleCallback | null = null, + getStyleToSet: StyleGetter | null = null ) { if (editorState.getSelection().isCollapsed()) { // A different set of rules apply here From da2cf1d7bedf2219d81717a2f5a5db695a1a9855 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 5 Sep 2022 19:38:38 +0300 Subject: [PATCH 05/35] More types --- packages/rich-text/src/customExport.ts | 7 ++-- packages/rich-text/src/customImport.ts | 2 +- packages/rich-text/src/customInlineDisplay.ts | 2 +- packages/rich-text/src/draftUtils.ts | 11 ++++-- packages/rich-text/src/fauxSelection.ts | 34 +++++++++++++------ .../utils/getCaretCharacterOffsetWithin.ts | 14 +++++--- packages/rich-text/src/utils/getValidHTML.ts | 2 +- 7 files changed, 48 insertions(+), 24 deletions(-) diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index 9671e28ec037..f4e1a4a35e42 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -18,13 +18,14 @@ * External dependencies */ import { stateToHTML } from 'draft-js-export-html'; +import type { EditorState } from 'draft-js'; /** * Internal dependencies */ import formatters from './formatters'; -function inlineStyleFn(styles) { +function inlineStyleFn(styles: string[]) { const inlineCSS = formatters.reduce( (css, { stylesToCSS }) => ({ ...css, ...stylesToCSS(styles) }), {} @@ -40,14 +41,14 @@ function inlineStyleFn(styles) { }; } -function exportHTML(editorState) { +function exportHTML(editorState: EditorState) { if (!editorState) { return null; } const html = stateToHTML(editorState.getCurrentContent(), { inlineStyleFn, - defaultBlockTag: null, + defaultBlockTag: undefined, }); return html.replace(/
/g, '').replace(/ $/, ''); diff --git a/packages/rich-text/src/customImport.ts b/packages/rich-text/src/customImport.ts index 58549656b341..d9c89fd5f54e 100644 --- a/packages/rich-text/src/customImport.ts +++ b/packages/rich-text/src/customImport.ts @@ -25,7 +25,7 @@ import { stateFromHTML } from 'draft-js-import-html'; import getValidHTML from './utils/getValidHTML'; import formatters from './formatters'; -function customInlineFn(element, { Style }) { +function customInlineFn(element: HTMLElement, { Style }) { const styleStrings = formatters .map(({ elementToStyle }) => elementToStyle(element)) .filter((style) => Boolean(style)); diff --git a/packages/rich-text/src/customInlineDisplay.ts b/packages/rich-text/src/customInlineDisplay.ts index b2383daac136..fdcad4b71fcf 100644 --- a/packages/rich-text/src/customInlineDisplay.ts +++ b/packages/rich-text/src/customInlineDisplay.ts @@ -20,7 +20,7 @@ import formatters from './formatters'; import { fauxStylesToCSS } from './fauxSelection'; -function customInlineDisplay(styles) { +function customInlineDisplay(styles: string[]) { const stylesToCSSConverters = [ ...formatters.map(({ stylesToCSS }) => stylesToCSS), fauxStylesToCSS, diff --git a/packages/rich-text/src/draftUtils.ts b/packages/rich-text/src/draftUtils.ts index 76b3ebb44e70..d441d7d696d9 100644 --- a/packages/rich-text/src/draftUtils.ts +++ b/packages/rich-text/src/draftUtils.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /* Ignore reason: This is lifted from elsewhere - a combo of these basically: * * https://github.com/webdeveloperpr/draft-js-custom-styles/blob/f3e6b533905de8eee6da54f9727b5e5803d53fc4/src/index.js#L8-L52 @@ -39,11 +44,11 @@ * output: [Set("BOLD"), Set("BOLD", "ITALIC"), Set(), Set("UNDERLINE")] * * - * @param {Object} editorState The current state of the editor including + * @param editorState The current state of the editor including * selection - * @return {Array.>} list of sets of styles as described + * @return list of sets of styles as described */ -export function getAllStyleSetsInSelection(editorState) { +export function getAllStyleSetsInSelection(editorState: EditorState) { const styleSets = []; const contentState = editorState.getCurrentContent(); const selection = editorState.getSelection(); diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index 9120cf9b85f0..b133741e4e58 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -19,10 +19,15 @@ */ import { useEffect, useState } from '@googleforcreators/react'; import { EditorState, Modifier } from 'draft-js'; +import type { SelectionState } from 'draft-js'; +import type { SetStateAction } from 'react'; const FAUX_SELECTION = 'CUSTOM-FAUX'; -function isEqualSelectionIgnoreFocus(a, b) { +function isEqualSelectionIgnoreFocus( + a: SelectionState | null, + b: SelectionState | null +) { if (!a || !b) { return false; } @@ -37,12 +42,16 @@ function isEqualSelectionIgnoreFocus(a, b) { * If current selection in editor is unfocused, set faux style on current selection * else, if current selection in editor is focused, remove faux style from entire editor * - * @param {Object} editorState Current editor state - * @param {Function} setEditorState Callback to update current editor state - * @return {void} + * @param editorState Current editor state + * @param setEditorState Callback to update current editor state */ -export function useFauxSelection(editorState, setEditorState) { - const [fauxSelection, setFauxSelection] = useState(null); +export function useFauxSelection( + editorState: EditorState, + setEditorState: SetStateAction +) { + const [fauxSelection, setFauxSelection] = useState( + null + ); useEffect(() => { if (!editorState) { setFauxSelection(null); @@ -80,12 +89,12 @@ export function useFauxSelection(editorState, setEditorState) { } if (isFocused && hasSelectionChanged && hasFauxSelection) { - setEditorState((oldEditorState) => { + setEditorState((oldEditorState: EditorState) => { try { // Get new content with style removed from old selection const contentWithoutFaux = Modifier.removeInlineStyle( oldEditorState.getCurrentContent(), - fauxSelection, + fauxSelection as SelectionState, FAUX_SELECTION ); @@ -117,12 +126,17 @@ export function useFauxSelection(editorState, setEditorState) { }, [fauxSelection, editorState, setEditorState]); } -export function fauxStylesToCSS(styles, css) { +type Style = { + backgroundColor: string; + color?: string; +}; + +export function fauxStylesToCSS(styles: string[], css: Record) { const hasFauxSelection = styles.includes(FAUX_SELECTION); if (!hasFauxSelection) { return null; } - const style = { + const style: Style = { backgroundColor: 'rgba(169, 169, 169, 0.7)', }; if (css?.color) { diff --git a/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts b/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts index e1ab3b686aa5..c1e8130697bf 100644 --- a/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts +++ b/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts @@ -31,12 +31,16 @@ * This function includes some cross-browser optimization for older browsers even * though they aren't really supported by the editor at large (IE). * - * @param {Node} element DOM node to find current selection within. - * @param {number} clientX Optional x coordinate of click. - * @param {number} clientY Optional y coordinate of click. - * @return {number} Current selection start offset as seen in `element` or 0 if not found. + * @param element DOM node to find current selection within. + * @param clientX Optional x coordinate of click. + * @param clientY Optional y coordinate of click. + * @return Current selection start offset as seen in `element` or 0 if not found. */ -function getCaretCharacterOffsetWithin(element, clientX, clientY) { +function getCaretCharacterOffsetWithin( + element: Node, + clientX: number, + clientY: number +): number { const doc = element.ownerDocument || element.document; const win = doc.defaultView || doc.parentWindow; if (typeof win.getSelection !== 'undefined') { diff --git a/packages/rich-text/src/utils/getValidHTML.ts b/packages/rich-text/src/utils/getValidHTML.ts index 92f7d975ab9c..308f5f0b171d 100644 --- a/packages/rich-text/src/utils/getValidHTML.ts +++ b/packages/rich-text/src/utils/getValidHTML.ts @@ -16,7 +16,7 @@ const contentBuffer = document.createElement('template'); -export default function getValidHTML(string) { +export default function getValidHTML(string: string) { contentBuffer.innerHTML = string; return contentBuffer.innerHTML; } From a2b9d5b736062cd75e4b2b001d26ba8a2836114e Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 6 Sep 2022 16:19:01 +0300 Subject: [PATCH 06/35] More types. --- packages/rich-text/src/fauxSelection.ts | 4 +-- packages/rich-text/src/formatters/italic.ts | 2 +- .../rich-text/src/formatters/underline.ts | 2 +- .../rich-text/src/formatters/uppercase.ts | 2 +- packages/rich-text/src/formatters/weight.ts | 5 +-- packages/rich-text/src/getFontVariants.ts | 22 ++++++++----- packages/rich-text/src/getPastedBlocks.ts | 22 ++++++++++--- packages/rich-text/src/getStateInfo.ts | 15 +++++++-- packages/rich-text/src/htmlManipulation.ts | 31 +++++++++++------- packages/rich-text/src/styleManipulation.ts | 25 ++++++++------- packages/rich-text/src/types.ts | 31 ++++++++++++++++++ packages/rich-text/src/useHandlePastedText.ts | 10 ++++-- packages/rich-text/src/usePasteTextContent.ts | 12 +++++-- packages/rich-text/src/useRichText.ts | 4 ++- .../rich-text/src/useSelectionManipulation.ts | 32 ++++++++++++------- packages/rich-text/src/util.ts | 16 +++++++--- 16 files changed, 169 insertions(+), 66 deletions(-) diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index b133741e4e58..38c62db78d40 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -20,7 +20,7 @@ import { useEffect, useState } from '@googleforcreators/react'; import { EditorState, Modifier } from 'draft-js'; import type { SelectionState } from 'draft-js'; -import type { SetStateAction } from 'react'; +import type { Dispatch, SetStateAction } from 'react'; const FAUX_SELECTION = 'CUSTOM-FAUX'; @@ -47,7 +47,7 @@ function isEqualSelectionIgnoreFocus( */ export function useFauxSelection( editorState: EditorState, - setEditorState: SetStateAction + setEditorState: Dispatch> ) { const [fauxSelection, setFauxSelection] = useState( null diff --git a/packages/rich-text/src/formatters/italic.ts b/packages/rich-text/src/formatters/italic.ts index 7720cdf83263..1277c1b36d6d 100644 --- a/packages/rich-text/src/formatters/italic.ts +++ b/packages/rich-text/src/formatters/italic.ts @@ -53,7 +53,7 @@ function isItalic(editorState: EditorState) { function toggleItalic( editorState: EditorState, - flag: undefined | boolean + flag?: undefined | boolean ): EditorState { if (typeof flag === 'boolean') { return togglePrefixStyle(editorState, ITALIC, () => flag); diff --git a/packages/rich-text/src/formatters/underline.ts b/packages/rich-text/src/formatters/underline.ts index 5cae763719ac..fe5e6137005c 100644 --- a/packages/rich-text/src/formatters/underline.ts +++ b/packages/rich-text/src/formatters/underline.ts @@ -51,7 +51,7 @@ function isUnderline(editorState: EditorState) { return !styles.includes(NONE); } -function toggleUnderline(editorState: EditorState, flag: undefined | boolean) { +function toggleUnderline(editorState: EditorState, flag?: undefined | boolean) { if (typeof flag === 'boolean') { return togglePrefixStyle(editorState, UNDERLINE, () => flag); } diff --git a/packages/rich-text/src/formatters/uppercase.ts b/packages/rich-text/src/formatters/uppercase.ts index f078041a65cc..7fafd2e8648b 100644 --- a/packages/rich-text/src/formatters/uppercase.ts +++ b/packages/rich-text/src/formatters/uppercase.ts @@ -51,7 +51,7 @@ function isUppercase(editorState: EditorState) { return !styles.includes(NONE); } -function toggleUppercase(editorState: EditorState, flag: undefined | boolean) { +function toggleUppercase(editorState: EditorState, flag?: undefined | boolean) { if (typeof flag === 'boolean') { return togglePrefixStyle(editorState, UPPERCASE, () => flag); } diff --git a/packages/rich-text/src/formatters/weight.ts b/packages/rich-text/src/formatters/weight.ts index cdcc207d574b..ba4d1d744aaf 100644 --- a/packages/rich-text/src/formatters/weight.ts +++ b/packages/rich-text/src/formatters/weight.ts @@ -78,7 +78,7 @@ function isBold(editorState: EditorState) { return allIsBold; } -function toggleBold(editorState: EditorState, flag: undefined | boolean) { +function toggleBold(editorState: EditorState, flag?: undefined | boolean) { if (typeof flag === 'boolean') { if (flag) { const getDefault = () => weightToStyle(DEFAULT_BOLD); @@ -113,7 +113,8 @@ function getFontWeight(editorState: EditorState) { return weights[0]; } -function setFontWeight(editorState: EditorState, weight: number) { +// @todo Check what happens if weight is 0, it was not always set previously. +function setFontWeight(editorState: EditorState, weight = 0) { // if the weight to set is non-400, set a style // (if 400 is target, all other weights are just removed, and we're good) const shouldSetStyle = () => weight !== 400; diff --git a/packages/rich-text/src/getFontVariants.ts b/packages/rich-text/src/getFontVariants.ts index 647313db6ee2..2bdf561af5a1 100644 --- a/packages/rich-text/src/getFontVariants.ts +++ b/packages/rich-text/src/getFontVariants.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; + /** * Internal dependencies */ @@ -25,13 +30,14 @@ import { getSelectAllStateFromHTML } from './htmlManipulation'; /** * Gets font styles for a characters, considers italic and weight only. * - * @param {Object} styles Set of styles. - * @return {[]} Array of found styles for the character. + * @param styles Set of styles. + * @return Array of found styles for the character. */ -function getFontStylesForCharacter(styles) { +function getFontStylesForCharacter( + styles: Immutable.OrderedSet +): string[] { return styles .toArray() - .map((style) => style.style ?? style) .filter((style) => style === ITALIC || style.startsWith(WEIGHT)); } @@ -39,10 +45,10 @@ function getFontStylesForCharacter(styles) { * Gets an array of all found font variants (unique). * Font variants are in the format of [italic, weight] where italic is 0 or 1. * - * @param {Object} editorState Editor state. - * @return {[]} Array of variants. + * @param editorState Editor state. + * @return Array of variants. */ -function getVariants(editorState) { +function getVariants(editorState: EditorState) { const styleSets = getAllStyleSetsInSelection(editorState); if (styleSets.length === 0) { return getFontStylesForCharacter(editorState.getCurrentInlineStyle()); @@ -56,7 +62,7 @@ function getVariants(editorState) { return [...new Set(styles)]; } -export default function getFontVariants(html) { +export default function getFontVariants(html: string) { const htmlState = getSelectAllStateFromHTML(html); const variants = getVariants(htmlState).map((variant) => [ variant.startsWith(ITALIC) ? 1 : 0, diff --git a/packages/rich-text/src/getPastedBlocks.ts b/packages/rich-text/src/getPastedBlocks.ts index 3f4188d3a5cd..bc107b47bd08 100644 --- a/packages/rich-text/src/getPastedBlocks.ts +++ b/packages/rich-text/src/getPastedBlocks.ts @@ -29,7 +29,16 @@ import { */ import convertStyles from './formatters/convert'; +type ValueOf = T[keyof T]; +type Map = { + unstyled: Record; + 'unordered-list-item': Record; + 'ordered-list-item': Record; +}; + class CustomBlockRenderMap { + _map: Map; + constructor() { // Generally only allow unstyled block types // However, we want to add list styles so keep those @@ -49,12 +58,17 @@ class CustomBlockRenderMap { } // The only function from immutable.js#Map, that we need to implement - mapKeys(callback) { + mapKeys( + callback: ( + key: string, + element: ValueOf + ) => Record | Record + ) { return Object.keys(this._map).map((key) => callback(key, this._map[key])); } } -function getPastedBlocks(html, existingStyles = []) { +function getPastedBlocks(html: string, existingStyles: string[] = []) { const renderMap = new CustomBlockRenderMap(); const { contentBlocks, entityMap } = convertFromHTML( html, @@ -78,7 +92,7 @@ function getPastedBlocks(html, existingStyles = []) { // with corrected styles and the required added list styles const newContentBlocks = noEntityContent.getBlocksAsArray(); let updatedContentState = noEntityContent; - let lastBlockType = null; + let lastBlockType: string | null = null; let lastBlockNumber = 0; newContentBlocks.forEach((contentBlock) => { @@ -141,7 +155,7 @@ function getPastedBlocks(html, existingStyles = []) { export default getPastedBlocks; -function selectEverything(contentState) { +function selectEverything(contentState: ContentState) { const firstBlock = contentState.getFirstBlock(); const lastBlock = contentState.getLastBlock(); return SelectionState.createEmpty(firstBlock.getKey()).merge({ diff --git a/packages/rich-text/src/getStateInfo.ts b/packages/rich-text/src/getStateInfo.ts index 99876a30425e..b58bd55119f2 100644 --- a/packages/rich-text/src/getStateInfo.ts +++ b/packages/rich-text/src/getStateInfo.ts @@ -14,17 +14,28 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { EditorState } from 'draft-js'; +import type { Pattern } from '@googleforcreators/patterns'; + /** * Internal dependencies */ import formatters from './formatters'; -function getStateInfo(state) { +type Getter = (state: EditorState) => boolean | string | number | Pattern; + +function getStateInfo(state: EditorState) { const stateInfo = formatters.reduce( (aggr, { getters }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(getters).map(([key, getter]) => [key, getter(state)]) + Object.entries(getters).map(([key, getter]: [string, Getter]) => [ + key, + getter(state), + ]) ), }), {} diff --git a/packages/rich-text/src/htmlManipulation.ts b/packages/rich-text/src/htmlManipulation.ts index a9ce6313aa72..29c0f03e8e94 100644 --- a/packages/rich-text/src/htmlManipulation.ts +++ b/packages/rich-text/src/htmlManipulation.ts @@ -18,6 +18,7 @@ * External dependencies */ import { EditorState } from 'draft-js'; +import type {Pattern} from "@googleforcreators/patterns"; /** * Internal dependencies @@ -32,36 +33,42 @@ import { getSelectionForAll } from './util'; * Return an editor state object with content set to parsed HTML * and selection set to everything. * - * @param {string} html HTML string to parse into content - * @return {Object} New editor state with selection + * @param html HTML string to parse into content + * @return New editor state with selection */ -export function getSelectAllStateFromHTML(html) { +export function getSelectAllStateFromHTML(html: string) { const contentState = customImport(html); const initialState = EditorState.createWithContent(contentState); const selection = getSelectionForAll(initialState.getCurrentContent()); return EditorState.forceSelection(initialState, selection); } + + /** * Convert HTML via updater function. As updater function works on the * current selection in an editor state, first parse HTML to editor state * with entire body selected, then run updater and then export back to * HTML again. * - * @param {string} html HTML string to parse into content - * @param {Function} updater A function converting a state to a new state - * @param {Array} args Extra args to supply to updater other than state - * @return {Object} New HTML with updates applied + * @param html HTML string to parse into content + * @param updater A function converting a state to a new state + * @param args Extra args to supply to updater other than state + * @return New HTML with updates applied */ -function updateAndReturnHTML(html, updater, ...args) { +function updateAndReturnHTML( + html: string, + updater: Setter, + ...args: [AllowedArgs] +) { const stateWithUpdate = updater(getSelectAllStateFromHTML(html), ...args); const renderedHTML = customExport(stateWithUpdate); return renderedHTML; } const getHTMLFormatter = - (setter) => - (html, ...args) => + (setter: Setter) => + (html: string, ...args: [AllowedArgs]) => updateAndReturnHTML(html, setter, ...args); export const getHTMLFormatters = () => { @@ -69,7 +76,7 @@ export const getHTMLFormatters = () => { (aggr, { setters }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(setters).map(([key, setter]) => [ + Object.entries(setters).map(([key, setter]: [string, Setter]) => [ key, getHTMLFormatter(setter), ]) @@ -79,7 +86,7 @@ export const getHTMLFormatters = () => { ); }; -export function getHTMLInfo(html) { +export function getHTMLInfo(html: string) { const htmlStateInfo = getStateInfo(getSelectAllStateFromHTML(html)); return htmlStateInfo; } diff --git a/packages/rich-text/src/styleManipulation.ts b/packages/rich-text/src/styleManipulation.ts index 84df1275f1ee..d44553243bcf 100644 --- a/packages/rich-text/src/styleManipulation.ts +++ b/packages/rich-text/src/styleManipulation.ts @@ -18,24 +18,30 @@ * External dependencies */ import { Modifier, EditorState } from 'draft-js'; +import type { ContentState } from 'draft-js'; /** * Internal dependencies */ import { NONE } from './customConstants'; import { getAllStyleSetsInSelection } from './draftUtils'; +import type { SetStyleCallback, StyleGetter } from './types'; /** * Get a first style in the given set that match the given prefix, * or NONE if there's no match. * - * @param {Set.} styles Set (ImmutableSet even) of styles to check - * @param {string} prefix Prefix to test styles for - * @return {string} First match or NONE + * @param styles Set (ImmutableSet even) of styles to check + * @param prefix Prefix to test styles for + * @return First match or NONE */ -export function getPrefixStyleForCharacter(styles, prefix) { +export function getPrefixStyleForCharacter( + styles: Immutable.OrderedSet, + prefix: string | typeof NONE +): string { + // @todo Why is `style.style` possible? const list = styles.toArray().map((style) => style.style ?? style); - const matcher = (style) => style && style.startsWith(prefix); + const matcher = (style: string) => style && style.startsWith(prefix); if (!list.some(matcher)) { return NONE; } @@ -110,7 +116,7 @@ export function getPrefixStylesInSelection( return [...styles]; } -function applyContent(editorState, contentState) { +function applyContent(editorState: EditorState, contentState: ContentState) { return EditorState.push(editorState, contentState, 'change-inline-style'); } @@ -129,12 +135,9 @@ function applyContent(editorState, contentState) { * @return {Object} New editor state */ -type SetStyleCallback = (styles: string[]) => unknown; -type StyleGetter = (styles: string[]) => unknown; - export function togglePrefixStyle( - editorState, - prefix, + editorState: EditorState, + prefix: string, shouldSetStyle: SetStyleCallback | null = null, getStyleToSet: StyleGetter | null = null ) { diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index e69de29bb2d1..f7b5bf5522da 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import type { Pattern } from '@googleforcreators/patterns'; +import type { EditorState } from 'draft-js'; + +export type AllowedArgs = undefined | boolean | Pattern | number; + +export type Setter = ( + state: EditorState | null, + arg: AllowedArgs +) => EditorState; + +export type SetStyleCallback = (styles: string[]) => unknown; +export type StyleGetter = (styles: string[]) => string; diff --git a/packages/rich-text/src/useHandlePastedText.ts b/packages/rich-text/src/useHandlePastedText.ts index 0c83830d344e..2445c296769a 100644 --- a/packages/rich-text/src/useHandlePastedText.ts +++ b/packages/rich-text/src/useHandlePastedText.ts @@ -18,7 +18,9 @@ * External dependencies */ import { EditorState, Modifier } from 'draft-js'; +import type { EditorChangeType } from 'draft-js'; import { useCallback } from '@googleforcreators/react'; +import type { Dispatch, SetStateAction } from 'react'; /** * Internal dependencies @@ -33,12 +35,14 @@ import { getPrefixStylesInSelection } from './styleManipulation'; * Compare this with `usePasteTextContent` that handles generally pasting * text without being in text edit-mode anywhere. */ -function useHandlePastedText(setEditorState) { +function useHandlePastedText( + setEditorState: Dispatch> +) { return useCallback( - (text, html, state) => { + (text: string, html: string, state: EditorState) => { const content = state.getCurrentContent(); const selection = state.getSelection(); - let newState, stateChange; + let newState, stateChange: EditorChangeType; if (html) { // Get the styles of the current selection context (collapsed or not), // that should be applied to the entirety of the pasted HTML. diff --git a/packages/rich-text/src/usePasteTextContent.ts b/packages/rich-text/src/usePasteTextContent.ts index 09d20604d30f..77c672c3d298 100644 --- a/packages/rich-text/src/usePasteTextContent.ts +++ b/packages/rich-text/src/usePasteTextContent.ts @@ -32,9 +32,17 @@ import customExport from './customExport'; * Compare this with `useHandlePastedText` that handles pasting text while being * in text edit-mode. */ -function usePasteTextContent(insertElement, preset) { +// @todo this really returns the inserted element and props is Partial, so is preset. +function usePasteTextContent( + insertElement: ( + type: string, + props: Record, + asBackground?: boolean + ) => Record, + preset: Record +) { return useCallback( - (html) => { + (html: string) => { const blockMap = getPastedBlocks(html); const blockArray = blockMap.toArray(); const contentState = ContentState.createFromBlockArray(blockArray); diff --git a/packages/rich-text/src/useRichText.ts b/packages/rich-text/src/useRichText.ts index e6317e151bb7..2575d3ddd1dd 100644 --- a/packages/rich-text/src/useRichText.ts +++ b/packages/rich-text/src/useRichText.ts @@ -22,8 +22,10 @@ import { identity, useContextSelector } from '@googleforcreators/react'; * Internal dependencies */ import Context from './context'; +// @todo Type State. +import type { State } from './types'; -function useRichText(selector) { +function useRichText(selector: (state: State) => T) { return useContextSelector(Context, selector ?? identity); } diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index d1d487f1029c..8f3e217cdf8d 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -24,15 +24,21 @@ import { useMemo, } from '@googleforcreators/react'; import { EditorState } from 'draft-js'; +import type { SelectionState } from 'draft-js'; +import type { Dispatch, SetStateAction } from 'react'; /** * Internal dependencies */ import formatters from './formatters'; +import type { AllowedArgs, Setter } from './types'; -function useSelectionManipulation(editorState, setEditorState) { - const lastKnownState = useRef(null); - const lastKnownSelection = useRef(null); +function useSelectionManipulation( + editorState: EditorState, + setEditorState: Dispatch> +) { + const lastKnownState = useRef(null); + const lastKnownSelection = useRef(null); useEffect(() => { lastKnownState.current = editorState; if (!editorState) { @@ -43,12 +49,16 @@ function useSelectionManipulation(editorState, setEditorState) { }, [editorState]); const updateWhileUnfocused = useCallback( - (updater, shouldForceFocus = true) => { + ( + updater: (state: EditorState | null) => EditorState, + shouldForceFocus = true + ) => { const oldState = lastKnownState.current; const selection = lastKnownSelection.current; - const workingState = shouldForceFocus - ? EditorState.forceSelection(oldState, selection) - : oldState; + const workingState = + shouldForceFocus && oldState && selection + ? EditorState.forceSelection(oldState, selection) + : oldState; const newState = updater(workingState); setEditorState(newState); }, @@ -56,13 +66,13 @@ function useSelectionManipulation(editorState, setEditorState) { ); const getSetterName = useCallback( - (setterName) => `${setterName}InSelection`, + (setterName: string) => `${setterName}InSelection`, [] ); const getSetterCallback = useCallback( - (setter, autoFocus) => - (...args) => + (setter: Setter, autoFocus) => + (...args: [AllowedArgs]) => updateWhileUnfocused((state) => setter(state, ...args), autoFocus), [updateWhileUnfocused] ); @@ -73,7 +83,7 @@ function useSelectionManipulation(editorState, setEditorState) { (aggr, { setters, autoFocus }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(setters).map(([key, setter]) => [ + Object.entries(setters).map(([key, setter]: [string, Setter]) => [ getSetterName(key), getSetterCallback(setter, autoFocus), ]) diff --git a/packages/rich-text/src/util.ts b/packages/rich-text/src/util.ts index cf01bca80aaf..4d7ba64fcd27 100644 --- a/packages/rich-text/src/util.ts +++ b/packages/rich-text/src/util.ts @@ -18,7 +18,9 @@ * External dependencies */ import { EditorState, SelectionState } from 'draft-js'; +import type { ContentState } from 'draft-js'; import { filterEditorState } from 'draftjs-filters'; +import type { Dispatch, SetStateAction } from 'react'; /** * Internal dependencies @@ -28,7 +30,10 @@ import italicFormatter from './formatters/italic'; import underlineFormatter from './formatters/underline'; import uppercaseFormatter from './formatters/uppercase'; -export function getFilteredState(editorState, oldEditorState) { +export function getFilteredState( + editorState: EditorState, + oldEditorState: EditorState +) { const shouldFilterPaste = oldEditorState.getCurrentContent() !== editorState.getCurrentContent() && editorState.getLastChangeType() === 'insert-fragment'; @@ -49,7 +54,7 @@ export function getFilteredState(editorState, oldEditorState) { ); } -function getStateFromCommmand(command, oldEditorState) { +function getStateFromCommmand(command: string, oldEditorState: EditorState) { switch (command) { case 'bold': return weightFormatter.setters.toggleBold(oldEditorState); @@ -75,7 +80,8 @@ function getStateFromCommmand(command, oldEditorState) { } export const getHandleKeyCommandFromState = - (setEditorState) => (command, currentEditorState) => { + (setEditorState: Dispatch>) => + (command: string, currentEditorState: EditorState) => { const newEditorState = getStateFromCommmand(command, currentEditorState); if (newEditorState) { setEditorState(newEditorState); @@ -84,7 +90,7 @@ export const getHandleKeyCommandFromState = return 'not-handled'; }; -export function getSelectionForAll(content) { +export function getSelectionForAll(content: ContentState) { const firstBlock = content.getFirstBlock(); const lastBlock = content.getLastBlock(); return new SelectionState({ @@ -95,7 +101,7 @@ export function getSelectionForAll(content) { }); } -export function getSelectionForOffset(content, offset) { +export function getSelectionForOffset(content: ContentState, offset: number) { const blocks = content.getBlocksAsArray(); let countdown = offset; for (let i = 0; i < blocks.length && countdown >= 0; i++) { From 9592bcccaca3508bbeee794f13a0da02c312d31c Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 6 Sep 2022 16:58:54 +0300 Subject: [PATCH 07/35] Type state. --- packages/rich-text/src/getStateInfo.ts | 3 ++- packages/rich-text/src/provider.tsx | 4 +-- packages/rich-text/src/types.ts | 36 ++++++++++++++++++++++++++ packages/rich-text/src/useRichText.ts | 1 - packages/rich-text/src/util.ts | 3 +-- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/rich-text/src/getStateInfo.ts b/packages/rich-text/src/getStateInfo.ts index b58bd55119f2..28f064568542 100644 --- a/packages/rich-text/src/getStateInfo.ts +++ b/packages/rich-text/src/getStateInfo.ts @@ -24,10 +24,11 @@ import type { Pattern } from '@googleforcreators/patterns'; * Internal dependencies */ import formatters from './formatters'; +import type { StateInfo } from './types'; type Getter = (state: EditorState) => boolean | string | number | Pattern; -function getStateInfo(state: EditorState) { +function getStateInfo(state: EditorState): StateInfo { const stateInfo = formatters.reduce( (aggr, { getters }) => ({ ...aggr, diff --git a/packages/rich-text/src/provider.tsx b/packages/rich-text/src/provider.tsx index bf35b7270179..5e19f3307aee 100644 --- a/packages/rich-text/src/provider.tsx +++ b/packages/rich-text/src/provider.tsx @@ -62,7 +62,7 @@ function RichTextProvider({ children, editingState }) { }, [editorState]); const setStateFromContent = useCallback( - (content) => { + (content: string) => { const { offset, clearContent, selectAll } = editingState || {}; let state = EditorState.createWithContent(customImport(content)); if (clearContent) { @@ -91,7 +91,7 @@ function RichTextProvider({ children, editingState }) { // on paste and updates state accordingly. // Furthermore it also sets initial selection if relevant. const updateEditorState = useCallback( - (newEditorState) => { + (newEditorState: EditorState) => { let filteredState = getFilteredState(newEditorState, editorState); const isEmpty = filteredState.getCurrentContent().getPlainText('') === ''; if (isEmpty) { diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index f7b5bf5522da..b25b19fc02fe 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -29,3 +29,39 @@ export type Setter = ( export type SetStyleCallback = (styles: string[]) => unknown; export type StyleGetter = (styles: string[]) => string; + +export interface SelectionInfo { + isBold: boolean; + isItalic: boolean; + isUnderline: boolean; + isUppercase: boolean; +} + +// Partial to make the props optional. +export interface StateInfo extends Partial { + color?: Pattern; + letterSpacing?: number; + fontWeight?: number; +} + +export interface State { + state: { + editorState?: EditorState; + hasCurrentEditor?: boolean; + selectionInfo?: SelectionInfo; + }; + actions: { + setStateFromContent?: (content: string) => void; + updateEditorState?: (state: EditorState) => void; + getHandleKeyCommand?: () => string; + handlePastedText?: ( + text: string, + html: string, + state: EditorState + ) => boolean; + clearState?: () => void; + // @todo Add formatters. + selectionActions?: {}; + getContentFromState?: (editorState: EditorState) => string | null; + }; +} diff --git a/packages/rich-text/src/useRichText.ts b/packages/rich-text/src/useRichText.ts index 2575d3ddd1dd..b00931cd1b02 100644 --- a/packages/rich-text/src/useRichText.ts +++ b/packages/rich-text/src/useRichText.ts @@ -22,7 +22,6 @@ import { identity, useContextSelector } from '@googleforcreators/react'; * Internal dependencies */ import Context from './context'; -// @todo Type State. import type { State } from './types'; function useRichText(selector: (state: State) => T) { diff --git a/packages/rich-text/src/util.ts b/packages/rich-text/src/util.ts index 4d7ba64fcd27..6964da663a0e 100644 --- a/packages/rich-text/src/util.ts +++ b/packages/rich-text/src/util.ts @@ -20,7 +20,6 @@ import { EditorState, SelectionState } from 'draft-js'; import type { ContentState } from 'draft-js'; import { filterEditorState } from 'draftjs-filters'; -import type { Dispatch, SetStateAction } from 'react'; /** * Internal dependencies @@ -80,7 +79,7 @@ function getStateFromCommmand(command: string, oldEditorState: EditorState) { } export const getHandleKeyCommandFromState = - (setEditorState: Dispatch>) => + (setEditorState: (state: EditorState) => void) => (command: string, currentEditorState: EditorState) => { const newEditorState = getStateFromCommmand(command, currentEditorState); if (newEditorState) { From 9deb517205ebfd1ac5cd6faef10d6c7f987f61c1 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Wed, 7 Sep 2022 16:51:26 +0300 Subject: [PATCH 08/35] Fix text. --- packages/rich-text/src/customExport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index f4e1a4a35e42..eab75eedb4fc 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -48,7 +48,7 @@ function exportHTML(editorState: EditorState) { const html = stateToHTML(editorState.getCurrentContent(), { inlineStyleFn, - defaultBlockTag: undefined, + defaultBlockTag: null, }); return html.replace(/
/g, '').replace(/ $/, ''); From 05d48b2f3e53a781c92c0b8961ca74c13e3e158b Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Wed, 7 Sep 2022 16:56:48 +0300 Subject: [PATCH 09/35] Ignore ts --- packages/rich-text/src/customExport.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index eab75eedb4fc..d45347931ad6 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -48,6 +48,8 @@ function exportHTML(editorState: EditorState) { const html = stateToHTML(editorState.getCurrentContent(), { inlineStyleFn, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- See the next line for reason. + // @ts-ignore Reason: `defaultBlockTag` is optional (undefined | string), however, `undefined` means `p` so we need to use `null` here. defaultBlockTag: null, }); From b3fab604dc6ea8fa5020f2b6b7a4bb766d95786c Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Wed, 7 Sep 2022 18:11:37 +0300 Subject: [PATCH 10/35] Update provider.ts --- packages/rich-text/src/fauxSelection.ts | 4 +-- packages/rich-text/src/provider.tsx | 32 ++++++++----------- packages/rich-text/src/types.ts | 16 ++++++++-- packages/rich-text/src/useHandlePastedText.ts | 2 +- .../rich-text/src/useSelectionManipulation.ts | 4 +-- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index 38c62db78d40..a14145fc079b 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -46,8 +46,8 @@ function isEqualSelectionIgnoreFocus( * @param setEditorState Callback to update current editor state */ export function useFauxSelection( - editorState: EditorState, - setEditorState: Dispatch> + editorState: EditorState | null, + setEditorState: Dispatch> ) { const [fauxSelection, setFauxSelection] = useState( null diff --git a/packages/rich-text/src/provider.tsx b/packages/rich-text/src/provider.tsx index 5e19f3307aee..d58be256e463 100644 --- a/packages/rich-text/src/provider.tsx +++ b/packages/rich-text/src/provider.tsx @@ -17,7 +17,6 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import { useState, useCallback, @@ -25,6 +24,7 @@ import { useRef, } from '@googleforcreators/react'; import { EditorState } from 'draft-js'; +import type { DraftInlineStyle } from 'draft-js'; /** * Internal dependencies @@ -42,6 +42,7 @@ import customImport from './customImport'; import customExport from './customExport'; import useHandlePastedText from './useHandlePastedText'; import useSelectionManipulation from './useSelectionManipulation'; +import type { RichTextProviderProps } from './types'; const INITIAL_SELECTION_INFO = { isBold: false, @@ -50,9 +51,9 @@ const INITIAL_SELECTION_INFO = { isUppercase: false, }; -function RichTextProvider({ children, editingState }) { - const [editorState, setEditorState] = useState(null); - const lastKnownStyle = useRef(null); +function RichTextProvider({ children, editingState }: RichTextProviderProps) { + const [editorState, setEditorState] = useState(null); + const lastKnownStyle = useRef(null); const selectionInfo = useMemo(() => { if (editorState) { @@ -63,17 +64,13 @@ function RichTextProvider({ children, editingState }) { const setStateFromContent = useCallback( (content: string) => { - const { offset, clearContent, selectAll } = editingState || {}; + const { offset, selectAll } = editingState || {}; let state = EditorState.createWithContent(customImport(content)); - if (clearContent) { - // If `clearContent` is specified, push the update to clear content so that - // it can be undone. - state = EditorState.push(state, customImport(''), 'remove-range'); - } + let selection; if (selectAll) { selection = getSelectionForAll(state.getCurrentContent()); - } else if (!isNaN(offset)) { + } else if (offset) { selection = getSelectionForOffset(state.getCurrentContent(), offset); } if (selection) { @@ -92,9 +89,13 @@ function RichTextProvider({ children, editingState }) { // Furthermore it also sets initial selection if relevant. const updateEditorState = useCallback( (newEditorState: EditorState) => { + if (!newEditorState || !editorState) { + return; + } let filteredState = getFilteredState(newEditorState, editorState); - const isEmpty = filteredState.getCurrentContent().getPlainText('') === ''; - if (isEmpty) { + const isEmpty = + filteredState?.getCurrentContent().getPlainText('') === ''; + if (isEmpty && lastKnownStyle.current) { // Copy last known current style as inline style filteredState = EditorState.setInlineStyleOverride( filteredState, @@ -151,9 +152,4 @@ function RichTextProvider({ children, editingState }) { ); } -RichTextProvider.propTypes = { - children: PropTypes.node.isRequired, - editingState: PropTypes.object, -}; - export default RichTextProvider; diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index b25b19fc02fe..decc0570205d 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -19,6 +19,7 @@ */ import type { Pattern } from '@googleforcreators/patterns'; import type { EditorState } from 'draft-js'; +import type { ReactNode } from 'react'; export type AllowedArgs = undefined | boolean | Pattern | number; @@ -60,8 +61,19 @@ export interface State { state: EditorState ) => boolean; clearState?: () => void; - // @todo Add formatters. - selectionActions?: {}; + selectionActions?: Record; getContentFromState?: (editorState: EditorState) => string | null; }; } + +interface EditingState { + hasEditMenu?: boolean; + showOverflow?: boolean; + selectAll?: boolean; + offset?: number; +} + +export interface RichTextProviderProps { + children: ReactNode; + editingState: EditingState; +} diff --git a/packages/rich-text/src/useHandlePastedText.ts b/packages/rich-text/src/useHandlePastedText.ts index 2445c296769a..b4b3bf38f160 100644 --- a/packages/rich-text/src/useHandlePastedText.ts +++ b/packages/rich-text/src/useHandlePastedText.ts @@ -36,7 +36,7 @@ import { getPrefixStylesInSelection } from './styleManipulation'; * text without being in text edit-mode anywhere. */ function useHandlePastedText( - setEditorState: Dispatch> + setEditorState: Dispatch> ) { return useCallback( (text: string, html: string, state: EditorState) => { diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index 8f3e217cdf8d..854baf938c7b 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -34,8 +34,8 @@ import formatters from './formatters'; import type { AllowedArgs, Setter } from './types'; function useSelectionManipulation( - editorState: EditorState, - setEditorState: Dispatch> + editorState: EditorState | null, + setEditorState: Dispatch> ) { const lastKnownState = useRef(null); const lastKnownSelection = useRef(null); From e96fb551b659551ff28a1bc5d713c4bbea892724 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Wed, 7 Sep 2022 18:47:28 +0300 Subject: [PATCH 11/35] More types. --- packages/rich-text/src/customImport.ts | 6 ++++-- packages/rich-text/src/customInlineDisplay.ts | 1 + packages/rich-text/src/editor.tsx | 19 +++++++++++-------- packages/rich-text/src/fauxSelection.ts | 5 ++++- packages/rich-text/src/htmlManipulation.ts | 14 ++++++-------- packages/rich-text/src/types.ts | 8 ++++---- .../rich-text/src/useSelectionManipulation.ts | 16 +++++++++------- 7 files changed, 39 insertions(+), 30 deletions(-) diff --git a/packages/rich-text/src/customImport.ts b/packages/rich-text/src/customImport.ts index d9c89fd5f54e..474c3fc8bf25 100644 --- a/packages/rich-text/src/customImport.ts +++ b/packages/rich-text/src/customImport.ts @@ -18,6 +18,7 @@ * External dependencies */ import { stateFromHTML } from 'draft-js-import-html'; +import type { InlineCreators } from 'draft-js-import-html'; /** * Internal dependencies @@ -25,15 +26,16 @@ import { stateFromHTML } from 'draft-js-import-html'; import getValidHTML from './utils/getValidHTML'; import formatters from './formatters'; -function customInlineFn(element: HTMLElement, { Style }) { +function customInlineFn(element: Element, { Style }: InlineCreators) { const styleStrings = formatters - .map(({ elementToStyle }) => elementToStyle(element)) + .map(({ elementToStyle }) => elementToStyle(element as HTMLElement)) .filter((style) => Boolean(style)); if (styleStrings.length === 0) { return null; } + // @todo Type of Style doesn't seem to be correct, check. return Style(styleStrings); } diff --git a/packages/rich-text/src/customInlineDisplay.ts b/packages/rich-text/src/customInlineDisplay.ts index fdcad4b71fcf..4d6a0a5fc049 100644 --- a/packages/rich-text/src/customInlineDisplay.ts +++ b/packages/rich-text/src/customInlineDisplay.ts @@ -20,6 +20,7 @@ import formatters from './formatters'; import { fauxStylesToCSS } from './fauxSelection'; +// @todo Proper return type. function customInlineDisplay(styles: string[]) { const stylesToCSSConverters = [ ...formatters.map(({ stylesToCSS }) => stylesToCSS), diff --git a/packages/rich-text/src/editor.tsx b/packages/rich-text/src/editor.tsx index f3770dd611ec..dec89b2038f8 100644 --- a/packages/rich-text/src/editor.tsx +++ b/packages/rich-text/src/editor.tsx @@ -18,7 +18,6 @@ * External dependencies */ import { Editor, getDefaultKeyBinding, KeyBindingUtil } from 'draft-js'; -import PropTypes from 'prop-types'; import { useEffect, useRef, @@ -26,6 +25,7 @@ import { forwardRef, useUnmount, } from '@googleforcreators/react'; +import type { ForwardedRef, KeyboardEvent } from 'react'; /** * Internal dependencies @@ -33,7 +33,15 @@ import { import useRichText from './useRichText'; import customInlineDisplay from './customInlineDisplay'; -function RichTextEditor({ content, onChange }, ref) { +interface RichTextEditorProps { + content: string; + onChange: (content: string) => void; +} + +function RichTextEditor( + { content, onChange }: RichTextEditorProps, + ref: ForwardedRef +) { const editorRef = useRef(null); const { state: { editorState }, @@ -82,7 +90,7 @@ function RichTextEditor({ content, onChange }, ref) { const { hasCommandModifier } = KeyBindingUtil; - function bindKeys(e) { + function bindKeys(e: KeyboardEvent) { if (e.code === 'KeyA' && hasCommandModifier(e)) { return 'selectall'; } @@ -108,9 +116,4 @@ function RichTextEditor({ content, onChange }, ref) { const RichTextEditorWithRef = forwardRef(RichTextEditor); -RichTextEditor.propTypes = { - content: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, -}; - export default RichTextEditorWithRef; diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index a14145fc079b..fb93e920b333 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -89,8 +89,11 @@ export function useFauxSelection( } if (isFocused && hasSelectionChanged && hasFauxSelection) { - setEditorState((oldEditorState: EditorState) => { + setEditorState((oldEditorState: EditorState | null) => { try { + if (!oldEditorState) { + return null; + } // Get new content with style removed from old selection const contentWithoutFaux = Modifier.removeInlineStyle( oldEditorState.getCurrentContent(), diff --git a/packages/rich-text/src/htmlManipulation.ts b/packages/rich-text/src/htmlManipulation.ts index 29c0f03e8e94..17575cf0575c 100644 --- a/packages/rich-text/src/htmlManipulation.ts +++ b/packages/rich-text/src/htmlManipulation.ts @@ -18,7 +18,6 @@ * External dependencies */ import { EditorState } from 'draft-js'; -import type {Pattern} from "@googleforcreators/patterns"; /** * Internal dependencies @@ -28,6 +27,7 @@ import getStateInfo from './getStateInfo'; import customImport from './customImport'; import customExport from './customExport'; import { getSelectionForAll } from './util'; +import type { StyleSetter, AllowedSetterArgs } from './types'; /** * Return an editor state object with content set to parsed HTML @@ -43,8 +43,6 @@ export function getSelectAllStateFromHTML(html: string) { return EditorState.forceSelection(initialState, selection); } - - /** * Convert HTML via updater function. As updater function works on the * current selection in an editor state, first parse HTML to editor state @@ -58,8 +56,8 @@ export function getSelectAllStateFromHTML(html: string) { */ function updateAndReturnHTML( html: string, - updater: Setter, - ...args: [AllowedArgs] + updater: StyleSetter, + ...args: [AllowedSetterArgs] ) { const stateWithUpdate = updater(getSelectAllStateFromHTML(html), ...args); const renderedHTML = customExport(stateWithUpdate); @@ -67,8 +65,8 @@ function updateAndReturnHTML( } const getHTMLFormatter = - (setter: Setter) => - (html: string, ...args: [AllowedArgs]) => + (setter: StyleSetter) => + (html: string, ...args: [AllowedSetterArgs]) => updateAndReturnHTML(html, setter, ...args); export const getHTMLFormatters = () => { @@ -76,7 +74,7 @@ export const getHTMLFormatters = () => { (aggr, { setters }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(setters).map(([key, setter]: [string, Setter]) => [ + Object.entries(setters).map(([key, setter]: [string, StyleSetter]) => [ key, getHTMLFormatter(setter), ]) diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index decc0570205d..214b50af720f 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -21,11 +21,11 @@ import type { Pattern } from '@googleforcreators/patterns'; import type { EditorState } from 'draft-js'; import type { ReactNode } from 'react'; -export type AllowedArgs = undefined | boolean | Pattern | number; +export type AllowedSetterArgs = undefined | boolean | Pattern | number; -export type Setter = ( +export type StyleSetter = ( state: EditorState | null, - arg: AllowedArgs + arg: AllowedSetterArgs ) => EditorState; export type SetStyleCallback = (styles: string[]) => unknown; @@ -61,7 +61,7 @@ export interface State { state: EditorState ) => boolean; clearState?: () => void; - selectionActions?: Record; + selectionActions?: Record; getContentFromState?: (editorState: EditorState) => string | null; }; } diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index 854baf938c7b..d2ba26428485 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -31,7 +31,7 @@ import type { Dispatch, SetStateAction } from 'react'; * Internal dependencies */ import formatters from './formatters'; -import type { AllowedArgs, Setter } from './types'; +import type { AllowedSetterArgs, StyleSetter } from './types'; function useSelectionManipulation( editorState: EditorState | null, @@ -71,8 +71,8 @@ function useSelectionManipulation( ); const getSetterCallback = useCallback( - (setter: Setter, autoFocus) => - (...args: [AllowedArgs]) => + (setter: StyleSetter, autoFocus) => + (...args: [AllowedSetterArgs]) => updateWhileUnfocused((state) => setter(state, ...args), autoFocus), [updateWhileUnfocused] ); @@ -83,10 +83,12 @@ function useSelectionManipulation( (aggr, { setters, autoFocus }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(setters).map(([key, setter]: [string, Setter]) => [ - getSetterName(key), - getSetterCallback(setter, autoFocus), - ]) + Object.entries(setters).map( + ([key, setter]: [string, StyleSetter]) => [ + getSetterName(key), + getSetterCallback(setter, autoFocus), + ] + ) ), }), {} From 9bd12f2dcd1f3e0f6f18cf5ca75aec4c2d6dd421 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Wed, 7 Sep 2022 19:22:57 +0300 Subject: [PATCH 12/35] Update context.ts --- packages/rich-text/src/context.ts | 6 +++++- packages/rich-text/src/provider.tsx | 4 ++-- packages/rich-text/src/types.ts | 9 ++++++--- packages/rich-text/src/useSelectionManipulation.ts | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/rich-text/src/context.ts b/packages/rich-text/src/context.ts index 419e9d691734..08f4d3ef0eb3 100644 --- a/packages/rich-text/src/context.ts +++ b/packages/rich-text/src/context.ts @@ -18,7 +18,11 @@ * External dependencies */ import { createContext } from '@googleforcreators/react'; +/** + * Internal dependencies + */ +import type { State } from './types'; -const RichTextContext = createContext({ state: {}, actions: {} }); +const RichTextContext = createContext({ state: {}, actions: {} }); export default RichTextContext; diff --git a/packages/rich-text/src/provider.tsx b/packages/rich-text/src/provider.tsx index d58be256e463..548ee247af48 100644 --- a/packages/rich-text/src/provider.tsx +++ b/packages/rich-text/src/provider.tsx @@ -42,7 +42,7 @@ import customImport from './customImport'; import customExport from './customExport'; import useHandlePastedText from './useHandlePastedText'; import useSelectionManipulation from './useSelectionManipulation'; -import type { RichTextProviderProps } from './types'; +import type { RichTextProviderProps, State } from './types'; const INITIAL_SELECTION_INFO = { isBold: false, @@ -127,7 +127,7 @@ function RichTextProvider({ children, editingState }: RichTextProviderProps) { setEditorState ); - const value = { + const value: State = { state: { editorState, hasCurrentEditor, diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index 214b50af720f..673f7c98ae3f 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -47,14 +47,17 @@ export interface StateInfo extends Partial { export interface State { state: { - editorState?: EditorState; + editorState?: EditorState | null; hasCurrentEditor?: boolean; - selectionInfo?: SelectionInfo; + selectionInfo?: StateInfo | undefined; }; actions: { setStateFromContent?: (content: string) => void; updateEditorState?: (state: EditorState) => void; - getHandleKeyCommand?: () => string; + getHandleKeyCommand?: () => ( + command: string, + currentEditorState: EditorState + ) => 'handled' | 'not-handled'; handlePastedText?: ( text: string, html: string, diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index d2ba26428485..f5fc94b6a7c7 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -36,7 +36,7 @@ import type { AllowedSetterArgs, StyleSetter } from './types'; function useSelectionManipulation( editorState: EditorState | null, setEditorState: Dispatch> -) { +): Record { const lastKnownState = useRef(null); const lastKnownSelection = useRef(null); useEffect(() => { From ce03a9b78c64e33db91950e376658e155bf4bfa9 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 12:12:52 +0300 Subject: [PATCH 13/35] More types --- packages/rich-text/src/customImport.ts | 3 +- packages/rich-text/src/editor.tsx | 13 ++--- packages/rich-text/src/styleManipulation.ts | 6 +-- packages/rich-text/src/types.ts | 5 ++ .../rich-text/src/useSelectionManipulation.ts | 2 +- .../utils/getCaretCharacterOffsetWithin.ts | 53 +++++++++++-------- tsconfig.shared.json | 2 +- 7 files changed, 50 insertions(+), 34 deletions(-) diff --git a/packages/rich-text/src/customImport.ts b/packages/rich-text/src/customImport.ts index 474c3fc8bf25..e6a57f76d9e6 100644 --- a/packages/rich-text/src/customImport.ts +++ b/packages/rich-text/src/customImport.ts @@ -35,7 +35,8 @@ function customInlineFn(element: Element, { Style }: InlineCreators) { return null; } - // @todo Type of Style doesn't seem to be correct, check. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- The defined type is `string` but it actually accepts arrays of strings, too. + // @ts-ignore return Style(styleStrings); } diff --git a/packages/rich-text/src/editor.tsx b/packages/rich-text/src/editor.tsx index dec89b2038f8..7e328005a792 100644 --- a/packages/rich-text/src/editor.tsx +++ b/packages/rich-text/src/editor.tsx @@ -32,17 +32,18 @@ import type { ForwardedRef, KeyboardEvent } from 'react'; */ import useRichText from './useRichText'; import customInlineDisplay from './customInlineDisplay'; +import type { RichTextEditorProps, State } from './types'; -interface RichTextEditorProps { - content: string; - onChange: (content: string) => void; +interface DraftEditor { + editorContainer: HTMLElement | null | undefined; + focus(): void; } function RichTextEditor( { content, onChange }: RichTextEditorProps, - ref: ForwardedRef + ref: ForwardedRef> ) { - const editorRef = useRef(null); + const editorRef = useRef(null); const { state: { editorState }, actions: { @@ -53,7 +54,7 @@ function RichTextEditor( handlePastedText, clearState, }, - } = useRichText(); + } = useRichText(); // Load state from parent when content changes useEffect(() => { diff --git a/packages/rich-text/src/styleManipulation.ts b/packages/rich-text/src/styleManipulation.ts index d44553243bcf..16cbf9d7f02f 100644 --- a/packages/rich-text/src/styleManipulation.ts +++ b/packages/rich-text/src/styleManipulation.ts @@ -39,13 +39,13 @@ export function getPrefixStyleForCharacter( styles: Immutable.OrderedSet, prefix: string | typeof NONE ): string { - // @todo Why is `style.style` possible? - const list = styles.toArray().map((style) => style.style ?? style); + const list = styles.toArray(); const matcher = (style: string) => style && style.startsWith(prefix); if (!list.some(matcher)) { return NONE; } - return list.find(matcher); + // If there are no matches, we return NONE, so the value is not `undefined` here, thus casting to `string`. + return list.find(matcher) as string; } /** diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index 673f7c98ae3f..af2077ef9873 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -80,3 +80,8 @@ export interface RichTextProviderProps { children: ReactNode; editingState: EditingState; } + +export interface RichTextEditorProps { + content: string; + onChange: (content: string) => void; +} diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index f5fc94b6a7c7..cbd57240503e 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -43,7 +43,7 @@ function useSelectionManipulation( lastKnownState.current = editorState; if (!editorState) { lastKnownSelection.current = null; - } else if (editorState.getSelection().hasFocus) { + } else if (editorState.getSelection().getHasFocus()) { lastKnownSelection.current = editorState.getSelection(); } }, [editorState]); diff --git a/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts b/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts index c1e8130697bf..12ba29adfaee 100644 --- a/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts +++ b/packages/rich-text/src/utils/getCaretCharacterOffsetWithin.ts @@ -36,27 +36,46 @@ * @param clientY Optional y coordinate of click. * @return Current selection start offset as seen in `element` or 0 if not found. */ + +interface CaretPosition { + readonly offset: number; + readonly offsetNode: Node; + getClientRect(): DOMRect | null; +} + +interface SelectionDocument extends Document { + caretPositionFromPoint?: (x: number, y: number) => CaretPosition | null; +} + +// Extend for wider browser support. +interface SelectionElement extends Node { + document?: SelectionDocument; +} + function getCaretCharacterOffsetWithin( - element: Node, + element: SelectionElement, clientX: number, clientY: number ): number { - const doc = element.ownerDocument || element.document; - const win = doc.defaultView || doc.parentWindow; - if (typeof win.getSelection !== 'undefined') { + const doc = (element.ownerDocument || element.document) as SelectionDocument; + const win = doc?.defaultView; + if (typeof win?.getSelection !== 'undefined') { let range; if (clientX && clientY) { if (doc.caretPositionFromPoint) { - const caretPosition = document.caretPositionFromPoint(clientX, clientY); + const caretPosition = doc.caretPositionFromPoint(clientX, clientY); // Create a range from the caret position. if (caretPosition) { range = document.createRange(); range.setStart(caretPosition.offsetNode, caretPosition.offset); } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- caretRangeFromPoint does not exist in FF, even though TS claims it always exists. + // @ts-ignore } else if (doc.caretRangeFromPoint) { // Change 'user-select' temporarily to make getting the correct range work for Safari. // See https://github.com/googleforcreators/web-stories-wp/issues/7745. - const elementsToAdjust = document.querySelectorAll('[data-fix-caret]'); + const elementsToAdjust = + document.querySelectorAll('[data-fix-caret]'); for (const elementToAdjust of elementsToAdjust) { elementToAdjust.style.webkitUserSelect = 'auto'; } @@ -68,8 +87,8 @@ function getCaretCharacterOffsetWithin( } if (!range) { const sel = win.getSelection(); - if (sel.rangeCount > 0) { - range = win.getSelection().getRangeAt(0); + if (sel && sel.rangeCount > 0) { + range = win.getSelection()?.getRangeAt(0); } } if (range) { @@ -77,23 +96,13 @@ function getCaretCharacterOffsetWithin( preCaretRange.selectNodeContents(element); preCaretRange.setEnd(range.endContainer, range.endOffset); // This is for ensuring that if the logic fails to get the correct offset, it won't cause unexpected behaviour. - if (preCaretRange.toString().length <= element.textContent.length) { + if ( + element.textContent && + preCaretRange.toString().length <= element.textContent.length + ) { return preCaretRange.toString().length; } - return 0; - } - } - - const sel = doc.selection; - if (sel && sel.type !== 'Control') { - const textRange = sel.createRange(); - if (clientX && clientY) { - textRange.moveToPoint(clientX, clientY); } - const preCaretTextRange = doc.body.createTextRange(); - preCaretTextRange.moveToElementText(element); - preCaretTextRange.setEndPoint('EndToEnd', textRange); - return preCaretTextRange.text.length; } return 0; diff --git a/tsconfig.shared.json b/tsconfig.shared.json index 2b57ca3296eb..11f8fca0a467 100644 --- a/tsconfig.shared.json +++ b/tsconfig.shared.json @@ -6,7 +6,7 @@ "jsx": "preserve", "target": "esnext", "module": "esnext", - "lib": ["dom", "esnext"], + "lib": ["dom", "dom.iterable", "esnext"], "declaration": true, "declarationMap": true, "composite": true, From fbe6e77c1b1cd4cffbfb1e3f6fc7d7616621e11d Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 12:33:49 +0300 Subject: [PATCH 14/35] More type error fixes. --- packages/patterns/src/generatePatternStyles.ts | 2 +- packages/rich-text/src/customInlineDisplay.ts | 8 ++++++-- packages/rich-text/src/formatters/weight.ts | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/patterns/src/generatePatternStyles.ts b/packages/patterns/src/generatePatternStyles.ts index 6f28e608bddc..18dde203ce16 100644 --- a/packages/patterns/src/generatePatternStyles.ts +++ b/packages/patterns/src/generatePatternStyles.ts @@ -106,7 +106,7 @@ function getStopList(stops: Array, alpha: number) { function generatePatternStyles( pattern: Pattern | null = null, property = 'background' -) { +): Record { if (pattern === null) { return { [property]: 'transparent' }; } diff --git a/packages/rich-text/src/customInlineDisplay.ts b/packages/rich-text/src/customInlineDisplay.ts index 4d6a0a5fc049..e84f3f0677f9 100644 --- a/packages/rich-text/src/customInlineDisplay.ts +++ b/packages/rich-text/src/customInlineDisplay.ts @@ -14,14 +14,18 @@ * limitations under the License. */ +/** + * External dependencies + */ +import type { CSSProperties } from 'react'; + /** * Internal dependencies */ import formatters from './formatters'; import { fauxStylesToCSS } from './fauxSelection'; -// @todo Proper return type. -function customInlineDisplay(styles: string[]) { +function customInlineDisplay(styles: string[]): CSSProperties { const stylesToCSSConverters = [ ...formatters.map(({ stylesToCSS }) => stylesToCSS), fauxStylesToCSS, diff --git a/packages/rich-text/src/formatters/weight.ts b/packages/rich-text/src/formatters/weight.ts index ba4d1d744aaf..1467b98c4006 100644 --- a/packages/rich-text/src/formatters/weight.ts +++ b/packages/rich-text/src/formatters/weight.ts @@ -113,7 +113,6 @@ function getFontWeight(editorState: EditorState) { return weights[0]; } -// @todo Check what happens if weight is 0, it was not always set previously. function setFontWeight(editorState: EditorState, weight = 0) { // if the weight to set is non-400, set a style // (if 400 is target, all other weights are just removed, and we're good) From 7026b32658dc12e57e1ed05454e7f7e7f0b2a8df Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 13:42:38 +0300 Subject: [PATCH 15/35] Use DraftInlineStyle --- packages/react/src/useContextSelector.ts | 2 +- packages/rich-text/src/context.ts | 7 +++++- packages/rich-text/src/customExport.ts | 4 ++-- packages/rich-text/src/customInlineDisplay.ts | 3 ++- packages/rich-text/src/editor.tsx | 13 ++++------ packages/rich-text/src/formatters/color.ts | 4 ++-- packages/rich-text/src/formatters/italic.ts | 4 ++-- .../rich-text/src/formatters/letterSpacing.ts | 4 ++-- .../rich-text/src/formatters/underline.ts | 4 ++-- .../rich-text/src/formatters/uppercase.ts | 4 ++-- packages/rich-text/src/formatters/util.ts | 4 ++-- packages/rich-text/src/formatters/weight.ts | 4 ++-- packages/rich-text/src/types.ts | 24 +++++++++---------- packages/rich-text/src/useHandlePastedText.ts | 10 +++++--- packages/rich-text/src/useRichText.ts | 4 +++- 15 files changed, 52 insertions(+), 43 deletions(-) diff --git a/packages/react/src/useContextSelector.ts b/packages/react/src/useContextSelector.ts index cbad6f1c911f..dcd471819f29 100644 --- a/packages/react/src/useContextSelector.ts +++ b/packages/react/src/useContextSelector.ts @@ -53,7 +53,7 @@ function useContextSelector( const equalityFnCallback = (state: T) => { const selected = selector(state); - if (equalityFn(ref.current, selected)) { + if (ref.current && equalityFn(ref.current, selected)) { return ref.current; } ref.current = selected; diff --git a/packages/rich-text/src/context.ts b/packages/rich-text/src/context.ts index 08f4d3ef0eb3..5436acc880b1 100644 --- a/packages/rich-text/src/context.ts +++ b/packages/rich-text/src/context.ts @@ -23,6 +23,11 @@ import { createContext } from '@googleforcreators/react'; */ import type { State } from './types'; -const RichTextContext = createContext({ state: {}, actions: {} }); +// @todo Not sure how to use Partial correctly here to work with empty state and context selector. +// Should make all state and action values optional as before? +const RichTextContext = createContext({ + state: {}, + actions: {}, +} as State); export default RichTextContext; diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index d45347931ad6..90582b6ad7e7 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -18,14 +18,14 @@ * External dependencies */ import { stateToHTML } from 'draft-js-export-html'; -import type { EditorState } from 'draft-js'; +import type { DraftInlineStyle, EditorState } from 'draft-js'; /** * Internal dependencies */ import formatters from './formatters'; -function inlineStyleFn(styles: string[]) { +function inlineStyleFn(styles: DraftInlineStyle) { const inlineCSS = formatters.reduce( (css, { stylesToCSS }) => ({ ...css, ...stylesToCSS(styles) }), {} diff --git a/packages/rich-text/src/customInlineDisplay.ts b/packages/rich-text/src/customInlineDisplay.ts index e84f3f0677f9..7ca989f63605 100644 --- a/packages/rich-text/src/customInlineDisplay.ts +++ b/packages/rich-text/src/customInlineDisplay.ts @@ -18,6 +18,7 @@ * External dependencies */ import type { CSSProperties } from 'react'; +import type { DraftInlineStyle } from 'draft-js'; /** * Internal dependencies @@ -25,7 +26,7 @@ import type { CSSProperties } from 'react'; import formatters from './formatters'; import { fauxStylesToCSS } from './fauxSelection'; -function customInlineDisplay(styles: string[]): CSSProperties { +function customInlineDisplay(styles: DraftInlineStyle): CSSProperties { const stylesToCSSConverters = [ ...formatters.map(({ stylesToCSS }) => stylesToCSS), fauxStylesToCSS, diff --git a/packages/rich-text/src/editor.tsx b/packages/rich-text/src/editor.tsx index 7e328005a792..4d2e194212f1 100644 --- a/packages/rich-text/src/editor.tsx +++ b/packages/rich-text/src/editor.tsx @@ -18,6 +18,7 @@ * External dependencies */ import { Editor, getDefaultKeyBinding, KeyBindingUtil } from 'draft-js'; +import type { EditorState } from 'draft-js'; import { useEffect, useRef, @@ -34,16 +35,11 @@ import useRichText from './useRichText'; import customInlineDisplay from './customInlineDisplay'; import type { RichTextEditorProps, State } from './types'; -interface DraftEditor { - editorContainer: HTMLElement | null | undefined; - focus(): void; -} - function RichTextEditor( { content, onChange }: RichTextEditorProps, ref: ForwardedRef> ) { - const editorRef = useRef(null); + const editorRef = useRef(null); const { state: { editorState }, actions: { @@ -54,7 +50,7 @@ function RichTextEditor( handlePastedText, clearState, }, - } = useRichText(); + } = useRichText() as State; // Load state from parent when content changes useEffect(() => { @@ -104,7 +100,8 @@ function RichTextEditor( { +function stylesToCSS(styles: DraftInlineStyle): null | Record { const style = styles.find((someStyle) => isStyle(someStyle, COLOR)); if (!style) { return null; diff --git a/packages/rich-text/src/formatters/italic.ts b/packages/rich-text/src/formatters/italic.ts index 1277c1b36d6d..ec2ff91086c7 100644 --- a/packages/rich-text/src/formatters/italic.ts +++ b/packages/rich-text/src/formatters/italic.ts @@ -17,7 +17,7 @@ /** * External dependencies */ -import type { EditorState } from 'draft-js'; +import type { EditorState, DraftInlineStyle } from 'draft-js'; /** * Internal dependencies @@ -38,7 +38,7 @@ function elementToStyle(element: HTMLElement) { return null; } -function stylesToCSS(styles: string[]) { +function stylesToCSS(styles: DraftInlineStyle) { const hasItalic = styles.includes(ITALIC); if (!hasItalic) { return null; diff --git a/packages/rich-text/src/formatters/letterSpacing.ts b/packages/rich-text/src/formatters/letterSpacing.ts index d73fb0c29705..5adc234a8a6c 100644 --- a/packages/rich-text/src/formatters/letterSpacing.ts +++ b/packages/rich-text/src/formatters/letterSpacing.ts @@ -17,7 +17,7 @@ /** * External dependencies */ -import type { EditorState } from 'draft-js'; +import type { EditorState, DraftInlineStyle } from 'draft-js'; /** * Internal dependencies @@ -49,7 +49,7 @@ function elementToStyle(element: HTMLElement) { return null; } -function stylesToCSS(styles: string[]) { +function stylesToCSS(styles: DraftInlineStyle) { const style = styles.find((someStyle) => isStyle(someStyle, LETTERSPACING)); if (!style) { return null; diff --git a/packages/rich-text/src/formatters/underline.ts b/packages/rich-text/src/formatters/underline.ts index fe5e6137005c..a1342972dd00 100644 --- a/packages/rich-text/src/formatters/underline.ts +++ b/packages/rich-text/src/formatters/underline.ts @@ -17,7 +17,7 @@ /** * External dependencies */ -import type { EditorState } from 'draft-js'; +import type { DraftInlineStyle, EditorState } from 'draft-js'; /** * Internal dependencies @@ -38,7 +38,7 @@ function elementToStyle(element: HTMLElement) { return null; } -function stylesToCSS(styles: string[]) { +function stylesToCSS(styles: DraftInlineStyle) { const hasUnderline = styles.includes(UNDERLINE); if (!hasUnderline) { return null; diff --git a/packages/rich-text/src/formatters/uppercase.ts b/packages/rich-text/src/formatters/uppercase.ts index 7fafd2e8648b..d3fc34d7fa0a 100644 --- a/packages/rich-text/src/formatters/uppercase.ts +++ b/packages/rich-text/src/formatters/uppercase.ts @@ -17,7 +17,7 @@ /** * External dependencies */ -import type { EditorState } from 'draft-js'; +import type { EditorState, DraftInlineStyle } from 'draft-js'; /** * Internal dependencies @@ -38,7 +38,7 @@ function elementToStyle(element: HTMLElement) { return null; } -function stylesToCSS(styles: string[]) { +function stylesToCSS(styles: DraftInlineStyle) { const hasUppercase = styles.includes(UPPERCASE); if (!hasUppercase) { return null; diff --git a/packages/rich-text/src/formatters/util.ts b/packages/rich-text/src/formatters/util.ts index ffe6e057d5ab..7c8e9dc63a35 100644 --- a/packages/rich-text/src/formatters/util.ts +++ b/packages/rich-text/src/formatters/util.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -export const isStyle = (style: string, prefix: string) => - style.startsWith(prefix); +export const isStyle = (style: string | undefined, prefix: string) => + Boolean(style?.startsWith(prefix)); export const getVariable = (style: string, prefix: string) => style.slice(prefix.length + 1); diff --git a/packages/rich-text/src/formatters/weight.ts b/packages/rich-text/src/formatters/weight.ts index 1467b98c4006..09c7d9ce18cf 100644 --- a/packages/rich-text/src/formatters/weight.ts +++ b/packages/rich-text/src/formatters/weight.ts @@ -17,7 +17,7 @@ /** * External dependencies */ -import type { EditorState } from 'draft-js'; +import type { EditorState, DraftInlineStyle } from 'draft-js'; /** * Internal dependencies @@ -52,7 +52,7 @@ function elementToStyle(element: HTMLElement) { return null; } -function stylesToCSS(styles: string[]) { +function stylesToCSS(styles: DraftInlineStyle) { const style = styles.find((someStyle) => isStyle(someStyle, WEIGHT)); if (!style) { return null; diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index af2077ef9873..f56d25da91b8 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -47,25 +47,25 @@ export interface StateInfo extends Partial { export interface State { state: { - editorState?: EditorState | null; - hasCurrentEditor?: boolean; - selectionInfo?: StateInfo | undefined; + editorState: EditorState | null; + hasCurrentEditor: boolean; + selectionInfo: StateInfo | undefined; }; actions: { - setStateFromContent?: (content: string) => void; - updateEditorState?: (state: EditorState) => void; - getHandleKeyCommand?: () => ( + setStateFromContent: (content: string) => void; + updateEditorState: (state: EditorState) => void; + getHandleKeyCommand: () => ( command: string, currentEditorState: EditorState ) => 'handled' | 'not-handled'; - handlePastedText?: ( + handlePastedText: ( text: string, html: string, state: EditorState - ) => boolean; - clearState?: () => void; - selectionActions?: Record; - getContentFromState?: (editorState: EditorState) => string | null; + ) => 'handled' | 'not-handled'; + clearState: () => void; + selectionActions: Record; + getContentFromState: (editorState: EditorState) => string | null; }; } @@ -83,5 +83,5 @@ export interface RichTextProviderProps { export interface RichTextEditorProps { content: string; - onChange: (content: string) => void; + onChange: (content: string | null) => void; } diff --git a/packages/rich-text/src/useHandlePastedText.ts b/packages/rich-text/src/useHandlePastedText.ts index b4b3bf38f160..6b0dfe50e486 100644 --- a/packages/rich-text/src/useHandlePastedText.ts +++ b/packages/rich-text/src/useHandlePastedText.ts @@ -18,7 +18,7 @@ * External dependencies */ import { EditorState, Modifier } from 'draft-js'; -import type { EditorChangeType } from 'draft-js'; +import type { DraftHandleValue, EditorChangeType } from 'draft-js'; import { useCallback } from '@googleforcreators/react'; import type { Dispatch, SetStateAction } from 'react'; @@ -39,7 +39,11 @@ function useHandlePastedText( setEditorState: Dispatch> ) { return useCallback( - (text: string, html: string, state: EditorState) => { + ( + text: string, + html: string | undefined, + state: EditorState + ): DraftHandleValue => { const content = state.getCurrentContent(); const selection = state.getSelection(); let newState, stateChange: EditorChangeType; @@ -63,7 +67,7 @@ function useHandlePastedText( } const result = EditorState.push(state, newState, stateChange); setEditorState(result); - return true; + return 'handled'; }, [setEditorState] ); diff --git a/packages/rich-text/src/useRichText.ts b/packages/rich-text/src/useRichText.ts index b00931cd1b02..ab3ddc73a2f2 100644 --- a/packages/rich-text/src/useRichText.ts +++ b/packages/rich-text/src/useRichText.ts @@ -24,7 +24,9 @@ import { identity, useContextSelector } from '@googleforcreators/react'; import Context from './context'; import type { State } from './types'; -function useRichText(selector: (state: State) => T) { +function useRichText( + selector?: (state: State | null) => Partial | State +) { return useContextSelector(Context, selector ?? identity); } From 6c4042b752b742c22fcf7a8c69608d248c585049 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 13:45:17 +0300 Subject: [PATCH 16/35] One more DraftInlineStyle usage --- packages/rich-text/src/fauxSelection.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index fb93e920b333..004b2b37c492 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -19,7 +19,7 @@ */ import { useEffect, useState } from '@googleforcreators/react'; import { EditorState, Modifier } from 'draft-js'; -import type { SelectionState } from 'draft-js'; +import type { DraftInlineStyle, SelectionState } from 'draft-js'; import type { Dispatch, SetStateAction } from 'react'; const FAUX_SELECTION = 'CUSTOM-FAUX'; @@ -134,7 +134,10 @@ type Style = { color?: string; }; -export function fauxStylesToCSS(styles: string[], css: Record) { +export function fauxStylesToCSS( + styles: DraftInlineStyle, + css: Record +) { const hasFauxSelection = styles.includes(FAUX_SELECTION); if (!hasFauxSelection) { return null; From c02797a111bc6b96a9e5743b496be702c59832da Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 15:41:04 +0300 Subject: [PATCH 17/35] Use CSSProperties --- packages/rich-text/src/fauxSelection.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index 004b2b37c492..d8110a5dc6c3 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -20,7 +20,7 @@ import { useEffect, useState } from '@googleforcreators/react'; import { EditorState, Modifier } from 'draft-js'; import type { DraftInlineStyle, SelectionState } from 'draft-js'; -import type { Dispatch, SetStateAction } from 'react'; +import type { CSSProperties, Dispatch, SetStateAction } from 'react'; const FAUX_SELECTION = 'CUSTOM-FAUX'; @@ -129,11 +129,6 @@ export function useFauxSelection( }, [fauxSelection, editorState, setEditorState]); } -type Style = { - backgroundColor: string; - color?: string; -}; - export function fauxStylesToCSS( styles: DraftInlineStyle, css: Record @@ -142,7 +137,7 @@ export function fauxStylesToCSS( if (!hasFauxSelection) { return null; } - const style: Style = { + const style: CSSProperties = { backgroundColor: 'rgba(169, 169, 169, 0.7)', }; if (css?.color) { From 298cfc11de3f817afeedce71b996c01fe87a504a Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 15:47:04 +0300 Subject: [PATCH 18/35] Fix import --- packages/rich-text/src/formatters/convert.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rich-text/src/formatters/convert.ts b/packages/rich-text/src/formatters/convert.ts index 62033b4121e7..c22671dc9e1d 100644 --- a/packages/rich-text/src/formatters/convert.ts +++ b/packages/rich-text/src/formatters/convert.ts @@ -17,8 +17,8 @@ /** * External dependencies */ -import { - Modifier, +import { Modifier } from 'draft-js'; +import type { ContentBlock, SelectionState, ContentState, From 27752693f4fbe212f5be5d01a0d2b404afb1dd48 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Thu, 8 Sep 2022 18:23:32 +0300 Subject: [PATCH 19/35] Override convertFromHTML type --- packages/rich-text/src/getPastedBlocks.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/rich-text/src/getPastedBlocks.ts b/packages/rich-text/src/getPastedBlocks.ts index bc107b47bd08..cd946dfead50 100644 --- a/packages/rich-text/src/getPastedBlocks.ts +++ b/packages/rich-text/src/getPastedBlocks.ts @@ -24,6 +24,8 @@ import { SelectionState, } from 'draft-js'; +import type { ContentBlock } from 'draft-js'; + /** * Internal dependencies */ @@ -68,13 +70,18 @@ class CustomBlockRenderMap { } } +// Overriding this since entityMap is marked as `any` in there, however, it seems to be an object in this case. +type ConvertFromHTMLReturn = { + contentBlocks: ContentBlock[]; + entityMap: Record; +}; function getPastedBlocks(html: string, existingStyles: string[] = []) { const renderMap = new CustomBlockRenderMap(); const { contentBlocks, entityMap } = convertFromHTML( html, undefined /* This has to be undefined to trigger default argument */, renderMap - ); + ) as ConvertFromHTMLReturn; const pastedContentState = ContentState.createFromBlockArray( contentBlocks, entityMap From 75bfe9d1ee0c60be7dd09de9de113ec32596adfc Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Fri, 9 Sep 2022 12:05:50 +0300 Subject: [PATCH 20/35] Try fixing pasting types. --- packages/rich-text/src/getPastedBlocks.ts | 36 +++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/rich-text/src/getPastedBlocks.ts b/packages/rich-text/src/getPastedBlocks.ts index cd946dfead50..8f32b294be85 100644 --- a/packages/rich-text/src/getPastedBlocks.ts +++ b/packages/rich-text/src/getPastedBlocks.ts @@ -23,23 +23,21 @@ import { ContentState, SelectionState, } from 'draft-js'; - -import type { ContentBlock } from 'draft-js'; +import type { ContentBlock, DraftBlockRenderConfig } from 'draft-js'; /** * Internal dependencies */ import convertStyles from './formatters/convert'; -type ValueOf = T[keyof T]; -type Map = { - unstyled: Record; - 'unordered-list-item': Record; - 'ordered-list-item': Record; -}; +interface TagMap { + unstyled: DraftBlockRenderConfig | undefined; + 'unordered-list-item': DraftBlockRenderConfig | undefined; + 'ordered-list-item': DraftBlockRenderConfig | undefined; +} class CustomBlockRenderMap { - _map: Map; + _map: TagMap; constructor() { // Generally only allow unstyled block types @@ -48,6 +46,8 @@ class CustomBlockRenderMap { this._map = { unstyled: { element: 'div', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- aliasedElements actually does exist on the type when looking at the module but the correct type is not imported. + // @ts-ignore aliasedElements: ['p', 'h1', 'h2', 'h3', 'blockquote', 'pre'], }, 'unordered-list-item': { @@ -58,15 +58,19 @@ class CustomBlockRenderMap { }, }; } - // The only function from immutable.js#Map, that we need to implement - mapKeys( + mapKeys( callback: ( - key: string, - element: ValueOf - ) => Record | Record - ) { - return Object.keys(this._map).map((key) => callback(key, this._map[key])); + key?: string | undefined, + value?: DraftBlockRenderConfig | undefined, + iter?: + | Immutable.Collection.Keyed + | undefined + ) => M + ): M[] { + return Object.keys(this._map).map((key) => + callback(key, this._map[key as keyof TagMap]) + ); } } From 45ce84af02b6976a676da69c456ee7e59fdf68ee Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 12 Sep 2022 16:21:55 +0300 Subject: [PATCH 21/35] Use Immutable Map for renderMap --- packages/rich-text/src/getPastedBlocks.ts | 62 ++++++----------------- 1 file changed, 16 insertions(+), 46 deletions(-) diff --git a/packages/rich-text/src/getPastedBlocks.ts b/packages/rich-text/src/getPastedBlocks.ts index 8f32b294be85..deacbdadd631 100644 --- a/packages/rich-text/src/getPastedBlocks.ts +++ b/packages/rich-text/src/getPastedBlocks.ts @@ -23,56 +23,27 @@ import { ContentState, SelectionState, } from 'draft-js'; -import type { ContentBlock, DraftBlockRenderConfig } from 'draft-js'; +import type { ContentBlock } from 'draft-js'; /** * Internal dependencies */ import convertStyles from './formatters/convert'; -interface TagMap { - unstyled: DraftBlockRenderConfig | undefined; - 'unordered-list-item': DraftBlockRenderConfig | undefined; - 'ordered-list-item': DraftBlockRenderConfig | undefined; -} - -class CustomBlockRenderMap { - _map: TagMap; - - constructor() { - // Generally only allow unstyled block types - // However, we want to add list styles so keep those - // Will be replaced with unstyled blocks later - this._map = { - unstyled: { - element: 'div', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- aliasedElements actually does exist on the type when looking at the module but the correct type is not imported. - // @ts-ignore - aliasedElements: ['p', 'h1', 'h2', 'h3', 'blockquote', 'pre'], - }, - 'unordered-list-item': { - element: 'li', - }, - 'ordered-list-item': { - element: 'li', - }, - }; - } - // The only function from immutable.js#Map, that we need to implement - mapKeys( - callback: ( - key?: string | undefined, - value?: DraftBlockRenderConfig | undefined, - iter?: - | Immutable.Collection.Keyed - | undefined - ) => M - ): M[] { - return Object.keys(this._map).map((key) => - callback(key, this._map[key as keyof TagMap]) - ); - } -} +const RENDER_MAP = Immutable.Map({ + unstyled: { + element: 'div', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- aliasedElements actually does exist on the type when looking at the module but the correct type is not imported. + // @ts-ignore + aliasedElements: ['p', 'h1', 'h2', 'h3', 'blockquote', 'pre'], + }, + 'unordered-list-item': { + element: 'li', + }, + 'ordered-list-item': { + element: 'li', + }, +}); // Overriding this since entityMap is marked as `any` in there, however, it seems to be an object in this case. type ConvertFromHTMLReturn = { @@ -80,11 +51,10 @@ type ConvertFromHTMLReturn = { entityMap: Record; }; function getPastedBlocks(html: string, existingStyles: string[] = []) { - const renderMap = new CustomBlockRenderMap(); const { contentBlocks, entityMap } = convertFromHTML( html, undefined /* This has to be undefined to trigger default argument */, - renderMap + RENDER_MAP ) as ConvertFromHTMLReturn; const pastedContentState = ContentState.createFromBlockArray( contentBlocks, From da481f007ec20a7d4014e4d804890b2bb57d586e Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 12 Sep 2022 16:24:50 +0300 Subject: [PATCH 22/35] Move types for props to the relevant component files. --- packages/rich-text/src/editor.tsx | 7 ++++++- packages/rich-text/src/provider.tsx | 14 +++++++++++++- packages/rich-text/src/types.ts | 18 ------------------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/rich-text/src/editor.tsx b/packages/rich-text/src/editor.tsx index 4d2e194212f1..b17310acbda8 100644 --- a/packages/rich-text/src/editor.tsx +++ b/packages/rich-text/src/editor.tsx @@ -33,7 +33,12 @@ import type { ForwardedRef, KeyboardEvent } from 'react'; */ import useRichText from './useRichText'; import customInlineDisplay from './customInlineDisplay'; -import type { RichTextEditorProps, State } from './types'; +import type { State } from './types'; + +export interface RichTextEditorProps { + content: string; + onChange: (content: string | null) => void; +} function RichTextEditor( { content, onChange }: RichTextEditorProps, diff --git a/packages/rich-text/src/provider.tsx b/packages/rich-text/src/provider.tsx index 548ee247af48..8dfa21fa0e26 100644 --- a/packages/rich-text/src/provider.tsx +++ b/packages/rich-text/src/provider.tsx @@ -23,6 +23,7 @@ import { useMemo, useRef, } from '@googleforcreators/react'; +import type { ReactNode } from 'react'; import { EditorState } from 'draft-js'; import type { DraftInlineStyle } from 'draft-js'; @@ -42,7 +43,7 @@ import customImport from './customImport'; import customExport from './customExport'; import useHandlePastedText from './useHandlePastedText'; import useSelectionManipulation from './useSelectionManipulation'; -import type { RichTextProviderProps, State } from './types'; +import type { State } from './types'; const INITIAL_SELECTION_INFO = { isBold: false, @@ -51,6 +52,17 @@ const INITIAL_SELECTION_INFO = { isUppercase: false, }; +interface EditingState { + hasEditMenu?: boolean; + showOverflow?: boolean; + selectAll?: boolean; + offset?: number; +} + +export interface RichTextProviderProps { + children: ReactNode; + editingState: EditingState; +} function RichTextProvider({ children, editingState }: RichTextProviderProps) { const [editorState, setEditorState] = useState(null); const lastKnownStyle = useRef(null); diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index f56d25da91b8..784c9f347a67 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -19,7 +19,6 @@ */ import type { Pattern } from '@googleforcreators/patterns'; import type { EditorState } from 'draft-js'; -import type { ReactNode } from 'react'; export type AllowedSetterArgs = undefined | boolean | Pattern | number; @@ -68,20 +67,3 @@ export interface State { getContentFromState: (editorState: EditorState) => string | null; }; } - -interface EditingState { - hasEditMenu?: boolean; - showOverflow?: boolean; - selectAll?: boolean; - offset?: number; -} - -export interface RichTextProviderProps { - children: ReactNode; - editingState: EditingState; -} - -export interface RichTextEditorProps { - content: string; - onChange: (content: string | null) => void; -} From 4664bd5ffb2b3d1d0e58c52eb3ad3968609ac6a2 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 12 Sep 2022 16:30:04 +0300 Subject: [PATCH 23/35] Use !== null instead of Boolean() --- packages/rich-text/src/editor.tsx | 5 ++--- packages/rich-text/src/fauxSelection.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/rich-text/src/editor.tsx b/packages/rich-text/src/editor.tsx index b17310acbda8..99e0d1c6ffb9 100644 --- a/packages/rich-text/src/editor.tsx +++ b/packages/rich-text/src/editor.tsx @@ -71,7 +71,7 @@ function RichTextEditor( onChange(newContent); }, [onChange, getContentFromState, editorState]); - const hasEditorState = Boolean(editorState); + const hasEditorState = editorState !== null; // On unmount, clear state in provider useUnmount(clearState); @@ -105,8 +105,7 @@ function RichTextEditor( Date: Mon, 12 Sep 2022 16:30:48 +0300 Subject: [PATCH 24/35] Use CSSProperties --- packages/rich-text/src/fauxSelection.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/rich-text/src/fauxSelection.ts b/packages/rich-text/src/fauxSelection.ts index d2d1ea4fe6ca..f8783ccb3cb8 100644 --- a/packages/rich-text/src/fauxSelection.ts +++ b/packages/rich-text/src/fauxSelection.ts @@ -129,10 +129,7 @@ export function useFauxSelection( }, [fauxSelection, editorState, setEditorState]); } -export function fauxStylesToCSS( - styles: DraftInlineStyle, - css: Record -) { +export function fauxStylesToCSS(styles: DraftInlineStyle, css: CSSProperties) { const hasFauxSelection = styles.includes(FAUX_SELECTION); if (!hasFauxSelection) { return null; From b1f8ee01a187354d335760ee952c5250b7563eec Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 12 Sep 2022 16:35:38 +0300 Subject: [PATCH 25/35] Adjust styleMap to be a bit cleaner. --- packages/rich-text/src/formatters/convert.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rich-text/src/formatters/convert.ts b/packages/rich-text/src/formatters/convert.ts index c22671dc9e1d..7ac64834e37f 100644 --- a/packages/rich-text/src/formatters/convert.ts +++ b/packages/rich-text/src/formatters/convert.ts @@ -72,15 +72,18 @@ function convertStyles( return updatedContentState; } -const styleMap: Record = { +const styleMap = { BOLD: weightToStyle(700), ITALIC: ITALIC, UNDERLINE: UNDERLINE, }; +type StyleMapKey = keyof typeof styleMap; function getNewStyles(oldStyles: string[]): string[] { return oldStyles - .map((oldStyle) => styleMap[oldStyle] ?? null) + .map((oldStyle) => + oldStyle in styleMap ? styleMap[oldStyle as StyleMapKey] : '' + ) .filter(Boolean); } From 56c273b01a90764d2ff7f023ae34006a5fc44303 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 12 Sep 2022 16:41:40 +0300 Subject: [PATCH 26/35] Add function overload for useRichText() --- packages/rich-text/src/editor.tsx | 4 +--- packages/rich-text/src/useRichText.ts | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/rich-text/src/editor.tsx b/packages/rich-text/src/editor.tsx index 99e0d1c6ffb9..4740690d9040 100644 --- a/packages/rich-text/src/editor.tsx +++ b/packages/rich-text/src/editor.tsx @@ -18,7 +18,6 @@ * External dependencies */ import { Editor, getDefaultKeyBinding, KeyBindingUtil } from 'draft-js'; -import type { EditorState } from 'draft-js'; import { useEffect, useRef, @@ -33,7 +32,6 @@ import type { ForwardedRef, KeyboardEvent } from 'react'; */ import useRichText from './useRichText'; import customInlineDisplay from './customInlineDisplay'; -import type { State } from './types'; export interface RichTextEditorProps { content: string; @@ -55,7 +53,7 @@ function RichTextEditor( handlePastedText, clearState, }, - } = useRichText() as State; + } = useRichText(); // Load state from parent when content changes useEffect(() => { diff --git a/packages/rich-text/src/useRichText.ts b/packages/rich-text/src/useRichText.ts index ab3ddc73a2f2..559ecc2f66b7 100644 --- a/packages/rich-text/src/useRichText.ts +++ b/packages/rich-text/src/useRichText.ts @@ -24,6 +24,7 @@ import { identity, useContextSelector } from '@googleforcreators/react'; import Context from './context'; import type { State } from './types'; +function useRichText(): State; function useRichText( selector?: (state: State | null) => Partial | State ) { From 14d8cccd7f23737b0f88796c8d6b6243e28fc03e Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Mon, 12 Sep 2022 16:48:42 +0300 Subject: [PATCH 27/35] Init empty state with values --- packages/rich-text/src/context.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/rich-text/src/context.ts b/packages/rich-text/src/context.ts index 5436acc880b1..d8f3c9ccd87f 100644 --- a/packages/rich-text/src/context.ts +++ b/packages/rich-text/src/context.ts @@ -23,11 +23,21 @@ import { createContext } from '@googleforcreators/react'; */ import type { State } from './types'; -// @todo Not sure how to use Partial correctly here to work with empty state and context selector. -// Should make all state and action values optional as before? const RichTextContext = createContext({ - state: {}, - actions: {}, -} as State); + state: { + editorState: null, + hasCurrentEditor: false, + selectionInfo: undefined, + }, + actions: { + setStateFromContent: () => undefined, + updateEditorState: () => undefined, + getHandleKeyCommand: () => () => 'not-handled', + handlePastedText: () => 'not-handled', + clearState: () => undefined, + selectionActions: {}, + getContentFromState: () => null, + }, +}); export default RichTextContext; From da066f28d0ba3cb6e0f1c20055474af262be0872 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 13 Sep 2022 14:32:12 +0300 Subject: [PATCH 28/35] Add Formatter interface --- packages/rich-text/src/formatters/index.ts | 3 ++- packages/rich-text/src/formatters/uppercase.ts | 2 +- packages/rich-text/src/types.ts | 14 +++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/rich-text/src/formatters/index.ts b/packages/rich-text/src/formatters/index.ts index 4600a139ed22..178caea2e1c4 100644 --- a/packages/rich-text/src/formatters/index.ts +++ b/packages/rich-text/src/formatters/index.ts @@ -17,6 +17,7 @@ /** * Internal dependencies */ +import type { Formatter } from '../types'; import weightFormatter from './weight'; import italicFormatter from './italic'; import underlineFormatter from './underline'; @@ -24,7 +25,7 @@ import colorFormatter from './color'; import letterSpacingFormatter from './letterSpacing'; import uppercaseFormatter from './uppercase'; -const formatters = [ +const formatters: Formatter[] = [ weightFormatter, italicFormatter, underlineFormatter, diff --git a/packages/rich-text/src/formatters/uppercase.ts b/packages/rich-text/src/formatters/uppercase.ts index d3fc34d7fa0a..59af63230c4e 100644 --- a/packages/rich-text/src/formatters/uppercase.ts +++ b/packages/rich-text/src/formatters/uppercase.ts @@ -43,7 +43,7 @@ function stylesToCSS(styles: DraftInlineStyle) { if (!hasUppercase) { return null; } - return { textTransform: 'uppercase' }; + return { textTransform: 'uppercase' as const }; } function isUppercase(editorState: EditorState) { diff --git a/packages/rich-text/src/types.ts b/packages/rich-text/src/types.ts index 784c9f347a67..4c41c3dd7d19 100644 --- a/packages/rich-text/src/types.ts +++ b/packages/rich-text/src/types.ts @@ -18,7 +18,8 @@ * External dependencies */ import type { Pattern } from '@googleforcreators/patterns'; -import type { EditorState } from 'draft-js'; +import type { EditorState, DraftInlineStyle } from 'draft-js'; +import type { CSSProperties } from 'react'; export type AllowedSetterArgs = undefined | boolean | Pattern | number; @@ -67,3 +68,14 @@ export interface State { getContentFromState: (editorState: EditorState) => string | null; }; } + +// @todo Figure out a better type for setters. +type AllowedGetterArgs = '((MULTIPLE))' | boolean | Pattern | number; +type Getter = (state: EditorState) => AllowedGetterArgs; +export interface Formatter { + elementToStyle: (element: HTMLElement) => string | null; + stylesToCSS: (styles: DraftInlineStyle) => CSSProperties | null; + getters: Record; + autoFocus: boolean; + setters: Record; +} From 8b8ce752cf5ec9cf593aa3c04e83e30a9020a2eb Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 13 Sep 2022 16:06:08 +0300 Subject: [PATCH 29/35] Import immutable --- package-lock.json | 18 ++++++++++++++---- packages/rich-text/package.json | 1 + packages/rich-text/src/customExport.ts | 3 ++- packages/rich-text/src/getPastedBlocks.ts | 3 ++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e12e8826a7d4..129db12d5bb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48646,6 +48646,7 @@ "draft-js-export-html": "^1.4.1", "draft-js-import-html": "^1.4.1", "draftjs-filters": "^3.0.1", + "immutable": "^4.1.0", "prop-types": "^15.7.2" }, "devDependencies": { @@ -48657,6 +48658,11 @@ "npm": ">= 7.3" } }, + "packages/rich-text/node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==" + }, "packages/stickers": { "name": "@googleforcreators/stickers", "version": "0.1.202208291229", @@ -48942,8 +48948,6 @@ "@googleforcreators/dashboard": "*", "@googleforcreators/date": "*", "@googleforcreators/design-system": "*", - "@googleforcreators/element-library": "*", - "@googleforcreators/elements": "*", "@googleforcreators/fonts": "*", "@googleforcreators/i18n": "*", "@googleforcreators/media": "*", @@ -51200,7 +51204,15 @@ "draft-js-export-html": "^1.4.1", "draft-js-import-html": "^1.4.1", "draftjs-filters": "^3.0.1", + "immutable": "^4.1.0", "prop-types": "^15.7.2" + }, + "dependencies": { + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==" + } } }, "@googleforcreators/stickers": { @@ -60998,8 +61010,6 @@ "@googleforcreators/dashboard": "*", "@googleforcreators/date": "*", "@googleforcreators/design-system": "*", - "@googleforcreators/element-library": "*", - "@googleforcreators/elements": "*", "@googleforcreators/fonts": "*", "@googleforcreators/i18n": "*", "@googleforcreators/media": "*", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 8ab4c495b951..9ec77b4e9173 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -44,6 +44,7 @@ "draft-js-export-html": "^1.4.1", "draft-js-import-html": "^1.4.1", "draftjs-filters": "^3.0.1", + "immutable": "^4.1.0", "prop-types": "^15.7.2" }, "devDependencies": { diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index 90582b6ad7e7..b2609421241f 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -19,13 +19,14 @@ */ import { stateToHTML } from 'draft-js-export-html'; import type { DraftInlineStyle, EditorState } from 'draft-js'; +import type { RenderConfig } from 'draft-js-export-html'; /** * Internal dependencies */ import formatters from './formatters'; -function inlineStyleFn(styles: DraftInlineStyle) { +function inlineStyleFn(styles: DraftInlineStyle): RenderConfig | null { const inlineCSS = formatters.reduce( (css, { stylesToCSS }) => ({ ...css, ...stylesToCSS(styles) }), {} diff --git a/packages/rich-text/src/getPastedBlocks.ts b/packages/rich-text/src/getPastedBlocks.ts index deacbdadd631..b1602b3c2714 100644 --- a/packages/rich-text/src/getPastedBlocks.ts +++ b/packages/rich-text/src/getPastedBlocks.ts @@ -24,13 +24,14 @@ import { SelectionState, } from 'draft-js'; import type { ContentBlock } from 'draft-js'; +import { Map } from 'immutable'; /** * Internal dependencies */ import convertStyles from './formatters/convert'; -const RENDER_MAP = Immutable.Map({ +const RENDER_MAP = Map({ unstyled: { element: 'div', // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- aliasedElements actually does exist on the type when looking at the module but the correct type is not imported. From fcdcbaaefedff665ee37ec2cdb58ff0f4ba6521d Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 13 Sep 2022 16:26:35 +0300 Subject: [PATCH 30/35] Fix setter types. --- packages/rich-text/src/customExport.ts | 8 +++----- packages/rich-text/src/htmlManipulation.ts | 4 ++-- packages/rich-text/src/useSelectionManipulation.ts | 10 ++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index b2609421241f..908017c186dd 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -18,7 +18,7 @@ * External dependencies */ import { stateToHTML } from 'draft-js-export-html'; -import type { DraftInlineStyle, EditorState } from 'draft-js'; +import type { EditorState } from 'draft-js'; import type { RenderConfig } from 'draft-js-export-html'; /** @@ -26,14 +26,14 @@ import type { RenderConfig } from 'draft-js-export-html'; */ import formatters from './formatters'; -function inlineStyleFn(styles: DraftInlineStyle): RenderConfig | null { +function inlineStyleFn(styles: Immutable.OrderedSet): RenderConfig { const inlineCSS = formatters.reduce( (css, { stylesToCSS }) => ({ ...css, ...stylesToCSS(styles) }), {} ); if (Object.keys(inlineCSS).length === 0) { - return null; + return {}; } return { @@ -49,8 +49,6 @@ function exportHTML(editorState: EditorState) { const html = stateToHTML(editorState.getCurrentContent(), { inlineStyleFn, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- See the next line for reason. - // @ts-ignore Reason: `defaultBlockTag` is optional (undefined | string), however, `undefined` means `p` so we need to use `null` here. defaultBlockTag: null, }); diff --git a/packages/rich-text/src/htmlManipulation.ts b/packages/rich-text/src/htmlManipulation.ts index 17575cf0575c..bae4c5d8ded1 100644 --- a/packages/rich-text/src/htmlManipulation.ts +++ b/packages/rich-text/src/htmlManipulation.ts @@ -74,9 +74,9 @@ export const getHTMLFormatters = () => { (aggr, { setters }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(setters).map(([key, setter]: [string, StyleSetter]) => [ + Object.entries(setters).map(([key, setter]) => [ key, - getHTMLFormatter(setter), + getHTMLFormatter(setter as StyleSetter), ]) ), }), diff --git a/packages/rich-text/src/useSelectionManipulation.ts b/packages/rich-text/src/useSelectionManipulation.ts index cbd57240503e..21710136c05d 100644 --- a/packages/rich-text/src/useSelectionManipulation.ts +++ b/packages/rich-text/src/useSelectionManipulation.ts @@ -83,12 +83,10 @@ function useSelectionManipulation( (aggr, { setters, autoFocus }) => ({ ...aggr, ...Object.fromEntries( - Object.entries(setters).map( - ([key, setter]: [string, StyleSetter]) => [ - getSetterName(key), - getSetterCallback(setter, autoFocus), - ] - ) + Object.entries(setters).map(([key, setter]) => [ + getSetterName(key), + getSetterCallback(setter as StyleSetter, autoFocus), + ]) ), }), {} From b4ac830390a79ce1a079d85e5aefcb92465ae563 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 13 Sep 2022 16:58:55 +0300 Subject: [PATCH 31/35] Fix tests. --- packages/rich-text/src/customExport.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index 908017c186dd..37187e9df83c 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -26,14 +26,16 @@ import type { RenderConfig } from 'draft-js-export-html'; */ import formatters from './formatters'; -function inlineStyleFn(styles: Immutable.OrderedSet): RenderConfig { +function inlineStyleFn( + styles: Immutable.OrderedSet +): RenderConfig | null { const inlineCSS = formatters.reduce( (css, { stylesToCSS }) => ({ ...css, ...stylesToCSS(styles) }), {} ); if (Object.keys(inlineCSS).length === 0) { - return {}; + return null; } return { @@ -49,6 +51,8 @@ function exportHTML(editorState: EditorState) { const html = stateToHTML(editorState.getCurrentContent(), { inlineStyleFn, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- See the next line for reason. + // @ts-ignore Reason: `defaultBlockTag` is optional (undefined | string), however, `undefined` means `p` so we need to use `null` here. defaultBlockTag: null, }); From 54e12c6074332e01d28e1a1f5d6b62b15c454f18 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 13 Sep 2022 11:05:05 -0700 Subject: [PATCH 32/35] Use draft-js-export-html fork with built files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Installed with `—force` --- package-lock.json | 17 ++++++++--------- packages/story-editor/package.json | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2b75d7c674b..bb9da26b8ba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22226,8 +22226,8 @@ }, "node_modules/draft-js-export-html": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/draft-js-export-html/-/draft-js-export-html-1.4.1.tgz", - "integrity": "sha512-G4VGBSalPowktIE4wp3rFbhjs+Ln9IZ2FhXeHjsZDSw0a2+h+BjKu5Enq+mcsyVb51RW740GBK8Xbf7Iic51tw==", + "resolved": "git+ssh://git@github.com/swissspidy/draft-js-export-html.git#1cf62729b3a1c9c9a06f2ec92dd2423992392d54", + "license": "ISC", "dependencies": { "draft-js-utils": "^1.4.0" }, @@ -48841,8 +48841,8 @@ "classnames": "^2.3.1", "colorthief": "^2.3.2", "draft-js": "^0.11.7", - "draft-js-export-html": "^1.4.1", - "draft-js-import-element": "git+ssh://git@github.com/swissspidy/draft-js-import-element.git", + "draft-js-export-html": "https://github.com/swissspidy/draft-js-export-html", + "draft-js-import-element": "https://github.com/swissspidy/draft-js-import-element", "draft-js-import-html": "^1.4.1", "draftjs-filters": "^3.0.1", "flagged": "^2.0.6", @@ -51408,8 +51408,8 @@ "classnames": "^2.3.1", "colorthief": "^2.3.2", "draft-js": "^0.11.7", - "draft-js-export-html": "^1.4.1", - "draft-js-import-element": "git+ssh://git@github.com/swissspidy/draft-js-import-element.git", + "draft-js-export-html": "https://github.com/swissspidy/draft-js-export-html", + "draft-js-import-element": "https://github.com/swissspidy/draft-js-import-element", "draft-js-import-html": "^1.4.1", "draftjs-filters": "^3.0.1", "flagged": "^2.0.6", @@ -66441,9 +66441,8 @@ } }, "draft-js-export-html": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/draft-js-export-html/-/draft-js-export-html-1.4.1.tgz", - "integrity": "sha512-G4VGBSalPowktIE4wp3rFbhjs+Ln9IZ2FhXeHjsZDSw0a2+h+BjKu5Enq+mcsyVb51RW740GBK8Xbf7Iic51tw==", + "version": "git+ssh://git@github.com/swissspidy/draft-js-export-html.git#1cf62729b3a1c9c9a06f2ec92dd2423992392d54", + "from": "draft-js-export-html@^1.4.1", "requires": { "draft-js-utils": "^1.4.0" } diff --git a/packages/story-editor/package.json b/packages/story-editor/package.json index 52adef7523de..0b598b2f7a24 100644 --- a/packages/story-editor/package.json +++ b/packages/story-editor/package.json @@ -67,7 +67,7 @@ "classnames": "^2.3.1", "colorthief": "^2.3.2", "draft-js": "^0.11.7", - "draft-js-export-html": "^1.4.1", + "draft-js-export-html": "https://github.com/swissspidy/draft-js-export-html", "draft-js-import-element": "https://github.com/swissspidy/draft-js-import-element", "draft-js-import-html": "^1.4.1", "draftjs-filters": "^3.0.1", From 7a892dfdfd3729fc8af2da3efdea4d82dd26ae58 Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 13 Sep 2022 21:08:17 +0300 Subject: [PATCH 33/35] Adjust function overload for useRichText --- packages/rich-text/src/useRichText.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/rich-text/src/useRichText.ts b/packages/rich-text/src/useRichText.ts index 559ecc2f66b7..9e7117cc69c8 100644 --- a/packages/rich-text/src/useRichText.ts +++ b/packages/rich-text/src/useRichText.ts @@ -25,10 +25,9 @@ import Context from './context'; import type { State } from './types'; function useRichText(): State; -function useRichText( - selector?: (state: State | null) => Partial | State -) { - return useContextSelector(Context, selector ?? identity); +function useRichText(selector: (state: State) => T): T; +function useRichText(selector: (state: State) => T | State = identity) { + return useContextSelector(Context, selector); } export default useRichText; From 68dcde80657f7754ef36da233aabb9c326240c14 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 13 Sep 2022 11:47:08 -0700 Subject: [PATCH 34/35] Update lock file --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb9da26b8ba5..992506ad6e99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22226,7 +22226,7 @@ }, "node_modules/draft-js-export-html": { "version": "1.4.1", - "resolved": "git+ssh://git@github.com/swissspidy/draft-js-export-html.git#1cf62729b3a1c9c9a06f2ec92dd2423992392d54", + "resolved": "git+ssh://git@github.com/swissspidy/draft-js-export-html.git#366642f1c6e5090f24f7a66a357328115e4a6a62", "license": "ISC", "dependencies": { "draft-js-utils": "^1.4.0" @@ -66441,7 +66441,7 @@ } }, "draft-js-export-html": { - "version": "git+ssh://git@github.com/swissspidy/draft-js-export-html.git#1cf62729b3a1c9c9a06f2ec92dd2423992392d54", + "version": "git+ssh://git@github.com/swissspidy/draft-js-export-html.git#366642f1c6e5090f24f7a66a357328115e4a6a62", "from": "draft-js-export-html@^1.4.1", "requires": { "draft-js-utils": "^1.4.0" From b4fc5e2d4918779d4b13abdb8503c4521027f4dc Mon Sep 17 00:00:00 2001 From: Miina Sikk Date: Tue, 13 Sep 2022 22:04:31 +0300 Subject: [PATCH 35/35] Remove ts-ignore --- packages/rich-text/src/customExport.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/rich-text/src/customExport.ts b/packages/rich-text/src/customExport.ts index 37187e9df83c..5c38d59f3fd2 100644 --- a/packages/rich-text/src/customExport.ts +++ b/packages/rich-text/src/customExport.ts @@ -51,8 +51,6 @@ function exportHTML(editorState: EditorState) { const html = stateToHTML(editorState.getCurrentContent(), { inlineStyleFn, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- See the next line for reason. - // @ts-ignore Reason: `defaultBlockTag` is optional (undefined | string), however, `undefined` means `p` so we need to use `null` here. defaultBlockTag: null, });