diff --git a/package.json b/package.json index 658ec9f527..50e948f3b7 100644 --- a/package.json +++ b/package.json @@ -76,9 +76,9 @@ "remark-gfm": "^3.0.1", "remark-parse": "^10.0.1", "remark-slate": "^1.8.6", - "slate": "^0.78.0", + "slate": "^0.94.1", "slate-history": "^0.66.0", - "slate-react": "^0.77.4", + "slate-react": "^0.98.3", "slugify": "^1.6.5", "tsconfig-paths": "^4.1.2", "unified": "^10.1.2", diff --git a/src/features/callAssignments/models/CallerInstructionsModel.ts b/src/features/callAssignments/models/CallerInstructionsModel.ts index d3c33c7471..26bbf238d9 100644 --- a/src/features/callAssignments/models/CallerInstructionsModel.ts +++ b/src/features/callAssignments/models/CallerInstructionsModel.ts @@ -50,9 +50,10 @@ export default class CallerInstructionsModel extends ModelBase { return false; } - const lsInstructions = localStorage.getItem(this._key) || ''; + const lsInstructions = localStorage.getItem(this._key)?.trim() || ''; + const dataInstructions = data.instructions.trim(); - return data.instructions != lsInstructions; + return dataInstructions != lsInstructions; } get isSaving(): boolean { diff --git a/src/zui/ZUITextEditor/TextElement.tsx b/src/zui/ZUITextEditor/TextElement.tsx index 7cc7952074..013c08cf93 100644 --- a/src/zui/ZUITextEditor/TextElement.tsx +++ b/src/zui/ZUITextEditor/TextElement.tsx @@ -22,7 +22,7 @@ const TextElement: React.FunctionComponent = ({ if (element.type === 'bulleted-list') { return ( ); } diff --git a/src/zui/ZUITextEditor/helpers.ts b/src/zui/ZUITextEditor/helpers.ts index ff6cf11897..9a44d6e401 100644 --- a/src/zui/ZUITextEditor/helpers.ts +++ b/src/zui/ZUITextEditor/helpers.ts @@ -1,14 +1,16 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import isUrl from 'is-url'; -import { BlockType, LeafType, NodeTypes, serialize } from 'remark-slate'; import { + Ancestor, Descendant, Editor, + NodeEntry, Range, Element as SlateElement, Text, Transforms, } from 'slate'; +import { BlockType, LeafType, NodeTypes, serialize } from 'remark-slate'; import isHotkey, { isKeyHotkey } from 'is-hotkey'; const LIST_TYPES = ['numbered-list', 'bulleted-list']; @@ -225,6 +227,23 @@ const convertSlateToRemarked = ( return convertedChildren; }; +const shouldBeRemoved = (node: NodeEntry): boolean => { + if (node && Object.prototype.hasOwnProperty.call(node[0], 'children')) { + if ( + 'children' in node[0].children[0] && + Object.prototype.hasOwnProperty.call(node[0].children[0], 'children') + ) { + if ( + node[0].children[0].children[0] && + node[0].children[0].children[0].text === '' + ) { + return true; + } + } + } + return false; +}; + const slateToMarkdown = (slateArray: Descendant[]): string => { const nodeTypes = { block_quote: 'block-quote', @@ -269,6 +288,7 @@ export { isMarkActive, keyDownHandler, LIST_TYPES, + shouldBeRemoved, slateToMarkdown, withInlines, unwrapLink, diff --git a/src/zui/ZUITextEditor/index.tsx b/src/zui/ZUITextEditor/index.tsx index a5a5f7e4d8..e5093a1f17 100644 --- a/src/zui/ZUITextEditor/index.tsx +++ b/src/zui/ZUITextEditor/index.tsx @@ -4,7 +4,14 @@ import { makeStyles } from '@mui/styles'; import { markdownToSlate } from './utils/markdownToSlate'; import { withHistory } from 'slate-history'; import { Box, ClickAwayListener, Collapse } from '@mui/material'; -import { createEditor, Descendant, Editor, Transforms } from 'slate'; +import { + createEditor, + deleteBackward, + Descendant, + Editor, + Node, + Transforms, +} from 'slate'; import { Editable, ReactEditor, @@ -13,7 +20,13 @@ import { Slate, withReact, } from 'slate-react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import './types'; import { FileUpload } from 'features/files/hooks/useFileUploads'; @@ -21,7 +34,12 @@ import TextElement from './TextElement'; import theme from 'theme'; import Toolbar from './Toolbar'; import { ZetkinFileUploadChip } from 'zui/ZUIFileChip'; -import { keyDownHandler, slateToMarkdown, withInlines } from './helpers'; +import { + keyDownHandler, + shouldBeRemoved, + slateToMarkdown, + withInlines, +} from './helpers'; const emptySlate = [ { @@ -86,6 +104,34 @@ const ZUITextEditor: React.FunctionComponent = ({ () => withInlines(withHistory(withReact(createEditor()))), [] ); + + //fixes deleting the missing bullet point in empty list in root + editor.deleteBackward = (...args) => { + deleteBackward(editor, ...args); + + const bulletListNode = Editor.above(editor, { + match: (n: Node) => + 'type' in n && + n.type === 'list-item' && + Object.prototype.hasOwnProperty.call(n, 'children'), + }); + + if (bulletListNode) { + if (shouldBeRemoved(bulletListNode)) { + Transforms.setNodes( + editor, + { type: 'paragraph' }, + { + at: bulletListNode[1], + match: (n) => 'type' in n && n.type === 'list-item', + } + ); + } + } + return editor; + }; + + const markdownValue = useRef(''); const [initialValueSlate, setInitialValueSlate] = useState< Descendant[] | null >(null); @@ -93,8 +139,10 @@ const ZUITextEditor: React.FunctionComponent = ({ useEffect(() => { (async () => { if (initialValue) { - const slate = await markdownToSlate(initialValue as string); - setInitialValueSlate(slate as Descendant[]); + if (initialValue !== markdownValue.current) { + const slate = await markdownToSlate(initialValue); + setInitialValueSlate(slate as Descendant[]); + } } else { setInitialValueSlate(emptySlate); } @@ -123,8 +171,11 @@ const ZUITextEditor: React.FunctionComponent = ({ {initialValueSlate && ( onChange(slateToMarkdown(slateArray))} - value={initialValueSlate} + initialValue={initialValueSlate} + onChange={(slateArray) => { + markdownValue.current = slateToMarkdown(slateArray); + onChange(markdownValue.current); + }} > = ({ renderElement={renderElement} renderLeaf={renderLeaf} spellCheck - style={{ overflowY: 'scroll' }} + style={{ + outline: 'none', + overflowY: 'scroll', + }} /> diff --git a/yarn.lock b/yarn.lock index b26b8a303a..d242edf3f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2221,6 +2221,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@juggle/resize-observer@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" + integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== + "@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" @@ -4362,7 +4367,12 @@ dependencies: "@types/geojson" "*" -"@types/lodash@^4.14.149", "@types/lodash@^4.14.167": +"@types/lodash@^4.14.149": + version "4.14.198" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" + integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== + +"@types/lodash@^4.14.167": version "4.14.182" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== @@ -6582,10 +6592,10 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" -compute-scroll-into-view@^1.0.17: - version "1.0.17" - resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" - integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== +compute-scroll-into-view@^1.0.20: + version "1.0.20" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43" + integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg== concat-map@0.0.1: version "0.0.1" @@ -14275,11 +14285,11 @@ schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: ajv-keywords "^3.5.2" scroll-into-view-if-needed@^2.2.20: - version "2.2.29" - resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz#551791a84b7e2287706511f8c68161e4990ab885" - integrity sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg== + version "2.2.31" + resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587" + integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA== dependencies: - compute-scroll-into-view "^1.0.17" + compute-scroll-into-view "^1.0.20" "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.7.1" @@ -14488,11 +14498,12 @@ slate-history@^0.66.0: dependencies: is-plain-object "^5.0.0" -slate-react@^0.77.4: - version "0.77.4" - resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.77.4.tgz#b2532b57e47c4e088b1583ed8befc6e238a15ad5" - integrity sha512-e3gYuEhjbEX4IhydC4kWZgcvH5fgJkJMuu9paarO6gdW5vRRSWnys/EbOs5ALp4e9NWr5u1XzQkJnbpyW5AqBA== +slate-react@^0.98.3: + version "0.98.3" + resolved "https://registry.yarnpkg.com/slate-react/-/slate-react-0.98.3.tgz#5090d269d69186f3ec2a6b5862d2645f01772eda" + integrity sha512-p1BnF9eRyRM0i5hkgOb11KgmpWLQm9Zyp6jVkOAj5fPdIGheKhg48Z7aWKrayeJ4nmRyi/NjRZz/io5hQcphmw== dependencies: + "@juggle/resize-observer" "^3.4.0" "@types/is-hotkey" "^0.1.1" "@types/lodash" "^4.14.149" direction "^1.0.3" @@ -14502,10 +14513,10 @@ slate-react@^0.77.4: scroll-into-view-if-needed "^2.2.20" tiny-invariant "1.0.6" -slate@^0.78.0: - version "0.78.0" - resolved "https://registry.yarnpkg.com/slate/-/slate-0.78.0.tgz#cd0328d22b0a99c543987d2a2dd30903bb950ee9" - integrity sha512-VwQ0RafT3JPf9SFrXI02Dh3S4Iz9en7d1nn50C/LJjjqjfgv+a2ORbgWMdYjhycPYldaxJwcI3OpP9D1g4SXEg== +slate@^0.94.1: + version "0.94.1" + resolved "https://registry.yarnpkg.com/slate/-/slate-0.94.1.tgz#13b0ba7d0a7eeb0ec89a87598e9111cbbd685696" + integrity sha512-GH/yizXr1ceBoZ9P9uebIaHe3dC/g6Plpf9nlUwnvoyf6V1UOYrRwkabtOCd3ZfIGxomY4P7lfgLr7FPH8/BKA== dependencies: immer "^9.0.6" is-plain-object "^5.0.0"