From db0360aa82b97ef1aa6ffd1d4085c3f469064231 Mon Sep 17 00:00:00 2001 From: Madhuri Sandbhor Date: Tue, 31 Oct 2023 10:21:44 +0530 Subject: [PATCH] feat(blocks): add keyboard shortcuts for modifiers (#18581) * feat: added keyboard shortcuts for modifiers * fix: eventKeys are added per modifier, updated url validation with URL() * fix: replaced eventKey with isValidEventKey() in modifier store * refactor: moved insertData to withLinks plugin --- .../BlocksEditor/BlocksInput/index.js | 16 +++++++++++++ .../BlocksEditor/hooks/useModifiersStore.js | 6 +++++ .../components/BlocksEditor/index.js | 7 +++++- .../BlocksEditor/plugins/withLinks.js | 23 ++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js index dfacdaf569..c476a43846 100644 --- a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +++ b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js @@ -74,6 +74,21 @@ const BlocksInput = ({ disabled, placeholder }) => { } }; + /** + * Modifier keyboard shortcuts + */ + const handleKeyboardShortcuts = (event) => { + const isCtrlOrCmd = event.metaKey || event.ctrlKey; + + if (isCtrlOrCmd) { + Object.values(modifiers).forEach((value) => { + if (value.isValidEventKey(event)) { + value.handleToggle(); + } + }); + } + }; + const handleKeyDown = (event) => { if (event.key === 'Enter') { event.preventDefault(); @@ -82,6 +97,7 @@ const BlocksInput = ({ disabled, placeholder }) => { if (event.key === 'Backspace') { handleBackspaceEvent(event); } + handleKeyboardShortcuts(event); }; /** diff --git a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js index 6d95bdcde1..ea89b11e92 100644 --- a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +++ b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js @@ -44,6 +44,7 @@ const InlineCode = styled.code` * @returns {{ * [key: string]: { * icon: IconComponent, + * isValidEventKey: (event: Event) => boolean, * label: {id: string, defaultMessage: string}, * checkIsActive: () => boolean, * handleToggle: () => void, @@ -82,6 +83,7 @@ export function useModifiersStore() { return { bold: { icon: Bold, + isValidEventKey: (event) => event.key === 'b', label: { id: 'components.Blocks.modifiers.bold', defaultMessage: 'Bold' }, checkIsActive: () => baseCheckIsActive('bold'), handleToggle: () => baseHandleToggle('bold'), @@ -89,6 +91,7 @@ export function useModifiersStore() { }, italic: { icon: Italic, + isValidEventKey: (event) => event.key === 'i', label: { id: 'components.Blocks.modifiers.italic', defaultMessage: 'Italic' }, checkIsActive: () => baseCheckIsActive('italic'), handleToggle: () => baseHandleToggle('italic'), @@ -96,6 +99,7 @@ export function useModifiersStore() { }, underline: { icon: Underline, + isValidEventKey: (event) => event.key === 'u', label: { id: 'components.Blocks.modifiers.underline', defaultMessage: 'Underline' }, checkIsActive: () => baseCheckIsActive('underline'), handleToggle: () => baseHandleToggle('underline'), @@ -103,6 +107,7 @@ export function useModifiersStore() { }, strikethrough: { icon: StrikeThrough, + isValidEventKey: (event) => event.key === 'S' && event.shiftKey, label: { id: 'components.Blocks.modifiers.strikethrough', defaultMessage: 'Strikethrough' }, checkIsActive: () => baseCheckIsActive('strikethrough'), handleToggle: () => baseHandleToggle('strikethrough'), @@ -110,6 +115,7 @@ export function useModifiersStore() { }, code: { icon: Code, + isValidEventKey: (event) => event.key === 'e', label: { id: 'components.Blocks.modifiers.code', defaultMessage: 'Code' }, checkIsActive: () => baseCheckIsActive('code'), handleToggle: () => baseHandleToggle('code'), diff --git a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/index.js b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/index.js index 747e729da3..a5d309042d 100644 --- a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/index.js +++ b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/index.js @@ -85,6 +85,11 @@ function useResetKey(value) { return { key, incrementSlateUpdatesCount: () => (slateUpdatesCount.current += 1) }; } +const pipe = + (...fns) => + (value) => + fns.reduce((prev, fn) => fn(prev), value); + const BlocksEditor = React.forwardRef( ( { intlLabel, labelAction, name, disabled, required, error, value, onChange, placeholder, hint }, @@ -92,7 +97,7 @@ const BlocksEditor = React.forwardRef( ) => { const { formatMessage } = useIntl(); const [editor] = React.useState(() => - withReact(withStrapiSchema(withLinks(withImages(withHistory(createEditor()))))) + pipe(withHistory, withImages, withStrapiSchema, withReact, withLinks)(createEditor()) ); const label = intlLabel.id diff --git a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/plugins/withLinks.js b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/plugins/withLinks.js index 6fc93774b1..03bc97a68e 100644 --- a/packages/core/admin/admin/src/content-manager/components/BlocksEditor/plugins/withLinks.js +++ b/packages/core/admin/admin/src/content-manager/components/BlocksEditor/plugins/withLinks.js @@ -1,7 +1,9 @@ import { Path, Transforms, Range, Point, Editor } from 'slate'; +import { insertLink } from '../utils/links'; + const withLinks = (editor) => { - const { isInline, apply, insertText } = editor; + const { isInline, apply, insertText, insertData } = editor; // Links are inline elements, so we need to override the isInline method for slate editor.isInline = (element) => { @@ -55,6 +57,25 @@ const withLinks = (editor) => { insertText(text); }; + // Add data as a clickable link if its a valid URL + editor.insertData = (data) => { + const pastedText = data.getData('text/plain'); + + if (pastedText) { + try { + // eslint-disable-next-line no-new + new URL(pastedText); + insertLink(editor, { url: pastedText }); + + return; + } catch (error) { + // continue normal data insertion + } + } + + insertData(data); + }; + return editor; };