From 04b588dcc5c70f82f9764d86c2acdeab588c0632 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Sun, 30 Jul 2023 22:55:51 +0530 Subject: [PATCH 1/6] add faux cursor --- src/index.js | 106 ++++++++++++++++++++++++++++- src/insert-special-characters.scss | 14 ++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 551ec8d..3372321 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,14 @@ import { registerFormatType, toggleFormat, insert } from '@wordpress/rich-text'; -import { Fragment } from '@wordpress/element'; -import { BlockControls, RichTextShortcut } from '@wordpress/block-editor'; +import { Fragment, useEffect, useState } from '@wordpress/element'; +import { + BlockControls, + RichTextShortcut, + store as blockEditorStore +} from '@wordpress/block-editor'; import { Popover, ToolbarButton, ToolbarGroup } from '@wordpress/components'; import { applyFilters } from '@wordpress/hooks'; import { displayShortcut } from '@wordpress/keycodes'; +import { select, useSelect, dispatch, createReduxStore, register } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { CharacterMap } from 'react-character-map'; @@ -24,6 +29,55 @@ const type = `special-characters/${ name }`; let anchorRange; let anchorRect; +const DEFAULT_STATE = { + clientId: '', + originalContent: '', +}; + +const caretDataStore = createReduxStore( 'caret-data-store', { + reducer( state = DEFAULT_STATE, action ) { + switch ( action.type ) { + case 'SET_CLIENT_ID': + return { + ...state, + clientId: action.clientId, + }; + + case 'SET_ORIGINAL_CONTENT': + return { + ...state, + originalContent: action.originalContent, + }; + } + + return state; + }, + actions: { + setClientId( clientId ) { + return { + type: 'SET_CLIENT_ID', + clientId, + }; + }, + setOriginalContent( originalContent ) { + return { + type: 'SET_ORIGINAL_CONTENT', + originalContent, + }; + }, + }, + selectors: { + getClientId( state ) { + return state.clientId; + }, + getOriginalContent( state ) { + return state.originalContent; + }, + }, +} ); + +register( caretDataStore ); + /** * Register the "Format Type" to create the character inserter. */ @@ -43,6 +97,52 @@ registerFormatType( type, { * @param {HTMLElement} props.contentRef The editable element. */ edit( { isActive, value, onChange, contentRef } ) { + const [ inActiveBySelection, setInactiveBySelection ] = useState( false ); + const { start, end, selectedBlock } = useSelect( ( select ) => { + const start = select( blockEditorStore ).getSelectionStart(); + const end = select( blockEditorStore ).getSelectionEnd(); + const selectedBlock = select( blockEditorStore ).getSelectedBlock(); + + return { + start: start.offset, + end: end.offset, + selectedBlock, + } + } ); + + useEffect( () => { + const content = selectedBlock.attributes.content; + dispatch( caretDataStore ).setClientId( selectedBlock.clientId ); + dispatch( caretDataStore ).setOriginalContent( content ); + const preBreak = content.substring( 0, start ); + const postBreak = content.substring( start ); + const postBreakFirstChar = postBreak.substring( 0, 1 ); + const postBreakWithoutFirstChar = postBreak.substring( 1 ); + const charWrapper = `${ postBreakFirstChar }`; + const finalContent = preBreak + charWrapper + postBreakWithoutFirstChar; + + if ( ( isActive || inActiveBySelection ) && start - end === 0 ) { + contentRef.current.innerHTML = finalContent; + } + + return () => { + const storedClientId = select( caretDataStore ).getClientId(); + + if ( selectedBlock.clientId !== storedClientId ) { + return; + } + + const backupUpContent = select( caretDataStore ).getOriginalContent(); + + if ( backupUpContent ) { + if ( inActiveBySelection ) { + } + contentRef.current.innerHTML = backupUpContent; + dispatch( caretDataStore ).setOriginalContent( '' ); + } + } + }, [ isActive, inActiveBySelection ] ); + const onToggle = () => { const selection = contentRef.current.ownerDocument.getSelection(); @@ -90,6 +190,8 @@ registerFormatType( type, { text: char.char, }; + setInactiveBySelection( true ); + onChange( insert( value, diff --git a/src/insert-special-characters.scss b/src/insert-special-characters.scss index e28b820..196defc 100644 --- a/src/insert-special-characters.scss +++ b/src/insert-special-characters.scss @@ -1,5 +1,19 @@ $grid-border-color: #c8c8c8; +.insert-special-character__faux-caret { + border-left: 1px solid #000; + animation: 1s caret-blink step-end infinite; +} + +@keyframes caret-blink { + from, to { + border-color: black; + } + 50% { + border-color: transparent; + } +} + .character-map-popover { .components-popover__content { border: 1px solid #1e1e1e From 8ae03ca0c0de9303f361b7a1439f8c9473810530 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 31 Jul 2023 16:52:59 +0530 Subject: [PATCH 2/6] fix eslint errors --- src/index.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/index.js b/src/index.js index 3372321..aa3a0d7 100644 --- a/src/index.js +++ b/src/index.js @@ -3,12 +3,18 @@ import { Fragment, useEffect, useState } from '@wordpress/element'; import { BlockControls, RichTextShortcut, - store as blockEditorStore + store as blockEditorStore, } from '@wordpress/block-editor'; import { Popover, ToolbarButton, ToolbarGroup } from '@wordpress/components'; import { applyFilters } from '@wordpress/hooks'; import { displayShortcut } from '@wordpress/keycodes'; -import { select, useSelect, dispatch, createReduxStore, register } from '@wordpress/data'; +import { + select, + useSelect, + dispatch, + createReduxStore, + register, +} from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { CharacterMap } from 'react-character-map'; @@ -97,17 +103,14 @@ registerFormatType( type, { * @param {HTMLElement} props.contentRef The editable element. */ edit( { isActive, value, onChange, contentRef } ) { - const [ inActiveBySelection, setInactiveBySelection ] = useState( false ); - const { start, end, selectedBlock } = useSelect( ( select ) => { - const start = select( blockEditorStore ).getSelectionStart(); - const end = select( blockEditorStore ).getSelectionEnd(); - const selectedBlock = select( blockEditorStore ).getSelectedBlock(); - + const [ inActiveBySelection, setInactiveBySelection ] = + useState( false ); + const { start, end, selectedBlock } = useSelect( ( __select ) => { return { - start: start.offset, - end: end.offset, - selectedBlock, - } + start: __select( blockEditorStore ).getSelectionStart().offset, + end: __select( blockEditorStore ).getSelectionEnd().offset, + selectedBlock: __select( blockEditorStore ).getSelectedBlock(), + }; } ); useEffect( () => { @@ -119,7 +122,8 @@ registerFormatType( type, { const postBreakFirstChar = postBreak.substring( 0, 1 ); const postBreakWithoutFirstChar = postBreak.substring( 1 ); const charWrapper = `${ postBreakFirstChar }`; - const finalContent = preBreak + charWrapper + postBreakWithoutFirstChar; + const finalContent = + preBreak + charWrapper + postBreakWithoutFirstChar; if ( ( isActive || inActiveBySelection ) && start - end === 0 ) { contentRef.current.innerHTML = finalContent; @@ -132,7 +136,8 @@ registerFormatType( type, { return; } - const backupUpContent = select( caretDataStore ).getOriginalContent(); + const backupUpContent = + select( caretDataStore ).getOriginalContent(); if ( backupUpContent ) { if ( inActiveBySelection ) { @@ -140,7 +145,7 @@ registerFormatType( type, { contentRef.current.innerHTML = backupUpContent; dispatch( caretDataStore ).setOriginalContent( '' ); } - } + }; }, [ isActive, inActiveBySelection ] ); const onToggle = () => { From 152392cbcc26af171cd65f4db031680b8877ce26 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Mon, 31 Jul 2023 16:54:05 +0530 Subject: [PATCH 3/6] fix eslint errors --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index aa3a0d7..d07a13c 100644 --- a/src/index.js +++ b/src/index.js @@ -102,7 +102,7 @@ registerFormatType( type, { * @param {Function} props.onChange Event handler to detect range selection. * @param {HTMLElement} props.contentRef The editable element. */ - edit( { isActive, value, onChange, contentRef } ) { + edit: function Edit( { isActive, value, onChange, contentRef } ) { const [ inActiveBySelection, setInactiveBySelection ] = useState( false ); const { start, end, selectedBlock } = useSelect( ( __select ) => { From 92b273367a5a4b88d3b8f2e3ebd84ab57d30f698 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 4 Aug 2023 19:03:27 +0530 Subject: [PATCH 4/6] add support for text faux selection --- src/index.js | 24 +++++++++++++----------- src/insert-special-characters.scss | 4 ++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/index.js b/src/index.js index d07a13c..3a8e936 100644 --- a/src/index.js +++ b/src/index.js @@ -114,19 +114,23 @@ registerFormatType( type, { } ); useEffect( () => { - const content = selectedBlock.attributes.content; + let content = selectedBlock.attributes.content.replace( /|<\/insertspecialcharacters>/g, '' ); dispatch( caretDataStore ).setClientId( selectedBlock.clientId ); - dispatch( caretDataStore ).setOriginalContent( content ); + const preBreak = content.substring( 0, start ); - const postBreak = content.substring( start ); - const postBreakFirstChar = postBreak.substring( 0, 1 ); - const postBreakWithoutFirstChar = postBreak.substring( 1 ); - const charWrapper = `${ postBreakFirstChar }`; - const finalContent = - preBreak + charWrapper + postBreakWithoutFirstChar; if ( ( isActive || inActiveBySelection ) && start - end === 0 ) { - contentRef.current.innerHTML = finalContent; + dispatch( caretDataStore ).setOriginalContent( content ); + const postBreak = content.substring( start ); + const postBreakFirstChar = postBreak.substring( 0, 1 ); + const postBreakWithoutFirstChar = postBreak.substring( 1 ); + contentRef.current.innerHTML = preBreak + `${ postBreakFirstChar }` + postBreakWithoutFirstChar; + } else if ( ( isActive || inActiveBySelection ) && end - start > 0 ) { + dispatch( caretDataStore ).setOriginalContent( content ); + const selectedText = content.substring( start, end ); + const preSelectText = content.substring( 0, start ); + const postSelectText = content.substring( end ); + contentRef.current.innerHTML = preSelectText + `${ selectedText }` + postSelectText; } return () => { @@ -140,8 +144,6 @@ registerFormatType( type, { select( caretDataStore ).getOriginalContent(); if ( backupUpContent ) { - if ( inActiveBySelection ) { - } contentRef.current.innerHTML = backupUpContent; dispatch( caretDataStore ).setOriginalContent( '' ); } diff --git a/src/insert-special-characters.scss b/src/insert-special-characters.scss index 196defc..e75dd67 100644 --- a/src/insert-special-characters.scss +++ b/src/insert-special-characters.scss @@ -5,6 +5,10 @@ $grid-border-color: #c8c8c8; animation: 1s caret-blink step-end infinite; } +.insert-special-character__faux-selection { + background-color: rgba(0, 0, 0, 0.2); +} + @keyframes caret-blink { from, to { border-color: black; From 112fe4c9921b541c49c89914001586a9a074ba8a Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 4 Aug 2023 19:07:14 +0530 Subject: [PATCH 5/6] fix eslint errors --- src/index.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 3a8e936..252cca3 100644 --- a/src/index.js +++ b/src/index.js @@ -114,9 +114,12 @@ registerFormatType( type, { } ); useEffect( () => { - let content = selectedBlock.attributes.content.replace( /|<\/insertspecialcharacters>/g, '' ); + const content = selectedBlock.attributes.content.replace( + /|<\/insertspecialcharacters>/g, + '' + ); dispatch( caretDataStore ).setClientId( selectedBlock.clientId ); - + const preBreak = content.substring( 0, start ); if ( ( isActive || inActiveBySelection ) && start - end === 0 ) { @@ -124,13 +127,22 @@ registerFormatType( type, { const postBreak = content.substring( start ); const postBreakFirstChar = postBreak.substring( 0, 1 ); const postBreakWithoutFirstChar = postBreak.substring( 1 ); - contentRef.current.innerHTML = preBreak + `${ postBreakFirstChar }` + postBreakWithoutFirstChar; - } else if ( ( isActive || inActiveBySelection ) && end - start > 0 ) { + contentRef.current.innerHTML = + preBreak + + `${ postBreakFirstChar }` + + postBreakWithoutFirstChar; + } else if ( + ( isActive || inActiveBySelection ) && + end - start > 0 + ) { dispatch( caretDataStore ).setOriginalContent( content ); const selectedText = content.substring( start, end ); const preSelectText = content.substring( 0, start ); const postSelectText = content.substring( end ); - contentRef.current.innerHTML = preSelectText + `${ selectedText }` + postSelectText; + contentRef.current.innerHTML = + preSelectText + + `${ selectedText }` + + postSelectText; } return () => { From dd2dc9448e0ec1c3998c0afc826344771744e154 Mon Sep 17 00:00:00 2001 From: Siddharth Thevaril Date: Fri, 4 Aug 2023 19:12:51 +0530 Subject: [PATCH 6/6] add safety check --- src/index.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/index.js b/src/index.js index 252cca3..70055bd 100644 --- a/src/index.js +++ b/src/index.js @@ -127,10 +127,13 @@ registerFormatType( type, { const postBreak = content.substring( start ); const postBreakFirstChar = postBreak.substring( 0, 1 ); const postBreakWithoutFirstChar = postBreak.substring( 1 ); - contentRef.current.innerHTML = - preBreak + - `${ postBreakFirstChar }` + - postBreakWithoutFirstChar; + + if ( contentRef && contentRef.current ) { + contentRef.current.innerHTML = + preBreak + + `${ postBreakFirstChar }` + + postBreakWithoutFirstChar; + } } else if ( ( isActive || inActiveBySelection ) && end - start > 0 @@ -139,10 +142,13 @@ registerFormatType( type, { const selectedText = content.substring( start, end ); const preSelectText = content.substring( 0, start ); const postSelectText = content.substring( end ); - contentRef.current.innerHTML = - preSelectText + - `${ selectedText }` + - postSelectText; + + if ( contentRef && contentRef.current ) { + contentRef.current.innerHTML = + preSelectText + + `${ selectedText }` + + postSelectText; + } } return () => {