From 29fef387ecdeb006d6dbfcbf3534199d52115243 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Thu, 2 Mar 2023 08:32:22 +0900 Subject: [PATCH 01/28] docs(search): add as a comment the reference on why we use useMemo in context save time to search in Notion notes or on the web --- src/wrappers/PlaceIdContext.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wrappers/PlaceIdContext.js b/src/wrappers/PlaceIdContext.js index a55444ca..db344451 100644 --- a/src/wrappers/PlaceIdContext.js +++ b/src/wrappers/PlaceIdContext.js @@ -4,6 +4,6 @@ export const PlaceIdContext = createContext(); export function PlaceIdProvider({initialPlaceId = '', ...props}) { const [placeId, setPlaceId] = useState(initialPlaceId); - const value = useMemo(() => [placeId, setPlaceId], [placeId]); + const value = useMemo(() => [placeId, setPlaceId], [placeId]); // see https://github.com/kentcdodds/react-performance/blob/main/src/exercise/05.md return ; } From 6c9b9e267e2cb2d8022d5b785838f76b4f94fa95 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sat, 4 Mar 2023 07:26:33 +0900 Subject: [PATCH 02/28] refactor(savedplaces): separate userData from the ui state prepare for merging the ui states in SearchedPlace and SavedPlace components --- src/components/Places.js | 6 +++--- src/components/SavedPlaces.js | 8 ++++---- src/components/SearchedPlace.js | 5 ++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/Places.js b/src/components/Places.js index f3590e2c..b8512dd0 100644 --- a/src/components/Places.js +++ b/src/components/Places.js @@ -1,4 +1,4 @@ -import {createContext, useContext} from 'react'; +import {createContext, useContext, useState} from 'react'; import PropTypes from 'prop-types'; import {useStateObject} from 'src/hooks/useStateObject'; @@ -7,9 +7,9 @@ const PlacesContext = createContext(); PlacesContext.displayName = 'PlacesContext'; // for React DevTools to show export function Places({children, placeData}) { + const [userData, setUserData] = useState(placeData); const [places, setPlaces] = useStateObject({ ui: null, - userData: placeData, selectedPlace: null, ripple: { diameter: null, @@ -19,7 +19,7 @@ export function Places({children, placeData}) { }); return ( - + {children} ); diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index c9118d36..4c7d756d 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -36,8 +36,8 @@ const autolinker = new Autolinker({ }); // https://github.com/gregjacobs/Autolinker.js#usage export const SavedPlaces = ({mapObject}) => { - const {places, setPlaces} = usePlaces(); - const {ui, userData, selectedPlace, ripple} = places; + const {places, setPlaces, userData, setUserData} = usePlaces(); + const {ui, selectedPlace, ripple} = places; const nightMode = useContext(NightModeContext); @@ -231,9 +231,9 @@ export const SavedPlaces = ({mapObject}) => { ...userData[selectedPlaceIndex].properties, ...jsonResponse.properties, }; + setUserData(newUserData); setPlaces({ ui: 'open', - userData: newUserData, }); } else { throw new Error('PUT request to /api/places has failed.'); @@ -262,8 +262,8 @@ export const SavedPlaces = ({mapObject}) => { setPlaces({ ui: null, selectedPlace: null, - userData: newUserData, }); + setUserData(newUserData); // this must be executed after setPlaces(); otherwise the entire component re-renders and the `if(selectedPlace)` block will be run, returning an error. } else { throw new Error('DELETE request to /api/places has failed.'); } diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index b5d8c981..179d37ae 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -23,8 +23,7 @@ export const SearchedPlace = ({mapObject}) => { const [placeId] = useContext(PlaceIdContext); const nightMode = useContext(NightModeContext); - const {places, setPlaces} = usePlaces(); - const {userData} = places; + const {setPlaces, userData, setUserData} = usePlaces(); const [state, setState] = useStateObject({ status: 'initial', @@ -222,10 +221,10 @@ export const SearchedPlace = ({mapObject}) => { if (response.ok) { const jsonResponse = await response.json(); marker.current.setMap(null); // remove the searched place marker + setUserData([...userData, jsonResponse]); setState({status: 'saved'}); setPlaces({ ui: 'open', - userData: [...userData, jsonResponse], selectedPlace: { id: jsonResponse.id, coordinates: jsonResponse.coordinates, From 400bb9bca3382102a712c4d0fcaab6b48b49cb20 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sat, 4 Mar 2023 08:16:22 +0900 Subject: [PATCH 03/28] fix(search): reset placeId (searched place ID) once the place is saved fix #427 --- cypress/e2e/saved-places.cy.js | 35 +++++++++++++++++++++++++++++++++ src/components/SearchedPlace.js | 3 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/saved-places.cy.js b/cypress/e2e/saved-places.cy.js index bf31d4e7..32cc067a 100644 --- a/cypress/e2e/saved-places.cy.js +++ b/cypress/e2e/saved-places.cy.js @@ -232,3 +232,38 @@ describe('Saved place detail feature', () => { cy.focused().contains(buttonLabel.delete); }); }); +describe('Edge cases', () => { + beforeEach(() => { + cy.log('Resetting the database'); + cy.exec('npx prisma migrate reset --force'); // https://docs.cypress.io/guides/end-to-end-testing/testing-your-app#Seeding-data + cy.log('Setting up'); + cy.auth(); + cy.visit('/'); + cy.waitForMapToLoad(); + }); + it.skip('Searching a place just deleted will drop a place mark and show its detail', () => { + // TODO #428: Fix this test + cy.log(`Setup: Saving a place`); + const savedPlaceName = 'Kyoto Station'; + cy.findByRole('button', {name: buttonLabel.search}).click(); + cy.focused().realType(savedPlaceName); + cy.findByRole('option', {name: savedPlaceName}) + .should('be.visible') + .click(); // This fails... + cy.findByRole('button', {name: buttonLabel.saveSearchedPlace}).click(); + cy.findByRole('button', {name: buttonLabel.saveEdit}).click(); + cy.log(`Setup: Deleting a place`); + cy.findByRole('button', {name: savedPlaceName}).click(); + cy.findByRole('button', {name: buttonLabel.delete}).click(); + cy.findByRole('button', {name: buttonLabel.delete}).click(); + cy.log(`Execute: Searching for the place just deleted...`); + cy.findByRole('button', {name: buttonLabel.search}).click(); + cy.focused().realType(savedPlaceName); + cy.findByRole('option', {name: savedPlaceName}) + .should('be.visible') + .click(); // This fails... + cy.log('Verify: ...Shows the place on the map'); + cy.findByRole('button', {name: savedPlaceName}).should('be.visible'); + cy.findByRole('heading', {name: savedPlaceName}).should('be.visible'); + }); +}); diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 179d37ae..ea7f543c 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -20,7 +20,7 @@ import {useStateObject} from 'src/hooks/useStateObject'; import {buttonLabel, linkText, loadingMessage} from 'src/utils/uiCopies'; export const SearchedPlace = ({mapObject}) => { - const [placeId] = useContext(PlaceIdContext); + const [placeId, setPlaceId] = useContext(PlaceIdContext); const nightMode = useContext(NightModeContext); const {setPlaces, userData, setUserData} = usePlaces(); @@ -222,6 +222,7 @@ export const SearchedPlace = ({mapObject}) => { const jsonResponse = await response.json(); marker.current.setMap(null); // remove the searched place marker setUserData([...userData, jsonResponse]); + setPlaceId(''); setState({status: 'saved'}); setPlaces({ ui: 'open', From 47550a3b2543eb579c4d451fb2133605380094ca Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sun, 5 Mar 2023 07:12:16 +0900 Subject: [PATCH 04/28] refactor(search): reduce # of UI states in component The "initial" and "saved" states are the same as the "closed" state when it comes to what the user sees as UI. --- src/components/SearchedPlace.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index ea7f543c..8cd27b5c 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -26,7 +26,7 @@ export const SearchedPlace = ({mapObject}) => { const {setPlaces, userData, setUserData} = usePlaces(); const [state, setState] = useStateObject({ - status: 'initial', + status: 'closed', placeData: null, error: null, ripple: { @@ -223,7 +223,7 @@ export const SearchedPlace = ({mapObject}) => { marker.current.setMap(null); // remove the searched place marker setUserData([...userData, jsonResponse]); setPlaceId(''); - setState({status: 'saved'}); + setState({status: 'closed'}); setPlaces({ ui: 'open', selectedPlace: { @@ -274,7 +274,7 @@ export const SearchedPlace = ({mapObject}) => { ] : null; - if (status === 'initial') { + if (status === 'closed') { return null; } else if (status === 'loading') { return null; // TODO #208: render loading spinner or its equivalent @@ -330,8 +330,6 @@ export const SearchedPlace = ({mapObject}) => { ); - } else if (status === 'closed') { - return null; } else if (status === 'editing') { return ( { {loadingMessage.create} ); - } else if (status === 'saved') { - return null; } }; From ce68c5a9a524bc757548dad64e5c557e5c32097d Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 6 Mar 2023 06:50:36 +0900 Subject: [PATCH 05/28] build(package-lock.json): fix vulnerabilities with npm audit fix --- package-lock.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 459159ef..c3db94a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4716,9 +4716,9 @@ } }, "node_modules/@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", @@ -11844,9 +11844,9 @@ "dev": true }, "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "node_modules/http-errors": { "version": "1.8.1", @@ -30214,9 +30214,9 @@ } }, "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" }, "@sideway/pinpoint": { "version": "2.0.0", @@ -35747,9 +35747,9 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "http-errors": { "version": "1.8.1", From 82b64a854b8167887f62889b3fc5c5df6ab3d1eb Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Tue, 7 Mar 2023 07:11:35 +0900 Subject: [PATCH 06/28] refactor(editor): style the background of PlaceInfoEditor in SearchedPlace/SavedPlaces components prepare for replacing Slate.js with Tiptap --- src/components/PlaceInfoEditor.js | 65 +++++++++++++++---------------- src/components/SavedPlaces.js | 15 ++++--- src/components/SearchedPlace.js | 15 ++++--- 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/components/PlaceInfoEditor.js b/src/components/PlaceInfoEditor.js index 544266fa..63602d5b 100644 --- a/src/components/PlaceInfoEditor.js +++ b/src/components/PlaceInfoEditor.js @@ -7,7 +7,6 @@ import {Slate, Editable, withReact} from 'slate-react'; import {Transforms, createEditor, Node, Element} from 'slate'; import {withHistory} from 'slate-history'; -import {ModalPopup} from 'src/components/ModalPopup'; import {InlineChromiumBugfix} from './InlineChromiumBugfix'; import {H2PlaceName} from 'src/elements/H2PlaceName'; import {HeaderEditor} from 'src/elements/HeaderEditor'; @@ -113,39 +112,37 @@ export const PlaceInfoEditor = ({ }; return ( - +
+ (content.current = value)} + > + + + {editorLabel} + +
+ + +
+
+ +
+
); }; diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index 4c7d756d..77949aaf 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -10,6 +10,7 @@ import {ComposeDialog} from 'src/elements/ComposeDialog'; import {DivCloud} from 'src/elements/DivCloud'; import {DivModalBackdrop} from 'src/elements/DivModalBackdrop'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; +import {ModalPopup} from './ModalPopup'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {SpanRipple} from 'src/elements/SpanRipple'; @@ -365,12 +366,14 @@ export const SavedPlaces = ({mapObject}) => { ); } else if (ui === 'editing') { return ( - setPlaces({ui: 'open'})} - updateData={updateData} - /> + ); } else if (ui === 'saving') { return ( diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 8cd27b5c..396d0633 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -8,6 +8,7 @@ import {ButtonDialog} from 'src/elements/ButtonDialog'; import {CloseButton} from './CloseButton'; import {DivCloud} from 'src/elements/DivCloud'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; +import {ModalPopup} from './ModalPopup'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {PlaceInfoEditor} from './PlaceInfoEditor'; import {SpanRipple} from 'src/elements/SpanRipple'; @@ -332,12 +333,14 @@ export const SearchedPlace = ({mapObject}) => { ); } else if (status === 'editing') { return ( - + ); } else if (status === 'saving') { return ( From 8da3c13bc0eae2f125d328c1eb4a4cd3867fe887 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Tue, 7 Mar 2023 07:30:45 +0900 Subject: [PATCH 07/28] refactor(editor): use FocusLock outside ModalPopup prepare for replacing ModalPopup with DivPlaceInfoBackground --- src/components/ModalPopup.js | 29 +++++++++++++---------------- src/components/SavedPlaces.js | 22 ++++++++++++++-------- src/components/SearchedPlace.js | 19 +++++++++++-------- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/components/ModalPopup.js b/src/components/ModalPopup.js index 5c14cddf..74101424 100644 --- a/src/components/ModalPopup.js +++ b/src/components/ModalPopup.js @@ -1,5 +1,4 @@ import PropTypes from 'prop-types'; -import FocusLock from 'react-focus-lock'; import {DivDialog} from 'src/elements/DivDialog'; import {DivScrim} from 'src/elements/DivScrim'; @@ -7,22 +6,20 @@ import {DivPopup} from 'src/elements/DivPopup'; export const ModalPopup = ({alert, children, hidden, slideFrom, titleId}) => { return ( - - + + - - - {children} - - - + {children} + + ); }; diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index 77949aaf..151886fc 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -366,14 +366,20 @@ export const SavedPlaces = ({mapObject}) => { ); } else if (ui === 'editing') { return ( - + + + ); } else if (ui === 'saving') { return ( diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 396d0633..e5d09bb1 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -1,5 +1,6 @@ import {useContext, useEffect, useRef} from 'react'; import PropTypes from 'prop-types'; +import FocusLock from 'react-focus-lock'; import {NightModeContext} from 'src/wrappers/NightModeContext'; import {PlaceIdContext} from 'src/wrappers/PlaceIdContext'; @@ -333,14 +334,16 @@ export const SearchedPlace = ({mapObject}) => { ); } else if (status === 'editing') { return ( - + + + ); } else if (status === 'saving') { return ( From f90b6654979418d773d7fa21da51c2cb024ec157 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sat, 11 Mar 2023 09:11:24 +0900 Subject: [PATCH 08/28] fix(editor): use the same set of CSS declarations as the one for place details create visual consistency between place details and place info editor --- src/components/SavedPlaces.js | 28 +++++++++-------- src/components/SearchedPlace.js | 24 ++++++++------ src/elements/DivPlaceInfoBackground.js | 35 +++++++++++++++------ src/elements/DivPlaceInfoBackground.test.js | 31 +++++++++++++----- 4 files changed, 79 insertions(+), 39 deletions(-) diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index 151886fc..672c1ec4 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -10,7 +10,6 @@ import {ComposeDialog} from 'src/elements/ComposeDialog'; import {DivCloud} from 'src/elements/DivCloud'; import {DivModalBackdrop} from 'src/elements/DivModalBackdrop'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; -import {ModalPopup} from './ModalPopup'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {SpanRipple} from 'src/elements/SpanRipple'; @@ -367,18 +366,21 @@ export const SavedPlaces = ({mapObject}) => { } else if (ui === 'editing') { return ( - + + + setPlaces({ui: 'open'})} + updateData={updateData} + /> + + ); } else if (ui === 'saving') { diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index e5d09bb1..61a0848e 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -9,7 +9,6 @@ import {ButtonDialog} from 'src/elements/ButtonDialog'; import {CloseButton} from './CloseButton'; import {DivCloud} from 'src/elements/DivCloud'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; -import {ModalPopup} from './ModalPopup'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {PlaceInfoEditor} from './PlaceInfoEditor'; import {SpanRipple} from 'src/elements/SpanRipple'; @@ -335,14 +334,21 @@ export const SearchedPlace = ({mapObject}) => { } else if (status === 'editing') { return ( - + + + + + ); } else if (status === 'saving') { diff --git a/src/elements/DivPlaceInfoBackground.js b/src/elements/DivPlaceInfoBackground.js index 8f185f78..41c5594e 100644 --- a/src/elements/DivPlaceInfoBackground.js +++ b/src/elements/DivPlaceInfoBackground.js @@ -21,6 +21,9 @@ const setOuterSize = ` left: 0; right: 0; top: 50%; + &[data-fullscreen="true"] { + top: 0; + } `; const setBackground = stylePopupBackground(); @@ -31,25 +34,36 @@ const setInnerSize = ` position: absolute; right: 0; top: calc(var(--blur-radius) * 2); + &[data-fullscreen="true"] { + top: 0; + }`; +const setPadding = ` + padding-bottom: ${dimension.button['minimum target spacing 100']}; + padding-left: ${dimension.button['minimum target spacing 100']}; + padding-right: ${dimension.button['minimum target spacing 100']}; + padding-top: 0; + @media screen and (min-width: ${dimension.breakpoint.divPopup.padding}) { + padding-bottom: ${dimension.button['minimum target size 100']}; + padding-left: ${dimension.button['minimum target size 100']}; + padding-right: ${dimension.button['minimum target size 100']}; + } `; const positionCloseButton = ` - --popup-margin: ${dimension.button['minimum target spacing 100']}; - & button[aria-label="${buttonLabel.closePlaceDetail}"] { position: absolute; - right: var(--popup-margin); - top: var(--popup-margin); + right: ${dimension.button['minimum target spacing 100']}; + top: ${dimension.button['minimum target spacing 100']}; + } +`; +const positionOtherButtons = ` + & button+button { + margin-left: ${dimension.button['minimum target spacing 100']}; } `; const positionComponents = ` ${positionCloseButton} - /* horizontal spacing */ - & h2, - & p, - & button:not([aria-label="${buttonLabel.closePlaceDetail}"]) { - margin-left: var(--popup-margin); - } + ${positionOtherButtons} & h2, & p { --close-button-width: calc(${ @@ -130,6 +144,7 @@ const animateTransitionOut = css` export const DivPlaceInfoBackground = styled.div` ${setBackground} ${setInnerSize} + ${setPadding} ${positionComponents} ${setFontStyle} ${animateTransitionIn} diff --git a/src/elements/DivPlaceInfoBackground.test.js b/src/elements/DivPlaceInfoBackground.test.js index 2117a2c8..67eb12ef 100644 --- a/src/elements/DivPlaceInfoBackground.test.js +++ b/src/elements/DivPlaceInfoBackground.test.js @@ -19,7 +19,10 @@ describe('DivPlaceInfoBackground component', () => { position: absolute; right: 0; top: calc(var(--blur-radius) * 2); - --popup-margin: 8px; + padding-bottom: 8px; + padding-left: 8px; + padding-right: 8px; + padding-top: 0; color: var(--popup-text-color); font-family: 'Noto Sans',Verdana,sans-serif; font-size: 1.0506rem; @@ -35,16 +38,18 @@ describe('DivPlaceInfoBackground component', () => { animation-timing-function: linear; } +.c1[data-fullscreen="true"] { + top: 0; +} + .c1 button[aria-label="Close place detail"] { position: absolute; - right: var(--popup-margin); - top: var(--popup-margin); + right: 8px; + top: 8px; } -.c1 h2, -.c1 p, -.c1 button:not([aria-label="Close place detail"]) { - margin-left: var(--popup-margin); +.c1 button+button { + margin-left: 8px; } .c1 h2, @@ -104,6 +109,10 @@ describe('DivPlaceInfoBackground component', () => { overflow: hidden; } +.c0[data-fullscreen="true"] { + top: 0; +} + .c0[data-closing='true'] { color: black; mix-blend-mode: lighten; @@ -169,6 +178,14 @@ describe('DivPlaceInfoBackground component', () => { } } +@media screen and (min-width:540px) { + .c1 { + padding-bottom: 48px; + padding-left: 48px; + padding-right: 48px; + } +} + @media (prefers-reduced-motion:reduce) { .c1[data-closing='true'] { -webkit-animation-duration: 250ms; From acd7028b120d30f7d5ca0861ff3c06174e2c4486 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Tue, 4 Apr 2023 08:15:11 +0900 Subject: [PATCH 09/28] fix(geolocation): trap focus for error messages on geolocation API This bug was introduced in the commit before the last --- src/components/ModalPopup.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/ModalPopup.js b/src/components/ModalPopup.js index 74101424..5c14cddf 100644 --- a/src/components/ModalPopup.js +++ b/src/components/ModalPopup.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import FocusLock from 'react-focus-lock'; import {DivDialog} from 'src/elements/DivDialog'; import {DivScrim} from 'src/elements/DivScrim'; @@ -6,20 +7,22 @@ import {DivPopup} from 'src/elements/DivPopup'; export const ModalPopup = ({alert, children, hidden, slideFrom, titleId}) => { return ( - - - + - {children} - - + + + {children} + + + ); }; From b02c052949e13d645c7ce6d9c3e069d50e93ae30 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 6 Mar 2023 07:11:04 +0900 Subject: [PATCH 10/28] build(editor): install Tiptap replace Slate.js for PlaceInfoEditor --- package-lock.json | 1298 +++++++++++++++++++++++++++++++- package.json | 3 + src/components/TiptapEditor.js | 14 + 3 files changed, 1302 insertions(+), 13 deletions(-) create mode 100644 src/components/TiptapEditor.js diff --git a/package-lock.json b/package-lock.json index c3db94a4..4288336b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ "@googlemaps/js-api-loader": "1.12.1", "@googlemaps/react-wrapper": "1.1.15", "@prisma/client": "4.3.1", + "@tiptap/pm": "2.0.0-beta.220", + "@tiptap/react": "2.0.0-beta.220", + "@tiptap/starter-kit": "2.0.0-beta.220", "autolinker": "3.14.3", "dompurify": "2.3.3", "downshift": "7.2.0", @@ -2605,6 +2608,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@linaria/core": { + "version": "3.0.0-beta.13", + "resolved": "https://registry.npmjs.org/@linaria/core/-/core-3.0.0-beta.13.tgz", + "integrity": "sha512-3zEi5plBCOsEzUneRVuQb+2SAx3qaC1dj0FfFAI6zIJQoDWu0dlSwKijMRack7oO9tUWrchfj3OkKQAd1LBdVg==" + }, "node_modules/@next/bundle-analyzer": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-11.1.2.tgz", @@ -4308,6 +4316,15 @@ "integrity": "sha512-88p7+M0QGxKpmnkfXjS4V26AnoC/eiqZutE8GLdaI5X12NY75bXSdTY9NkmYb2Xyk1O+MmkuO6Frmsj84V6I8Q==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@prisma/client": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.3.1.tgz", @@ -4339,6 +4356,55 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.3.0-32.c875e43600dfe042452e0b868f7a48b817b9640b.tgz", "integrity": "sha512-8yWpXkQRmiSfsi2Wb/ZS5D3RFbeu/btL9Pm/gdF4phB0Lo5KGsDFMxFMgaD64mwED2nHc8ZaEJg/+4Jymb9Znw==" }, + "node_modules/@remirror/core-constants": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.0.tgz", + "integrity": "sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@remirror/core-helpers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@remirror/core-helpers/-/core-helpers-2.0.1.tgz", + "integrity": "sha512-s8M1pn33aBUhduvD1QR02uUQMegnFkGaTr4c1iBzxTTyg0rbQstzuQ7Q8TkL6n64JtgCdJS9jLz2dONb2meBKQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@linaria/core": "3.0.0-beta.13", + "@remirror/core-constants": "^2.0.0", + "@remirror/types": "^1.0.0", + "@types/object.omit": "^3.0.0", + "@types/object.pick": "^1.3.1", + "@types/throttle-debounce": "^2.1.0", + "case-anything": "^2.1.10", + "dash-get": "^1.0.2", + "deepmerge": "^4.2.2", + "fast-deep-equal": "^3.1.3", + "make-error": "^1.3.6", + "object.omit": "^3.0.0", + "object.pick": "^1.3.0", + "throttle-debounce": "^3.0.1" + } + }, + "node_modules/@remirror/types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@remirror/types/-/types-1.0.0.tgz", + "integrity": "sha512-7HQbW7k8VxrAtfzs9FxwO6XSDabn8tSFDi1wwzShOnU+cvaYpfxu0ygyTk3TpXsag1hgFKY3ZIlAfB4WVz2LkQ==", + "dependencies": { + "type-fest": "^2.0.0" + } + }, + "node_modules/@remirror/types/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@semantic-release/commit-analyzer": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz", @@ -5156,6 +5222,353 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tiptap/core": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.220.tgz", + "integrity": "sha512-F2Q666xJqijBU5o+GqekqseNgIEMTs6BhsLDaf9DwThhljGLS8RXKnSvQxrxLNrYEPpw39n/G3Qt8YAOk5qR6w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.220.tgz", + "integrity": "sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.1" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.220.tgz", + "integrity": "sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.220.tgz", + "integrity": "sha512-wthyec7s0vZlTSEAAZEgoFfx/1Arwg1zxDUrrE+YAost/Yn+w4xQksz/ts5Bx90iOk2qsJ+jzzttLRV17Ku7lA==", + "dependencies": { + "lodash": "^4.17.21", + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.220.tgz", + "integrity": "sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.0-beta.220.tgz", + "integrity": "sha512-JKKDZoceagqVXeC1XF/gOkKhLtsbYJYV+MRDorLnQVz4tXcg/SMs5Ez7OM9MxSSior8fIbUFMNsj1/UNlG+tFw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.220.tgz", + "integrity": "sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.0-beta.220.tgz", + "integrity": "sha512-2sja4ZvOb4iynHrzinnclCSFgLyo6fJc1fBV5fIYaOgZOYcvz9KK8fgKiq+wIpG58sJEmQ5kcwwBlkXv+NTK+g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.220.tgz", + "integrity": "sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.220.tgz", + "integrity": "sha512-+WfcBEedm82ntaVIEQAGz0Om96Rpav7a+4f7e8N4PrLKm6nZ3gBaEkZVQ6vjJ6S/1htiWCv1XosYIwRboPBG0w==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.220.tgz", + "integrity": "sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.220.tgz", + "integrity": "sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.220.tgz", + "integrity": "sha512-7mrHRj++UaZ26C2Gjwb0WKWAzpiKb8TOYkVC2uMaCwaNhLDXpFEwZ7RtJRSTNBHkIGnMO46BH8Z0qlkFMmk9Jw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.0-beta.220.tgz", + "integrity": "sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.220.tgz", + "integrity": "sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.220.tgz", + "integrity": "sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz", + "integrity": "sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.220.tgz", + "integrity": "sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz", + "integrity": "sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz", + "integrity": "sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.0-beta.220.tgz", + "integrity": "sha512-3tnffc2YMjNyv7Lbad6fx9wYDE/Buz8vhx76M2AOSrjYbzmTJf7mLkgdlPM0VTy7FGZD5CGgHJAgYNt5HIqPkQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.0-beta.220.tgz", + "integrity": "sha512-O9mGcmwUpEr630HY9RylIyZJKnpXi3xWINWNiAEfRJ1br5j5pHRoVRJQ1HzU+6+Z+i/8qp3zRHGLTBqihaZETA==", + "dependencies": { + "prosemirror-changeset": "^2.2.0", + "prosemirror-collab": "^1.3.0", + "prosemirror-commands": "^1.3.1", + "prosemirror-dropcursor": "^1.5.0", + "prosemirror-gapcursor": "^1.3.1", + "prosemirror-history": "^1.3.0", + "prosemirror-inputrules": "^1.2.0", + "prosemirror-keymap": "^1.2.0", + "prosemirror-markdown": "^1.10.1", + "prosemirror-menu": "^1.2.1", + "prosemirror-model": "^1.18.1", + "prosemirror-schema-basic": "^1.2.0", + "prosemirror-schema-list": "^1.2.2", + "prosemirror-state": "^1.4.1", + "prosemirror-tables": "^1.3.0", + "prosemirror-trailing-node": "^2.0.2", + "prosemirror-transform": "^1.7.0", + "prosemirror-view": "^1.28.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209" + } + }, + "node_modules/@tiptap/react": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.0.0-beta.220.tgz", + "integrity": "sha512-AZWaCGjm2FcJWNl1dxRCHOjGYvUV8R39L7tAcnKxHGajOHdFk8JQHc0XbVZhdBi2YgwvwEr7Tw9G2lzi9e6/fg==", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.0.0-beta.220", + "@tiptap/extension-floating-menu": "^2.0.0-beta.220" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.220.tgz", + "integrity": "sha512-3992NxY5sEp5xmLE/qv/yt1YkgpSpJiUlDRj02isJ0Xsxa4G6bNq+N+tN2rHB0Y8dtYVBSX2vV/DZYVX8O+Gpg==", + "dependencies": { + "@tiptap/core": "^2.0.0-beta.220", + "@tiptap/extension-blockquote": "^2.0.0-beta.220", + "@tiptap/extension-bold": "^2.0.0-beta.220", + "@tiptap/extension-bullet-list": "^2.0.0-beta.220", + "@tiptap/extension-code": "^2.0.0-beta.220", + "@tiptap/extension-code-block": "^2.0.0-beta.220", + "@tiptap/extension-document": "^2.0.0-beta.220", + "@tiptap/extension-dropcursor": "^2.0.0-beta.220", + "@tiptap/extension-gapcursor": "^2.0.0-beta.220", + "@tiptap/extension-hard-break": "^2.0.0-beta.220", + "@tiptap/extension-heading": "^2.0.0-beta.220", + "@tiptap/extension-history": "^2.0.0-beta.220", + "@tiptap/extension-horizontal-rule": "^2.0.0-beta.220", + "@tiptap/extension-italic": "^2.0.0-beta.220", + "@tiptap/extension-list-item": "^2.0.0-beta.220", + "@tiptap/extension-ordered-list": "^2.0.0-beta.220", + "@tiptap/extension-paragraph": "^2.0.0-beta.220", + "@tiptap/extension-strike": "^2.0.0-beta.220", + "@tiptap/extension-text": "^2.0.0-beta.220" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -5348,6 +5761,16 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "node_modules/@types/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==" + }, + "node_modules/@types/object.pick": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.2.tgz", + "integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==" + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -5434,6 +5857,11 @@ "@types/jest": "*" } }, + "node_modules/@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" + }, "node_modules/@types/yargs": { "version": "15.0.10", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.10.tgz", @@ -6968,6 +7396,17 @@ "cdl": "bin/cdl.js" } }, + "node_modules/case-anything": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", + "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -7647,6 +8086,11 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", + "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" + }, "node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -8349,6 +8793,11 @@ "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, + "node_modules/dash-get": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dash-get/-/dash-get-1.0.2.tgz", + "integrity": "sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==" + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -8537,7 +8986,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9068,6 +9516,17 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-ci": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.2.tgz", @@ -12910,7 +13369,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -12922,7 +13380,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -17593,6 +18050,14 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/lint-staged": { "version": "10.5.4", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", @@ -18423,6 +18888,26 @@ "node": ">=0.10.0" } }, + "node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/marked": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", @@ -18494,6 +18979,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -21958,11 +22448,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "dependencies": { + "is-extendable": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -21974,7 +22485,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -22170,6 +22680,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/orderedmap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.0.tgz", + "integrity": "sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==" + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -22789,6 +23304,194 @@ "react-is": "^16.13.1" } }, + "node_modules/prosemirror-changeset": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.0.tgz", + "integrity": "sha512-QM7ohGtkpVpwVGmFb8wqVhaz9+6IUXcIQBGZ81YNAKYuHiFJ1ShvSzab4pKqTinJhwciZbrtBEk/2WsqSt2PYg==", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.0.tgz", + "integrity": "sha512-+S/IJ69G2cUu2IM5b3PBekuxs94HO1CxJIWOFrLQXUaUDKL/JfBx+QcH31ldBlBXyDEUl+k3Vltfi1E1MKp2mA==", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.1.tgz", + "integrity": "sha512-ga1ga/RkbzxfAvb6iEXYmrEpekn5NCwTb8w1dr/gmhSoaGcQ0VPuCzOn5qDEpC45ql2oDkKoKQbRxLJwKLpMTQ==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.7.1.tgz", + "integrity": "sha512-GmWk9bAwhfHwA8xmJhBFjPcebxUG9zAPYtqpIr7NTDigWZZEJCgUYyUQeqgyscLr8ZHoh9aeprX9kW7BihUT+w==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.1.tgz", + "integrity": "sha512-GKTeE7ZoMsx5uVfc51/ouwMFPq0o8YrZ7Hx4jTF4EeGbXxBveUV8CGv46mSHuBBeXGmvu50guoV2kSnOeZZnUA==", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.0.tgz", + "integrity": "sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.2.0.tgz", + "integrity": "sha512-eAW/M/NTSSzpCOxfR8Abw6OagdG0MiDAiWHQMQveIsZtoKVYzm0AflSPq/ymqJd56/Su1YPbwy9lM13wgHOFmQ==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.1.tgz", + "integrity": "sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.10.1.tgz", + "integrity": "sha512-s7iaTLiX+qO5z8kF2NcMmy2T7mIlxzkS4Sp3vTKSYChPtbMpg6YxFkU0Y06rUg2WtKlvBu7v1bXzlGBkfjUWAA==", + "dependencies": { + "markdown-it": "^13.0.1", + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.1.tgz", + "integrity": "sha512-sBirXxVfHalZO4f1ZS63WzewINK4182+7dOmoMeBkqYO8wqMBvBS7wQuwVOHnkMWPEh0+N0LJ856KYUN+vFkmQ==", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.0.tgz", + "integrity": "sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.1.tgz", + "integrity": "sha512-vYBdIHsYKSDIqYmPBC7lnwk9DsKn8PnVqK97pMYP5MLEDFqWIX75JiaJTzndBii4bRuNqhC2UfDOfM3FKhlBHg==", + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.2.2.tgz", + "integrity": "sha512-rd0pqSDp86p0MUMKG903g3I9VmElFkQpkZ2iOd3EOVg1vo5Cst51rAsoE+5IPy0LPXq64eGcCYlW1+JPNxOj2w==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.2.tgz", + "integrity": "sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.2.tgz", + "integrity": "sha512-/9JTeN6s58Zq66HXaxP6uf8PAmc7XXKZFPlOGVtLvxEd6xBP6WtzaJB9wBjiGUzwbdhdMEy7V62yuHqk/3VrnQ==", + "dependencies": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.3.tgz", + "integrity": "sha512-lGrjMrn97KWkjQSW/FjdvnhJmqFACmQIyr6lKYApvHitDnKsCoZz6XzrHB7RZYHni/0NxQmZ01p/2vyK2SkvaA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@remirror/core-constants": "^2.0.0", + "@remirror/core-helpers": "^2.0.1", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1", + "prosemirror-state": "^1", + "prosemirror-view": "^1" + } + }, + "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.7.1.tgz", + "integrity": "sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg==", + "dependencies": { + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.30.1.tgz", + "integrity": "sha512-pZUfr7lICJkEY7XwzldAKrkflZDeIvnbfuu2RIS01N5NwJmR/dfZzDzJRzhb3SM2QtT/bM8b4Nnib8X3MGpAhA==", + "dependencies": { + "prosemirror-model": "^1.16.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -23365,6 +24068,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rope-sequence": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz", + "integrity": "sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q==" + }, "node_modules/rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -25514,6 +26222,14 @@ "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "engines": { + "node": ">=10" + } + }, "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -25576,6 +26292,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/title-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", @@ -25854,6 +26578,11 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "node_modules/uglify-js": { "version": "3.17.1", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", @@ -26183,6 +26912,11 @@ "browser-process-hrtime": "^1.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", + "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==" + }, "node_modules/w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", @@ -28661,6 +29395,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@linaria/core": { + "version": "3.0.0-beta.13", + "resolved": "https://registry.npmjs.org/@linaria/core/-/core-3.0.0-beta.13.tgz", + "integrity": "sha512-3zEi5plBCOsEzUneRVuQb+2SAx3qaC1dj0FfFAI6zIJQoDWu0dlSwKijMRack7oO9tUWrchfj3OkKQAd1LBdVg==" + }, "@next/bundle-analyzer": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/bundle-analyzer/-/bundle-analyzer-11.1.2.tgz", @@ -29925,6 +30664,11 @@ "integrity": "sha512-88p7+M0QGxKpmnkfXjS4V26AnoC/eiqZutE8GLdaI5X12NY75bXSdTY9NkmYb2Xyk1O+MmkuO6Frmsj84V6I8Q==", "dev": true }, + "@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + }, "@prisma/client": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.3.1.tgz", @@ -29943,6 +30687,51 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.3.0-32.c875e43600dfe042452e0b868f7a48b817b9640b.tgz", "integrity": "sha512-8yWpXkQRmiSfsi2Wb/ZS5D3RFbeu/btL9Pm/gdF4phB0Lo5KGsDFMxFMgaD64mwED2nHc8ZaEJg/+4Jymb9Znw==" }, + "@remirror/core-constants": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.0.tgz", + "integrity": "sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, + "@remirror/core-helpers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@remirror/core-helpers/-/core-helpers-2.0.1.tgz", + "integrity": "sha512-s8M1pn33aBUhduvD1QR02uUQMegnFkGaTr4c1iBzxTTyg0rbQstzuQ7Q8TkL6n64JtgCdJS9jLz2dONb2meBKQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@linaria/core": "3.0.0-beta.13", + "@remirror/core-constants": "^2.0.0", + "@remirror/types": "^1.0.0", + "@types/object.omit": "^3.0.0", + "@types/object.pick": "^1.3.1", + "@types/throttle-debounce": "^2.1.0", + "case-anything": "^2.1.10", + "dash-get": "^1.0.2", + "deepmerge": "^4.2.2", + "fast-deep-equal": "^3.1.3", + "make-error": "^1.3.6", + "object.omit": "^3.0.0", + "object.pick": "^1.3.0", + "throttle-debounce": "^3.0.1" + } + }, + "@remirror/types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@remirror/types/-/types-1.0.0.tgz", + "integrity": "sha512-7HQbW7k8VxrAtfzs9FxwO6XSDabn8tSFDi1wwzShOnU+cvaYpfxu0ygyTk3TpXsag1hgFKY3ZIlAfB4WVz2LkQ==", + "requires": { + "type-fest": "^2.0.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } + }, "@semantic-release/commit-analyzer": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz", @@ -30545,6 +31334,197 @@ "@babel/runtime": "^7.10.2" } }, + "@tiptap/core": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.220.tgz", + "integrity": "sha512-F2Q666xJqijBU5o+GqekqseNgIEMTs6BhsLDaf9DwThhljGLS8RXKnSvQxrxLNrYEPpw39n/G3Qt8YAOk5qR6w==", + "requires": {} + }, + "@tiptap/extension-blockquote": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.220.tgz", + "integrity": "sha512-uE1VRU/doQzXsfsZ/JqsbSbXeZYTJnyQkSfHYA2ZYhbEM2XqDEsYkgcmZEJgunUZJpERf+3ZTfTpqaHq29iMMg==", + "requires": {} + }, + "@tiptap/extension-bold": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.220.tgz", + "integrity": "sha512-KcEuKI85Drug/cCWbDy+HxhYrD+rLXHEBG10DmKPvgPpKHG/2wOau6LwUwyV4muWR8CR2mIO+mEc3yVBD8nNwQ==", + "requires": {} + }, + "@tiptap/extension-bubble-menu": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.220.tgz", + "integrity": "sha512-wthyec7s0vZlTSEAAZEgoFfx/1Arwg1zxDUrrE+YAost/Yn+w4xQksz/ts5Bx90iOk2qsJ+jzzttLRV17Ku7lA==", + "requires": { + "lodash": "^4.17.21", + "tippy.js": "^6.3.7" + } + }, + "@tiptap/extension-bullet-list": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.220.tgz", + "integrity": "sha512-QQ/0ZlYy6Hgb+UAc79V+fxvI+AaQf20cbKtBXaR8TIZ0x4FotSma89bKh+CIXMhFiBGXTcYBaYhl7OwACsKtxw==", + "requires": {} + }, + "@tiptap/extension-code": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.0-beta.220.tgz", + "integrity": "sha512-JKKDZoceagqVXeC1XF/gOkKhLtsbYJYV+MRDorLnQVz4tXcg/SMs5Ez7OM9MxSSior8fIbUFMNsj1/UNlG+tFw==", + "requires": {} + }, + "@tiptap/extension-code-block": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.220.tgz", + "integrity": "sha512-fgA7yTfHqhBtMJF7I9FPJ6UWuZPtxOQiN45Iv9LNmFIB6YRucdpmF+daZ27sElu0a+eICZyXwVn4w4iJphifuw==", + "requires": {} + }, + "@tiptap/extension-document": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.0-beta.220.tgz", + "integrity": "sha512-2sja4ZvOb4iynHrzinnclCSFgLyo6fJc1fBV5fIYaOgZOYcvz9KK8fgKiq+wIpG58sJEmQ5kcwwBlkXv+NTK+g==", + "requires": {} + }, + "@tiptap/extension-dropcursor": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.220.tgz", + "integrity": "sha512-BIaA4Lvb3xL9KFN+K6SO2IHqLO6hDmGN2/rGKHFaU3Eh+oiXM2G73KTSS5KIP1u872zY1RpAtswSc4kjv3cuVw==", + "requires": {} + }, + "@tiptap/extension-floating-menu": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.220.tgz", + "integrity": "sha512-+WfcBEedm82ntaVIEQAGz0Om96Rpav7a+4f7e8N4PrLKm6nZ3gBaEkZVQ6vjJ6S/1htiWCv1XosYIwRboPBG0w==", + "requires": { + "tippy.js": "^6.3.7" + } + }, + "@tiptap/extension-gapcursor": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.220.tgz", + "integrity": "sha512-W5N2Ey+thufUOrs2TFGpEGBGue7ZEhcUXvxcsZlGbrjVa9Y+4rEp68Du4y7yM0hCeSj2GGwiV+uPzkc0CSDE/g==", + "requires": {} + }, + "@tiptap/extension-hard-break": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.220.tgz", + "integrity": "sha512-oY3454o53YNFbuokzyGzG4PdMHkIYreY3nrALioZ0SwYeoFNcGA6Zcn4rDRfdp+QvbbiHfeBTR/CpWF13HZYTg==", + "requires": {} + }, + "@tiptap/extension-heading": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.220.tgz", + "integrity": "sha512-7mrHRj++UaZ26C2Gjwb0WKWAzpiKb8TOYkVC2uMaCwaNhLDXpFEwZ7RtJRSTNBHkIGnMO46BH8Z0qlkFMmk9Jw==", + "requires": {} + }, + "@tiptap/extension-history": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.0-beta.220.tgz", + "integrity": "sha512-qNL2a9UhnlmCs4y2iQYrfeMB8vEX3bHozBJanHu0PWNQJcj90R5xqorBp/bRcqZdi0kuQfxcTnGHtLUpN/U0TA==", + "requires": {} + }, + "@tiptap/extension-horizontal-rule": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.220.tgz", + "integrity": "sha512-XMIs4R+4BoH5LpIxey513mZuus0XLHqjVayqtf03enmjBTLWzkixvvWLPLw4a47FJL5Q8l4REFHxjNifRzOKkg==", + "requires": {} + }, + "@tiptap/extension-italic": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.220.tgz", + "integrity": "sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==", + "requires": {} + }, + "@tiptap/extension-list-item": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz", + "integrity": "sha512-+O0ivwxPP2l/m9PAowb2ytDT/cM5kwu0s1W5MUsHPIqf+M6ahnl4ESjhWZfDHUzvjqPq6MTbqoQLHbB1KS/N7w==", + "requires": {} + }, + "@tiptap/extension-ordered-list": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.220.tgz", + "integrity": "sha512-j3DmxJfwmNxFfMnvO7glmGlhYeZSIUnRrKnZu2KkpD6OcGJSh9y/yfnYwcuK80XbzEG/jKKIw0M2yRveOvyVwA==", + "requires": {} + }, + "@tiptap/extension-paragraph": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.220.tgz", + "integrity": "sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==", + "requires": {} + }, + "@tiptap/extension-strike": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz", + "integrity": "sha512-cIM2ma6mzk08pijOn+KS3ZoHWaUVsVT+OF3m6xewjwJdC0ILg9nApEOhPFrhbeDcxcPmJMlgBl/xeUrEu1HQMg==", + "requires": {} + }, + "@tiptap/extension-text": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.0-beta.220.tgz", + "integrity": "sha512-3tnffc2YMjNyv7Lbad6fx9wYDE/Buz8vhx76M2AOSrjYbzmTJf7mLkgdlPM0VTy7FGZD5CGgHJAgYNt5HIqPkQ==", + "requires": {} + }, + "@tiptap/pm": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.0-beta.220.tgz", + "integrity": "sha512-O9mGcmwUpEr630HY9RylIyZJKnpXi3xWINWNiAEfRJ1br5j5pHRoVRJQ1HzU+6+Z+i/8qp3zRHGLTBqihaZETA==", + "requires": { + "prosemirror-changeset": "^2.2.0", + "prosemirror-collab": "^1.3.0", + "prosemirror-commands": "^1.3.1", + "prosemirror-dropcursor": "^1.5.0", + "prosemirror-gapcursor": "^1.3.1", + "prosemirror-history": "^1.3.0", + "prosemirror-inputrules": "^1.2.0", + "prosemirror-keymap": "^1.2.0", + "prosemirror-markdown": "^1.10.1", + "prosemirror-menu": "^1.2.1", + "prosemirror-model": "^1.18.1", + "prosemirror-schema-basic": "^1.2.0", + "prosemirror-schema-list": "^1.2.2", + "prosemirror-state": "^1.4.1", + "prosemirror-tables": "^1.3.0", + "prosemirror-trailing-node": "^2.0.2", + "prosemirror-transform": "^1.7.0", + "prosemirror-view": "^1.28.2" + } + }, + "@tiptap/react": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.0.0-beta.220.tgz", + "integrity": "sha512-AZWaCGjm2FcJWNl1dxRCHOjGYvUV8R39L7tAcnKxHGajOHdFk8JQHc0XbVZhdBi2YgwvwEr7Tw9G2lzi9e6/fg==", + "requires": { + "@tiptap/extension-bubble-menu": "^2.0.0-beta.220", + "@tiptap/extension-floating-menu": "^2.0.0-beta.220" + } + }, + "@tiptap/starter-kit": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.220.tgz", + "integrity": "sha512-3992NxY5sEp5xmLE/qv/yt1YkgpSpJiUlDRj02isJ0Xsxa4G6bNq+N+tN2rHB0Y8dtYVBSX2vV/DZYVX8O+Gpg==", + "requires": { + "@tiptap/core": "^2.0.0-beta.220", + "@tiptap/extension-blockquote": "^2.0.0-beta.220", + "@tiptap/extension-bold": "^2.0.0-beta.220", + "@tiptap/extension-bullet-list": "^2.0.0-beta.220", + "@tiptap/extension-code": "^2.0.0-beta.220", + "@tiptap/extension-code-block": "^2.0.0-beta.220", + "@tiptap/extension-document": "^2.0.0-beta.220", + "@tiptap/extension-dropcursor": "^2.0.0-beta.220", + "@tiptap/extension-gapcursor": "^2.0.0-beta.220", + "@tiptap/extension-hard-break": "^2.0.0-beta.220", + "@tiptap/extension-heading": "^2.0.0-beta.220", + "@tiptap/extension-history": "^2.0.0-beta.220", + "@tiptap/extension-horizontal-rule": "^2.0.0-beta.220", + "@tiptap/extension-italic": "^2.0.0-beta.220", + "@tiptap/extension-list-item": "^2.0.0-beta.220", + "@tiptap/extension-ordered-list": "^2.0.0-beta.220", + "@tiptap/extension-paragraph": "^2.0.0-beta.220", + "@tiptap/extension-strike": "^2.0.0-beta.220", + "@tiptap/extension-text": "^2.0.0-beta.220" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -30733,6 +31713,16 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==" + }, + "@types/object.pick": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.2.tgz", + "integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==" + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -30821,6 +31811,11 @@ "@types/jest": "*" } }, + "@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" + }, "@types/yargs": { "version": "15.0.10", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.10.tgz", @@ -31972,6 +32967,11 @@ "redeyed": "~2.1.0" } }, + "case-anything": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", + "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -32533,6 +33533,11 @@ "yaml": "^1.10.0" } }, + "crelt": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", + "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -33059,6 +34064,11 @@ "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, + "dash-get": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dash-get/-/dash-get-1.0.2.tgz", + "integrity": "sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -33199,8 +34209,7 @@ "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, "defaults": { "version": "1.0.3", @@ -33617,6 +34626,11 @@ "ansi-colors": "^4.1.1" } }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" + }, "env-ci": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.2.tgz", @@ -36520,7 +37534,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -36528,8 +37541,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -40156,6 +41168,14 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "lint-staged": { "version": "10.5.4", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", @@ -40781,6 +41801,25 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + } + } + }, "marked": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", @@ -40824,6 +41863,11 @@ } } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -43282,11 +44326,28 @@ "has": "^1.0.3" } }, + "object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "requires": { + "is-extendable": "^1.0.0" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -43294,8 +44355,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -43435,6 +44495,11 @@ } } }, + "orderedmap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.0.tgz", + "integrity": "sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==" + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -43896,6 +44961,185 @@ "react-is": "^16.13.1" } }, + "prosemirror-changeset": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.0.tgz", + "integrity": "sha512-QM7ohGtkpVpwVGmFb8wqVhaz9+6IUXcIQBGZ81YNAKYuHiFJ1ShvSzab4pKqTinJhwciZbrtBEk/2WsqSt2PYg==", + "requires": { + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-collab": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.0.tgz", + "integrity": "sha512-+S/IJ69G2cUu2IM5b3PBekuxs94HO1CxJIWOFrLQXUaUDKL/JfBx+QcH31ldBlBXyDEUl+k3Vltfi1E1MKp2mA==", + "requires": { + "prosemirror-state": "^1.0.0" + } + }, + "prosemirror-commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.1.tgz", + "integrity": "sha512-ga1ga/RkbzxfAvb6iEXYmrEpekn5NCwTb8w1dr/gmhSoaGcQ0VPuCzOn5qDEpC45ql2oDkKoKQbRxLJwKLpMTQ==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-dropcursor": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.7.1.tgz", + "integrity": "sha512-GmWk9bAwhfHwA8xmJhBFjPcebxUG9zAPYtqpIr7NTDigWZZEJCgUYyUQeqgyscLr8ZHoh9aeprX9kW7BihUT+w==", + "requires": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "prosemirror-gapcursor": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.1.tgz", + "integrity": "sha512-GKTeE7ZoMsx5uVfc51/ouwMFPq0o8YrZ7Hx4jTF4EeGbXxBveUV8CGv46mSHuBBeXGmvu50guoV2kSnOeZZnUA==", + "requires": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "prosemirror-history": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.0.tgz", + "integrity": "sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA==", + "requires": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "rope-sequence": "^1.3.0" + } + }, + "prosemirror-inputrules": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.2.0.tgz", + "integrity": "sha512-eAW/M/NTSSzpCOxfR8Abw6OagdG0MiDAiWHQMQveIsZtoKVYzm0AflSPq/ymqJd56/Su1YPbwy9lM13wgHOFmQ==", + "requires": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-keymap": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.1.tgz", + "integrity": "sha512-kVK6WGC+83LZwuSJnuCb9PsADQnFZllt94qPP3Rx/vLcOUV65+IbBeH2nS5cFggPyEVJhGkGrgYFRrG250WhHQ==", + "requires": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "prosemirror-markdown": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.10.1.tgz", + "integrity": "sha512-s7iaTLiX+qO5z8kF2NcMmy2T7mIlxzkS4Sp3vTKSYChPtbMpg6YxFkU0Y06rUg2WtKlvBu7v1bXzlGBkfjUWAA==", + "requires": { + "markdown-it": "^13.0.1", + "prosemirror-model": "^1.0.0" + } + }, + "prosemirror-menu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.1.tgz", + "integrity": "sha512-sBirXxVfHalZO4f1ZS63WzewINK4182+7dOmoMeBkqYO8wqMBvBS7wQuwVOHnkMWPEh0+N0LJ856KYUN+vFkmQ==", + "requires": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "prosemirror-model": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.0.tgz", + "integrity": "sha512-/CvFGJnwc41EJSfDkQLly1cAJJJmBpZwwUJtwZPTjY2RqZJfM8HVbCreOY/jti8wTRbVyjagcylyGoeJH/g/3w==", + "requires": { + "orderedmap": "^2.0.0" + } + }, + "prosemirror-schema-basic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.1.tgz", + "integrity": "sha512-vYBdIHsYKSDIqYmPBC7lnwk9DsKn8PnVqK97pMYP5MLEDFqWIX75JiaJTzndBii4bRuNqhC2UfDOfM3FKhlBHg==", + "requires": { + "prosemirror-model": "^1.19.0" + } + }, + "prosemirror-schema-list": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.2.2.tgz", + "integrity": "sha512-rd0pqSDp86p0MUMKG903g3I9VmElFkQpkZ2iOd3EOVg1vo5Cst51rAsoE+5IPy0LPXq64eGcCYlW1+JPNxOj2w==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-state": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.2.tgz", + "integrity": "sha512-puuzLD2mz/oTdfgd8msFbe0A42j5eNudKAAPDB0+QJRw8cO1ygjLmhLrg9RvDpf87Dkd6D4t93qdef00KKNacQ==", + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "prosemirror-tables": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.2.tgz", + "integrity": "sha512-/9JTeN6s58Zq66HXaxP6uf8PAmc7XXKZFPlOGVtLvxEd6xBP6WtzaJB9wBjiGUzwbdhdMEy7V62yuHqk/3VrnQ==", + "requires": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "prosemirror-trailing-node": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.3.tgz", + "integrity": "sha512-lGrjMrn97KWkjQSW/FjdvnhJmqFACmQIyr6lKYApvHitDnKsCoZz6XzrHB7RZYHni/0NxQmZ01p/2vyK2SkvaA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@remirror/core-constants": "^2.0.0", + "@remirror/core-helpers": "^2.0.1", + "escape-string-regexp": "^4.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + } + } + }, + "prosemirror-transform": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.7.1.tgz", + "integrity": "sha512-VteoifAfpt46z0yEt6Fc73A5OID9t/y2QIeR5MgxEwTuitadEunD/V0c9jQW8ziT8pbFM54uTzRLJ/nLuQjMxg==", + "requires": { + "prosemirror-model": "^1.0.0" + } + }, + "prosemirror-view": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.30.1.tgz", + "integrity": "sha512-pZUfr7lICJkEY7XwzldAKrkflZDeIvnbfuu2RIS01N5NwJmR/dfZzDzJRzhb3SM2QtT/bM8b4Nnib8X3MGpAhA==", + "requires": { + "prosemirror-model": "^1.16.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -44338,6 +45582,11 @@ "glob": "^7.1.3" } }, + "rope-sequence": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz", + "integrity": "sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q==" + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -45990,6 +47239,11 @@ "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, + "throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==" + }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -46054,6 +47308,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "title-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", @@ -46277,6 +47539,11 @@ "dev": true, "peer": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "uglify-js": { "version": "3.17.1", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.1.tgz", @@ -46525,6 +47792,11 @@ "browser-process-hrtime": "^1.0.0" } }, + "w3c-keyname": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", + "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==" + }, "w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", diff --git a/package.json b/package.json index 8d22e3ea..b2c82bdc 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,9 @@ "@googlemaps/js-api-loader": "1.12.1", "@googlemaps/react-wrapper": "1.1.15", "@prisma/client": "4.3.1", + "@tiptap/pm": "2.0.0-beta.220", + "@tiptap/react": "2.0.0-beta.220", + "@tiptap/starter-kit": "2.0.0-beta.220", "autolinker": "3.14.3", "dompurify": "2.3.3", "downshift": "7.2.0", diff --git a/src/components/TiptapEditor.js b/src/components/TiptapEditor.js new file mode 100644 index 00000000..20642206 --- /dev/null +++ b/src/components/TiptapEditor.js @@ -0,0 +1,14 @@ +import {useEditor, EditorContent} from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +// import PropTypes from 'prop-types'; + +export const TiptapEditor = () => { + const editor = useEditor({ + extensions: [StarterKit], + content: '

Hello World! 🌎️

', + }); + + return ; +}; + +// TiptapEditor.propTypes = {}; From f5361597e6c606927e458a86ff81a078687f6d9f Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Thu, 9 Mar 2023 09:01:18 +0900 Subject: [PATCH 11/28] refactor(search): separate CSS variables for main and placeholder text color make it easy to change placeholder text color only re #429 --- src/elements/ComposeSearchBox.js | 2 +- src/elements/ComposeSearchBox.test.js | 8 ++++---- src/elements/GlobalStyle.js | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/elements/ComposeSearchBox.js b/src/elements/ComposeSearchBox.js index 041718ba..fd622164 100644 --- a/src/elements/ComposeSearchBox.js +++ b/src/elements/ComposeSearchBox.js @@ -57,7 +57,7 @@ const styleSearchboxText = ` ${styleText} } & input[type="search"]::placeholder { - color: var(--popup-text-color); + color: var(--placeholder-text-color); opacity: 1; /* to override the default of Firefox */ } `; diff --git a/src/elements/ComposeSearchBox.test.js b/src/elements/ComposeSearchBox.test.js index d493337f..6c566996 100644 --- a/src/elements/ComposeSearchBox.test.js +++ b/src/elements/ComposeSearchBox.test.js @@ -55,22 +55,22 @@ describe('ComposeSearchBox component', () => { } .c0 input[type="search"]::-webkit-input-placeholder { - color: var(--popup-text-color); + color: var(--placeholder-text-color); opacity: 1; } .c0 input[type="search"]::-moz-placeholder { - color: var(--popup-text-color); + color: var(--placeholder-text-color); opacity: 1; } .c0 input[type="search"]:-ms-input-placeholder { - color: var(--popup-text-color); + color: var(--placeholder-text-color); opacity: 1; } .c0 input[type="search"]::placeholder { - color: var(--popup-text-color); + color: var(--placeholder-text-color); opacity: 1; } diff --git a/src/elements/GlobalStyle.js b/src/elements/GlobalStyle.js index cb6d9ba9..91f86101 100644 --- a/src/elements/GlobalStyle.js +++ b/src/elements/GlobalStyle.js @@ -43,6 +43,7 @@ const setColorScheme = ` --popup-background-color-fallback: ${color['white 93']}; --popup-glow-color-fallback: ${color['white 93']}; --popup-text-color: ${color['dark-grey 100']}; + --placeholder-text-color: ${color['dark-grey 100']}; /* TODO: #429 Make it lighter than --popup-text-color */ --popup-background-highlighted: ${color['background for dark-grey text 100']}; --ripple-color: ${color['black 33']}; --login-background-image-url: url(/login-background-light.png); @@ -65,6 +66,7 @@ const setColorScheme = ` --popup-background-color-fallback: ${color['glass-grey 90']}; --popup-glow-color-fallback: ${color['glass-grey 90']}; --popup-text-color: ${color['off-white 100']}; + --placeholder-text-color: ${color['off-white 100']}; /* TODO: #429 Make it dimmer than --popup-text-color */ --popup-background-highlighted: ${color['off-black 100']}; --ripple-color: ${color['white 40']}; --login-background-image-url: url(/login-background-dark.png); From d80218746bd807f6c3b1f4fb32e6d9710d75fbd0 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 20 Mar 2023 07:33:25 +0900 Subject: [PATCH 12/28] refactor(editor): replace Slate.js with Tiptap for implementing place info text editor improve DX; allow us to save user notes as HTML in the database --- cypress/e2e/saved-places.cy.js | 8 +- package-lock.json | 55 ++++++ package.json | 2 + src/components/SavedPlaces.js | 87 +++++---- src/components/SearchedPlace.js | 104 +++-------- src/components/TiptapEditor.js | 190 +++++++++++++++++++- src/elements/DivPlaceInfoBackground.js | 20 +++ src/elements/DivPlaceInfoBackground.test.js | 13 ++ 8 files changed, 346 insertions(+), 133 deletions(-) diff --git a/cypress/e2e/saved-places.cy.js b/cypress/e2e/saved-places.cy.js index 32cc067a..51e76dcf 100644 --- a/cypress/e2e/saved-places.cy.js +++ b/cypress/e2e/saved-places.cy.js @@ -48,7 +48,7 @@ describe('Saved place detail feature', () => { cy.findByRole('heading', {name: editorLabel, timeout: 20000}).should( 'be.visible', ); - cy.log(`...autofocuses the note field`); + cy.log(`...autofocuses the note`); cy.focused().should('have.attr', 'role', 'textbox'); cy.log(`Typing text...`); @@ -56,13 +56,15 @@ describe('Saved place detail feature', () => { cy.wait(100); // otherwise, Cypress will type 'bc', not 'abc'. This is a known issue. See https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ cy.findByRole('textbox').type('abc '); cy.log(`...updates the place name*`); - cy.findByRole('textbox').get('h2').contains('abc', {timeout: 20000}); + cy.findByRole('heading', {level: 2}).should('include.text', 'abc', { + timeout: 20000, + }); cy.log(`Pressing Down Arrow key and typing URL text...`); cy.findByRole('textbox').type('{downarrow}'); cy.focused().type('https://google.com '); cy.log(`...updates the place note`); - cy.findByRole('textbox').get('p').contains('https://google.com'); + cy.contains('https://google.com').should('be.visible'); cy.log(`Clicking Save button...`); cy.findByRole('button', {name: buttonLabel.saveEdit}).click(); diff --git a/package-lock.json b/package-lock.json index 4288336b..321cbbe3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "@googlemaps/js-api-loader": "1.12.1", "@googlemaps/react-wrapper": "1.1.15", "@prisma/client": "4.3.1", + "@tiptap/extension-link": "2.0.0-beta.220", + "@tiptap/extension-placeholder": "2.0.0-beta.220", "@tiptap/pm": "2.0.0-beta.220", "@tiptap/react": "2.0.0-beta.220", "@tiptap/starter-kit": "2.0.0-beta.220", @@ -5428,6 +5430,22 @@ "@tiptap/core": "^2.0.0-beta.209" } }, + "node_modules/@tiptap/extension-link": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.0.0-beta.220.tgz", + "integrity": "sha512-vjEA8cE37ZZVVgPHSpttw3kbJoClb+ya/BVukDtJ1h6C7mIR1rqzNxTgpbnXJuA8xww0JOjpa5dpzEgcs294fA==", + "dependencies": { + "linkifyjs": "^4.1.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, "node_modules/@tiptap/extension-list-item": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz", @@ -5464,6 +5482,19 @@ "@tiptap/core": "^2.0.0-beta.209" } }, + "node_modules/@tiptap/extension-placeholder": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.0.0-beta.220.tgz", + "integrity": "sha512-Pq79BH/JqhjTNgxHkmbzcmwATsSJdRRSLHrnLx5upSmwEkQwCzqni9jL10rL2NM1ZyR+o25xC+r5loujx0aQ+Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.209", + "@tiptap/pm": "^2.0.0-beta.209" + } + }, "node_modules/@tiptap/extension-strike": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz", @@ -18058,6 +18089,11 @@ "uc.micro": "^1.0.1" } }, + "node_modules/linkifyjs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.0.tgz", + "integrity": "sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==" + }, "node_modules/lint-staged": { "version": "10.5.4", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", @@ -31435,6 +31471,14 @@ "integrity": "sha512-aWAgqoR8fql9fJ7T/ZrEqovkEjZXbUpvlvWEvdBDMG3id8ZTGNDpdDKdvI6J/Rl5ZGPIg1TpHJtd+UixheWQsQ==", "requires": {} }, + "@tiptap/extension-link": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.0.0-beta.220.tgz", + "integrity": "sha512-vjEA8cE37ZZVVgPHSpttw3kbJoClb+ya/BVukDtJ1h6C7mIR1rqzNxTgpbnXJuA8xww0JOjpa5dpzEgcs294fA==", + "requires": { + "linkifyjs": "^4.1.0" + } + }, "@tiptap/extension-list-item": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.220.tgz", @@ -31453,6 +31497,12 @@ "integrity": "sha512-ZGCzNGFYV4wa3l1nXtDIaYp7O6f0DrGTSl3alKkDTQe3SOmzXS2HjgWl9yPw8VXpU9W5mMGhXd+nGn/jUk+f/A==", "requires": {} }, + "@tiptap/extension-placeholder": { + "version": "2.0.0-beta.220", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.0.0-beta.220.tgz", + "integrity": "sha512-Pq79BH/JqhjTNgxHkmbzcmwATsSJdRRSLHrnLx5upSmwEkQwCzqni9jL10rL2NM1ZyR+o25xC+r5loujx0aQ+Q==", + "requires": {} + }, "@tiptap/extension-strike": { "version": "2.0.0-beta.220", "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.220.tgz", @@ -41176,6 +41226,11 @@ "uc.micro": "^1.0.1" } }, + "linkifyjs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.0.tgz", + "integrity": "sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==" + }, "lint-staged": { "version": "10.5.4", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", diff --git a/package.json b/package.json index b2c82bdc..8ed50179 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,8 @@ "@googlemaps/js-api-loader": "1.12.1", "@googlemaps/react-wrapper": "1.1.15", "@prisma/client": "4.3.1", + "@tiptap/extension-link": "2.0.0-beta.220", + "@tiptap/extension-placeholder": "2.0.0-beta.220", "@tiptap/pm": "2.0.0-beta.220", "@tiptap/react": "2.0.0-beta.220", "@tiptap/starter-kit": "2.0.0-beta.220", diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index 672c1ec4..c10a581e 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -12,6 +12,7 @@ import {DivModalBackdrop} from 'src/elements/DivModalBackdrop'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {SpanRipple} from 'src/elements/SpanRipple'; +import {TiptapEditor} from './TiptapEditor'; import {ClientOnlyPortal} from 'src/wrappers/ClientOnlyPortal'; @@ -196,52 +197,43 @@ export const SavedPlaces = ({mapObject}) => { } }, [ui]); - // for updating place info if (selectedPlace) { + // for rendering saved place info const selectedPlaceIndex = userData.findIndex( feature => feature.id === selectedPlace.id, ); const selectedPlaceName = userData[selectedPlaceIndex].properties.name; - const selectedPlaceNoteArray = userData[selectedPlaceIndex].properties.note; - const selectedPlaceNoteHtml = DOMPurify.sanitize( - getHtmlFromSlate({children: selectedPlaceNoteArray}), - {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 - ); - - const updateData = async ([newTitle, newNoteArray]) => { - try { - setPlaces({ui: 'saving'}); - // TODO #282: handle database access error - const response = await fetch('/api/places', { - method: 'PUT', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - id: selectedPlace.id, - properties: { - name: newTitle.children[0].text, // edited by user, not the one returned from Google Maps API server - note: newNoteArray, - }, - }), - }); - if (response.ok) { - const jsonResponse = await response.json(); - // // update user data - const newUserData = [...userData]; - newUserData[selectedPlaceIndex].properties = { - ...userData[selectedPlaceIndex].properties, - ...jsonResponse.properties, - }; - setUserData(newUserData); - setPlaces({ - ui: 'open', - }); - } else { - throw new Error('PUT request to /api/places has failed.'); - } - } catch (error) { - console.log(error); - } + let selectedPlaceNoteHtml; + if (typeof userData[selectedPlaceIndex].properties.note === 'string') { + // Tiptap format + selectedPlaceNoteHtml = DOMPurify.sanitize( + userData[selectedPlaceIndex].properties.note, + {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 + ); + } else { + // Slate format + const selectedPlaceNoteArray = + userData[selectedPlaceIndex].properties.note; + selectedPlaceNoteHtml = DOMPurify.sanitize( + `

${selectedPlaceName}

${getHtmlFromSlate( + {children: selectedPlaceNoteArray}, + )}
`, + {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 + ); + } + // for updating place info + const handleResponse = jsonResponse => { + const newUserData = [...userData]; + newUserData[selectedPlaceIndex].properties = { + ...userData[selectedPlaceIndex].properties, + ...jsonResponse.properties, + }; + setUserData(newUserData); + setPlaces({ + ui: 'open', + }); }; + // for deleting saved place const deletePlace = async () => { try { setDeleteUi('deleting'); @@ -271,6 +263,7 @@ export const SavedPlaces = ({mapObject}) => { console.log(error); } }; + // UI rendering if (ui === 'open' || ui === 'closing') { return ( <> @@ -292,12 +285,10 @@ export const SavedPlaces = ({mapObject}) => { ref={closeButton} testId="close-button-saved-place" /> -

{selectedPlaceName}

setPlaces({ui: 'editing'})} @@ -373,11 +364,15 @@ export const SavedPlaces = ({mapObject}) => { data-fullscreen role="dialog" > - setPlaces({ui: 'open'})} - updateData={updateData} + handleResponse={handleResponse} + setUi={setPlaces} /> diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 61a0848e..5836df65 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -10,8 +10,8 @@ import {CloseButton} from './CloseButton'; import {DivCloud} from 'src/elements/DivCloud'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; -import {PlaceInfoEditor} from './PlaceInfoEditor'; import {SpanRipple} from 'src/elements/SpanRipple'; +import {TiptapEditor} from './TiptapEditor'; import {useOnClickOutside} from 'src/hooks/useOnClickOutside'; import {useOnEscKeyDown} from 'src/hooks/useOnEscKeyDown'; @@ -191,89 +191,29 @@ export const SearchedPlace = ({mapObject}) => { handler: closePlaceInfo, }); + // for saving the searched place const openEditor = () => { setState({status: 'editing'}); }; const handleCancel = () => { setState({status: 'open'}); }; - - const updateData = async ([title, noteArray]) => { - try { - setState({status: 'saving'}); - // TODO #282: handle database access error - const response = await fetch('/api/places', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - geometry: { - coordinates: [placeData.coordinates.lng, placeData.coordinates.lat], - type: 'Point', - }, - properties: { - address: placeData.address, - 'Google Maps URL': placeData.url, - name: title.children[0].text, // edited by user, not the one returned from Google Maps API server - note: noteArray, - }, - type: 'Feature', - }), - }); - if (response.ok) { - const jsonResponse = await response.json(); - marker.current.setMap(null); // remove the searched place marker - setUserData([...userData, jsonResponse]); - setPlaceId(''); - setState({status: 'closed'}); - setPlaces({ - ui: 'open', - selectedPlace: { - id: jsonResponse.id, - coordinates: jsonResponse.coordinates, - }, - }); - } else { - throw new Error('POST request to /api/places has failed.'); - } - } catch (error) { - console.log(error); - } + const handleResponse = jsonResponse => { + marker.current.setMap(null); // remove the searched place marker + setUserData([...userData, jsonResponse]); + setPlaceId(''); + setState({status: 'closed'}); + setPlaces({ + ui: 'open', + selectedPlace: { + id: jsonResponse.id, + coordinates: jsonResponse.coordinates, + }, + }); }; const placeNameId = 'place-name'; const placeDetailId = 'place-detail'; - const placeNoteArray = placeData - ? [ - { - type: 'paragraph', - children: [ - { - text: placeData.address, - }, - ], - }, - { - type: 'paragraph', - children: [ - { - text: '', - }, - { - type: 'link', - url: placeData.url, - children: [ - { - text: linkText.searchedPlace, - }, - ], - }, - { - text: '', - }, - ], - }, - ] - : null; if (status === 'closed') { return null; @@ -341,11 +281,19 @@ export const SearchedPlace = ({mapObject}) => { data-fullscreen role="dialog" > - diff --git a/src/components/TiptapEditor.js b/src/components/TiptapEditor.js index 20642206..3713b584 100644 --- a/src/components/TiptapEditor.js +++ b/src/components/TiptapEditor.js @@ -1,14 +1,192 @@ +import Document from '@tiptap/extension-document'; +import Link from '@tiptap/extension-link'; +import {Placeholder} from '@tiptap/extension-placeholder'; import {useEditor, EditorContent} from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; -// import PropTypes from 'prop-types'; -export const TiptapEditor = () => { +import DOMPurify from 'dompurify'; + +import PropTypes from 'prop-types'; + +import {HeaderEditor} from 'src/elements/HeaderEditor'; +import {Heading} from 'src/elements/Heading'; + +import {buttonLabel, editorLabel} from 'src/utils/uiCopies'; + +const CustomDocument = Document.extend({ + content: 'heading block*', // turning the first element into

+}); + +export const TiptapEditor = ({ + data, + handleCancel, + handleResponse, + searchedPlace, + setUi, +}) => { + // Handling input + let content; + if (data.html) { + // saved place + content = DOMPurify.sanitize(data.html); + } else { + // searched place + content = { + type: 'doc', + content: [ + { + type: 'heading', + attrs: {level: 2}, + content: [{type: 'text', text: data.name}], + }, + { + type: 'paragraph', + content: [{type: 'text', text: data.address}], + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + marks: [ + { + type: 'link', + attrs: { + href: data.url, + target: '_blank', + class: null, + }, + }, + ], + text: data.linkText, + }, + ], + }, + ], + }; + } + + // Setting up text editor const editor = useEditor({ - extensions: [StarterKit], - content: '

Hello World! 🌎️

', + extensions: [ + CustomDocument, + Link.configure({ + HTMLAttributes: { + rel: 'noreferrer', // to override the default of "noopener noreferrer nofollow"; see https://tiptap.dev/api/marks/link + }, + }), + StarterKit.configure({ + document: false, // to use CustomDocument, not the default Document extension included in StarterKit + heading: { + levels: [2, 3], // turning the first element into

, not

+ }, + }), + Placeholder.configure({ + placeholder: ({node}) => { + if (node.type.name === 'heading') { + return 'Enter the place name'; // Shown when no place name is provided + } + + return 'Enter your notes on the place'; // Shown when no note is provided + }, + }), + ], + content: content, + autofocus: true, // so users can immediately start typing once the editor is opened + editorProps: { + attributes: { + role: 'textbox', // for accessibility: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/textbox_role + }, + }, + injectCSS: false, // remove the default style }); - return ; + // Handling output + const handleClickSave = async event => { + event.preventDefault(); + setUi(searchedPlace ? {status: 'saving'} : {ui: 'saving'}); + + const {content: userText} = editor.getJSON(); + const userPlaceName = userText[0].content[0].text; + + const userPlaceNote = DOMPurify.sanitize( + editor.getHTML(), + {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 + ); + + try { + const response = await fetch('/api/places', { + method: searchedPlace ? 'POST' : 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify( + searchedPlace + ? { + geometry: { + coordinates: [data.lng, data.lat], + type: 'Point', + }, + properties: { + address: data.address, + 'Google Maps URL': data.url, + name: userPlaceName, // edited by user, not the one returned from Google Maps API server + note: userPlaceNote, + }, + type: 'Feature', + } + : { + id: data.id, + properties: { + name: userPlaceName, // edited by user, not the one returned from Google Maps API server + note: userPlaceNote, + }, + }, + ), + }); + if (response.ok) { + const jsonResponse = await response.json(); + handleResponse(jsonResponse); + } else { + throw new Error( + searchedPlace + ? 'POST request to /api/places has failed.' + : 'PUT request to /api/places has failed.', + ); + } + } catch (error) { + // TODO #282: handle database access error + console.error(error); + } + }; + + return ( +
+ + + {editorLabel} + +
+ + +
+
+ + + ); }; -// TiptapEditor.propTypes = {}; +TiptapEditor.propTypes = { + data: PropTypes.object, + handleCancel: PropTypes.func, + handleResponse: PropTypes.func, + searchedPlace: PropTypes.bool, + setUi: PropTypes.func, +}; diff --git a/src/elements/DivPlaceInfoBackground.js b/src/elements/DivPlaceInfoBackground.js index 41c5594e..24ce31b0 100644 --- a/src/elements/DivPlaceInfoBackground.js +++ b/src/elements/DivPlaceInfoBackground.js @@ -141,6 +141,24 @@ const animateTransitionOut = css` } `; +const showPlaceholderTextInEditor = css` + /* Docs: https://tiptap.dev/api/extensions/placeholder#additional-setup */ + & .ProseMirror h2.is-empty::before, + & .ProseMirror p.is-empty:first-of-type::before { + color: var(--placeholder-text-color); + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; + } +`; + +const styleTextEditor = css` + /* Remove the browser's default focus-ring */ + & .ProseMirror:focus-visible { + outline-style: none; + } +`; export const DivPlaceInfoBackground = styled.div` ${setBackground} ${setInnerSize} @@ -149,6 +167,8 @@ export const DivPlaceInfoBackground = styled.div` ${setFontStyle} ${animateTransitionIn} ${animateTransitionOut} + ${showPlaceholderTextInEditor} + ${styleTextEditor} `; DivPlaceInfoBackground.Wrapper = styled.div` diff --git a/src/elements/DivPlaceInfoBackground.test.js b/src/elements/DivPlaceInfoBackground.test.js index 67eb12ef..59b3ce1e 100644 --- a/src/elements/DivPlaceInfoBackground.test.js +++ b/src/elements/DivPlaceInfoBackground.test.js @@ -99,6 +99,19 @@ describe('DivPlaceInfoBackground component', () => { animation-timing-function: linear; } +.c1 .ProseMirror h2.is-empty::before, +.c1 .ProseMirror p.is-empty:first-of-type::before { + color: var(--placeholder-text-color); + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; +} + +.c1 .ProseMirror:focus-visible { + outline-style: none; +} + .c0 { position: absolute; z-index: 2; From cb14d28885d9b9da2b7201e5201d5c2cd2e02e41 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 20 Mar 2023 08:06:24 +0900 Subject: [PATCH 13/28] fix(editor): add "nofollow" to the rel attribute of links in the user's notes prevent search engines from following the link to gauge the site's reputation --- cypress/e2e/saving-a-place.cy.js | 2 +- src/components/TiptapEditor.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/saving-a-place.cy.js b/cypress/e2e/saving-a-place.cy.js index 97f55a96..9fbe79ae 100644 --- a/cypress/e2e/saving-a-place.cy.js +++ b/cypress/e2e/saving-a-place.cy.js @@ -68,7 +68,7 @@ describe('Saving feature', () => { cy.log('...shows link text'); cy.findByRole('link', {name: linkText.searchedPlace}) .should('have.attr', 'target', '_blank') - .should('have.attr', 'rel', 'noreferrer') + .should('have.attr', 'rel', 'nofollow noreferrer') .then(link => { cy.request(link.prop('href')).its('status').should('eq', 200); }); diff --git a/src/components/TiptapEditor.js b/src/components/TiptapEditor.js index 3713b584..87f9dcdd 100644 --- a/src/components/TiptapEditor.js +++ b/src/components/TiptapEditor.js @@ -72,8 +72,10 @@ export const TiptapEditor = ({ CustomDocument, Link.configure({ HTMLAttributes: { - rel: 'noreferrer', // to override the default of "noopener noreferrer nofollow"; see https://tiptap.dev/api/marks/link + rel: 'nofollow noreferrer', // to override the default of "noopener noreferrer nofollow"; see https://tiptap.dev/api/marks/link; }, + // for nofollow, see https://developers.google.com/search/docs/fundamentals/seo-starter-guide?hl=en&visit_id=638148628535504987-2673944290&rd=1#linkwithcaution + // we drop noopener as we don't need to support legacy browsers (Google Maps doesn't support them); for modern browsers, noreferrer implies noopener; see https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer }), StarterKit.configure({ document: false, // to use CustomDocument, not the default Document extension included in StarterKit From ddeb9ab1816364666699b660b84f8d75223c4295 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 20 Mar 2023 08:09:37 +0900 Subject: [PATCH 14/28] fix(editor): add aria-multiline attribute to the text editor tell screen readers that the Enter key inserts a line break rather than submitting the form --- cypress/e2e/saved-places.cy.js | 4 ++++ src/components/TiptapEditor.js | 1 + 2 files changed, 5 insertions(+) diff --git a/cypress/e2e/saved-places.cy.js b/cypress/e2e/saved-places.cy.js index 51e76dcf..cd3458f0 100644 --- a/cypress/e2e/saved-places.cy.js +++ b/cypress/e2e/saved-places.cy.js @@ -50,6 +50,10 @@ describe('Saved place detail feature', () => { ); cy.log(`...autofocuses the note`); cy.focused().should('have.attr', 'role', 'textbox'); + cy.log( + `...tells screen readers that pressing the Enter key will insert a line break, not submit the entered text`, + ); + cy.focused().should('have.attr', 'aria-multiline', 'true'); cy.log(`Typing text...`); // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/src/components/TiptapEditor.js b/src/components/TiptapEditor.js index 87f9dcdd..51b8c0d6 100644 --- a/src/components/TiptapEditor.js +++ b/src/components/TiptapEditor.js @@ -98,6 +98,7 @@ export const TiptapEditor = ({ editorProps: { attributes: { role: 'textbox', // for accessibility: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/textbox_role + 'aria-multiline': 'true', // for accessibility: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-multiline }, }, injectCSS: false, // remove the default style From 1223683a677dbfdac11fb0c81f92479724f53605 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 20 Mar 2023 08:43:49 +0900 Subject: [PATCH 15/28] fix(editor): label
element assign the ARIA landmark role of "form"; let screen readers know the purpose of the text editor --- cypress/e2e/saving-a-place.cy.js | 1 + src/components/TiptapEditor.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/saving-a-place.cy.js b/cypress/e2e/saving-a-place.cy.js index 9fbe79ae..29823733 100644 --- a/cypress/e2e/saving-a-place.cy.js +++ b/cypress/e2e/saving-a-place.cy.js @@ -38,6 +38,7 @@ describe('Saving feature', () => { cy.log('Clicking the save button on the searched place detail popup'); cy.findByRole('button', {name: buttonLabel.saveSearchedPlace}).click(); cy.log('...shows the text editor'); + cy.findByRole('form', {name: editorLabel}).should('be.visible'); // see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/form_role#description cy.findByRole('heading', {name: editorLabel}).should('be.visible'); cy.findByRole('textbox').contains(searchedPlace.name); cy.findByRole('textbox').contains(searchedPlace.address); diff --git a/src/components/TiptapEditor.js b/src/components/TiptapEditor.js index 51b8c0d6..1ba13562 100644 --- a/src/components/TiptapEditor.js +++ b/src/components/TiptapEditor.js @@ -162,9 +162,9 @@ export const TiptapEditor = ({ }; return ( - + - + {editorLabel}
From 115dd9af75bff86bb90deb920641ba4fbeb3b994 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Tue, 21 Mar 2023 07:13:58 +0900 Subject: [PATCH 16/28] fix(saving-a-place): allow users to close the place detail popup immediately after saving it Previously, we failed to set the coordinate of a newly saved place while saving it, which caused the bug. fix #366 --- cypress/e2e/saving-a-place.cy.js | 5 +++++ src/components/SearchedPlace.js | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/saving-a-place.cy.js b/cypress/e2e/saving-a-place.cy.js index 29823733..8d047709 100644 --- a/cypress/e2e/saving-a-place.cy.js +++ b/cypress/e2e/saving-a-place.cy.js @@ -73,6 +73,11 @@ describe('Saving feature', () => { .then(link => { cy.request(link.prop('href')).its('status').should('eq', 200); }); + cy.log('...allows user to close the popup'); + cy.findByRole('button', {name: buttonLabel.closePlaceDetail}).click(); + cy.findByRole('heading', {name: searchedPlace.name}).should( + 'not.exist', + ); }); }); it('cancel button', () => { diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 5836df65..553ca609 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -207,7 +207,10 @@ export const SearchedPlace = ({mapObject}) => { ui: 'open', selectedPlace: { id: jsonResponse.id, - coordinates: jsonResponse.coordinates, + coordinates: { + lat: jsonResponse.geometry.coordinates[1], + lng: jsonResponse.geometry.coordinates[0], + }, }, }); }; From 3d3b468604784182ea4119a881ad1b90db1d0778 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Wed, 22 Mar 2023 08:08:46 +0900 Subject: [PATCH 17/28] fix(editor): show "Unnamed place" as place name if user leaves it empty prevent the app from crashing if user doesn't enter any text for place name fix #143 --- cypress/e2e/all.cy.js | 1 + cypress/e2e/editor.cy.js | 33 +++++++++++++++++++++++++++++++++ src/components/TiptapEditor.js | 28 ++++++++++++++++++++-------- 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 cypress/e2e/editor.cy.js diff --git a/cypress/e2e/all.cy.js b/cypress/e2e/all.cy.js index 37afc092..dd6882c8 100644 --- a/cypress/e2e/all.cy.js +++ b/cypress/e2e/all.cy.js @@ -3,6 +3,7 @@ import './auth.cy'; import './background.cy'; +import './editor.cy'; import './geolocation.cy'; import './landmarks.cy'; import './menu.cy'; diff --git a/cypress/e2e/editor.cy.js b/cypress/e2e/editor.cy.js new file mode 100644 index 00000000..f994379f --- /dev/null +++ b/cypress/e2e/editor.cy.js @@ -0,0 +1,33 @@ +import {buttonLabel} from '../../src/utils/uiCopies'; + +const placeName = '出逢ひ茶屋おせん'; +describe('Editor feature', () => { + beforeEach(() => { + cy.log('Resetting the database'); + cy.exec('npx prisma migrate reset --force'); // https://docs.cypress.io/guides/end-to-end-testing/testing-your-app#Seeding-data + cy.log('Setting up'); + cy.auth('subscribed_user2', { + username: Cypress.env('auth0UserSubscribed2'), + password: Cypress.env('auth0PassSubscribed2'), + }); + cy.visit('/'); + cy.waitForMapToLoad(); + cy.log('Opening the editor'); + cy.findByRole('button', {name: placeName}).click(); + cy.findByRole('button', {name: buttonLabel.edit}).click(); + }); + it(`Saving a note without place name will show 'Unnamed place' as place name`, () => { + cy.log('Preparing for deleting the whole place name'); + const del = '{del}'; + let keystroke = ''; + for (let i = 0; i < placeName.length; i++) { + keystroke = del + keystroke; + } + cy.log('Deleting place name and saving the note...'); + cy.findByRole('textbox').type(keystroke); + cy.findByText(placeName).should('not.exist'); // verify if above commands work + cy.findByRole('button', {name: buttonLabel.saveEdit}).click(); + cy.log(`...shows 'Unnamed place' as its place name`); + cy.findByText('Unnamed place').should('be.visible'); + }); +}); diff --git a/src/components/TiptapEditor.js b/src/components/TiptapEditor.js index 1ba13562..6e0d1d94 100644 --- a/src/components/TiptapEditor.js +++ b/src/components/TiptapEditor.js @@ -108,14 +108,26 @@ export const TiptapEditor = ({ const handleClickSave = async event => { event.preventDefault(); setUi(searchedPlace ? {status: 'saving'} : {ui: 'saving'}); - - const {content: userText} = editor.getJSON(); - const userPlaceName = userText[0].content[0].text; - - const userPlaceNote = DOMPurify.sanitize( - editor.getHTML(), - {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 - ); + // retrieve user's note + const json = editor.getJSON(); + let userPlaceName, userPlaceNote; + if (json.content[0].content) { + // User has provided place name + userPlaceName = json.content[0].content[0].text; + userPlaceNote = DOMPurify.sanitize( + editor.getHTML(), + {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 + ); + } else { + // User has failed to provide place name + userPlaceName = 'Unnamed place'; + json.content[0].content = [{type: 'text', text: userPlaceName}]; // fill in to

, which would otherwise be empty + const {generateHTML} = await import('@tiptap/core'); // import here as it's needed only if user fails to provide place name. + userPlaceNote = DOMPurify.sanitize( + generateHTML(json, [StarterKit]), // docs: https://tiptap.dev/api/utilities/html#generate-html-from-json + {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 + ); // editor.getHTML() would include an empty

element whose text content (apparently) cannot be modified... + } try { const response = await fetch('/api/places', { From f9ea0af590b5bf582b391b8a42e12632509071ea Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Thu, 23 Mar 2023 09:33:58 +0900 Subject: [PATCH 18/28] perf(editor): import only when necessary reduce first load JS for the index page by 98kB fix #251 --- src/components/SavedPlaces.js | 14 ++++++-------- src/components/SearchedPlace.js | 11 ++++++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index c10a581e..401cf960 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -12,7 +12,6 @@ import {DivModalBackdrop} from 'src/elements/DivModalBackdrop'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {SpanRipple} from 'src/elements/SpanRipple'; -import {TiptapEditor} from './TiptapEditor'; import {ClientOnlyPortal} from 'src/wrappers/ClientOnlyPortal'; @@ -24,12 +23,11 @@ import {NightModeContext} from 'src/wrappers/NightModeContext'; import {buttonLabel, loadingMessage, modal} from 'src/utils/uiCopies'; +// import Tiptap only when necessary import dynamic from 'next/dynamic'; -const importPlaceInfoEditor = () => - import('src/components/PlaceInfoEditor').then( - module => module.PlaceInfoEditor, - ); -const PlaceInfoEditor = dynamic(importPlaceInfoEditor); +const importTiptapEditor = () => + import('src/components/TiptapEditor').then(module => module.TiptapEditor); +const TiptapEditor = dynamic(importTiptapEditor); // Prepare for converting URL text into link const autolinker = new Autolinker({ @@ -292,8 +290,8 @@ export const SavedPlaces = ({mapObject}) => { /> setPlaces({ui: 'editing'})} - onFocus={importPlaceInfoEditor} - onMouseEnter={importPlaceInfoEditor} + onFocus={importTiptapEditor} + onMouseEnter={importTiptapEditor} type="button" > {buttonLabel.edit} diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 553ca609..ab571c0d 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -11,7 +11,6 @@ import {DivCloud} from 'src/elements/DivCloud'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {SpanRipple} from 'src/elements/SpanRipple'; -import {TiptapEditor} from './TiptapEditor'; import {useOnClickOutside} from 'src/hooks/useOnClickOutside'; import {useOnEscKeyDown} from 'src/hooks/useOnEscKeyDown'; @@ -20,6 +19,12 @@ import {useStateObject} from 'src/hooks/useStateObject'; import {buttonLabel, linkText, loadingMessage} from 'src/utils/uiCopies'; +// import Tiptap only when necessary +import dynamic from 'next/dynamic'; +const importTiptapEditor = () => + import('src/components/TiptapEditor').then(module => module.TiptapEditor); +const TiptapEditor = dynamic(importTiptapEditor); + export const SearchedPlace = ({mapObject}) => { const [placeId, setPlaceId] = useContext(PlaceIdContext); const nightMode = useContext(NightModeContext); @@ -254,8 +259,8 @@ export const SearchedPlace = ({mapObject}) => {

{buttonLabel.saveSearchedPlace} From 61707dcfb63dd8f9b3a7709827be47bd9eaf6b52 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Thu, 23 Mar 2023 10:06:57 +0900 Subject: [PATCH 19/28] refactor(editor): use for the loading message during saving notes reduce code duplication --- src/components/SavedPlaces.js | 14 +++++++++++--- src/components/SearchedPlace.js | 15 +++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index 401cf960..affaec2f 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -378,9 +378,17 @@ export const SavedPlaces = ({mapObject}) => { ); } else if (ui === 'saving') { return ( - - {loadingMessage.update} - + + + + {loadingMessage.update} + + + ); } } diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index ab571c0d..08f9dc3b 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -7,7 +7,6 @@ import {PlaceIdContext} from 'src/wrappers/PlaceIdContext'; import {ButtonDialog} from 'src/elements/ButtonDialog'; import {CloseButton} from './CloseButton'; -import {DivCloud} from 'src/elements/DivCloud'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; import {SpanRipple} from 'src/elements/SpanRipple'; @@ -309,9 +308,17 @@ export const SearchedPlace = ({mapObject}) => { ); } else if (status === 'saving') { return ( - - {loadingMessage.create} - + + + + {loadingMessage.create} + + + ); } }; From 8d864f29f91f8210a7c83b2522ac5ca1dc6ec9c0 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Fri, 24 Mar 2023 08:51:21 +0900 Subject: [PATCH 20/28] fix(editor,savedplaces,searchedplace): center-align content for wide screens allow users to keep seeing the center of the screen, rather than looking at the left edge and then turn to the right edge to close the popup etc. --- src/components/SavedPlaces.js | 35 +++++++++++---------- src/components/SearchedPlace.js | 34 ++++++++++---------- src/elements/DivPlaceInfoBackground.js | 12 ++++++- src/elements/DivPlaceInfoBackground.test.js | 7 ++++- 4 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index affaec2f..ee9331c0 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -283,22 +283,25 @@ export const SavedPlaces = ({mapObject}) => { ref={closeButton} testId="close-button-saved-place" /> -
- setPlaces({ui: 'editing'})} - onFocus={importTiptapEditor} - onMouseEnter={importTiptapEditor} - type="button" - > - {buttonLabel.edit} - - - {buttonLabel.delete} - +
+
+ setPlaces({ui: 'editing'})} + onFocus={importTiptapEditor} + onMouseEnter={importTiptapEditor} + type="button" + > + {buttonLabel.edit} + + + {buttonLabel.delete} + +
+ {ui === 'closing' ? ( { ref={closeButton} testId="close-button-saved-place" /> -

{placeData.name}

-
-

{placeData.address}

-

- - {linkText.searchedPlace} - -

+
+

{placeData.name}

+
+

{placeData.address}

+

+ + {linkText.searchedPlace} + +

+
+ + {buttonLabel.saveSearchedPlace} +
- - {buttonLabel.saveSearchedPlace} - {status === 'closing' ? ( div" for searched and saved place info popup */ + /* "& > form" for text editor */ + & > div, + & > form { + margin: 0 auto; + max-width: ${dimension.searchBox['max-width']}; + } +`; + const positionComponents = ` ${positionCloseButton} ${positionOtherButtons} + ${centerAlignContent} & h2, & p { --close-button-width: calc(${ dimension.button['minimum target size 100'] } + ${dimension.button['minimum target spacing 100']} * 2); - max-width: ${dimension.searchBox['max-width']}; width: calc(100% - var(--close-button-width)); } diff --git a/src/elements/DivPlaceInfoBackground.test.js b/src/elements/DivPlaceInfoBackground.test.js index 59b3ce1e..08511846 100644 --- a/src/elements/DivPlaceInfoBackground.test.js +++ b/src/elements/DivPlaceInfoBackground.test.js @@ -52,10 +52,15 @@ describe('DivPlaceInfoBackground component', () => { margin-left: 8px; } +.c1 > div, +.c1 > form { + margin: 0 auto; + max-width: 561px; +} + .c1 h2, .c1 p { --close-button-width: calc(48px + 8px * 2); - max-width: 561px; width: calc(100% - var(--close-button-width)); } From fc0316e6f463a983fce33eac69b5a9659328399f Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Fri, 24 Mar 2023 08:57:08 +0900 Subject: [PATCH 21/28] fix(editor): show loading message when user opens text editor for the first time assure users that the app is working rather than being frozen --- src/components/SavedPlaces.js | 4 +++- src/components/SearchedPlace.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index ee9331c0..ff3855fb 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -27,7 +27,9 @@ import {buttonLabel, loadingMessage, modal} from 'src/utils/uiCopies'; import dynamic from 'next/dynamic'; const importTiptapEditor = () => import('src/components/TiptapEditor').then(module => module.TiptapEditor); -const TiptapEditor = dynamic(importTiptapEditor); +const TiptapEditor = dynamic(importTiptapEditor, { + loading: () => Loading text editor..., +}); // Prepare for converting URL text into link const autolinker = new Autolinker({ diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index d50a4e89..ef155904 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -22,7 +22,9 @@ import {buttonLabel, linkText, loadingMessage} from 'src/utils/uiCopies'; import dynamic from 'next/dynamic'; const importTiptapEditor = () => import('src/components/TiptapEditor').then(module => module.TiptapEditor); -const TiptapEditor = dynamic(importTiptapEditor); +const TiptapEditor = dynamic(importTiptapEditor, { + loading: () => Loading text editor..., +}); export const SearchedPlace = ({mapObject}) => { const [placeId, setPlaceId] = useContext(PlaceIdContext); From f74cdd3231876d13bd8bf991a9d1d4376f1f600c Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sun, 26 Mar 2023 08:11:14 +0900 Subject: [PATCH 22/28] fix(searchedplace, savedplaces): use aria-describedby to refer to the place info for screen readers With TipTap, adding an id attribute to

only is not straightforward; wrapping the rest of elements with
with an id attribute is also tricky. As a workaround, we use aria-describedby to refer to the
that wraps the entire note including

. re #431 --- src/components/SavedPlaces.js | 12 +++++------- src/components/SearchedPlace.js | 12 ++++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/components/SavedPlaces.js b/src/components/SavedPlaces.js index ff3855fb..020825c5 100644 --- a/src/components/SavedPlaces.js +++ b/src/components/SavedPlaces.js @@ -215,9 +215,9 @@ export const SavedPlaces = ({mapObject}) => { const selectedPlaceNoteArray = userData[selectedPlaceIndex].properties.note; selectedPlaceNoteHtml = DOMPurify.sanitize( - `

${selectedPlaceName}

${getHtmlFromSlate( - {children: selectedPlaceNoteArray}, - )}
`, + `

${selectedPlaceName}

${getHtmlFromSlate({ + children: selectedPlaceNoteArray, + })}
`, {ADD_ATTR: ['target']}, // see https://github.com/cure53/DOMPurify/issues/317#issuecomment-470429778 ); } @@ -274,7 +274,6 @@ export const SavedPlaces = ({mapObject}) => { { ref={closeButton} testId="close-button-saved-place" /> -
+
{ diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index ef155904..0db63215 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -221,9 +221,6 @@ export const SearchedPlace = ({mapObject}) => { }); }; - const placeNameId = 'place-name'; - const placeDetailId = 'place-detail'; - if (status === 'closed') { return null; } else if (status === 'loading') { @@ -237,8 +234,7 @@ export const SearchedPlace = ({mapObject}) => { onAnimationEnd={handleAnimationEnd} > { ref={closeButton} testId="close-button-saved-place" /> -
-

{placeData.name}

-
+
+

{placeData.name}

+

{placeData.address}

From 8586781568d2392fda7036cde64a70cfa49112ed Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sun, 26 Mar 2023 08:29:28 +0900 Subject: [PATCH 23/28] fix(search): show a loading message while retrieving place detail information from Google Maps let users with slow network connections learn that the app is working hard, rather than not being responsive fix #208 --- cypress/e2e/search.cy.js | 116 ++++++++++++++++++-------------- src/components/SearchedPlace.js | 14 +++- src/utils/uiCopies.js | 1 + 3 files changed, 81 insertions(+), 50 deletions(-) diff --git a/cypress/e2e/search.cy.js b/cypress/e2e/search.cy.js index 2cceb4de..a3986e9f 100644 --- a/cypress/e2e/search.cy.js +++ b/cypress/e2e/search.cy.js @@ -1,4 +1,10 @@ -import {buttonLabel, linkText, searchBoxLabel} from '../../src/utils/uiCopies'; +import {interceptIndefinitely} from '../../test/utils/cypress'; +import { + buttonLabel, + linkText, + loadingMessage, + searchBoxLabel, +} from '../../src/utils/uiCopies'; import { boldText, minPopupWidth, @@ -52,6 +58,10 @@ describe('Place search feature', () => { cy.findByRole('combobox').should('be.visible'); // Wait for search box module to be loaded; Cypress may not wait before starting to type. See https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ }); it('happy path for mobile/mouse users', () => { + cy.log(`Preparing for testing loading messages`); + const interception = interceptIndefinitely( + 'https://maps.googleapis.com/maps/api/place/js/PlaceService.GetPlaceDetails', + ); cy.log('Typing a place name...'); cy.focused().realType(searchWords[0].source); cy.log('...shows autocomplete suggesstions'); @@ -71,58 +81,66 @@ describe('Place search feature', () => { cy.focused().realPress('Space').realType(searchWords[1].source); cy.log('Selecting one of the autocomplete suggestions'); cy.findByRole('option', {name: placeName, timeout: 20000}).click(); - cy.log('...Shows the place on the map'); - cy.findByRole('button', {name: placeName}).should('be.visible'); - cy.log('...Shows the place info'); - cy.findByRole('heading', {name: placeName}).should('be.visible'); - cy.findByText(placeAddress).should('be.visible'); - cy.log( - '...Shows the working link to open Google Maps in a new tab for more detailed information', - ); - cy.findByText(linkText.searchedPlace) - .should('have.attr', 'target', '_blank') - .should('have.attr', 'rel', 'noreferrer') - .then(link => { - cy.request(link.prop('href')).its('status').should('eq', 200); - }); - // TODO #207: Make the following test pass - // cy.log('...Focuses the close button'); - // cy.focused().should( - // 'have.attr', - // 'aria-label', - // buttonLabel.closePlaceDetail, - // ); + cy.log('...initially shows a loading message'); + cy.findByText(loadingMessage.search) + .should('be.visible') + .then(() => { + cy.log(`And then...`); + interception.sendResponse(); - cy.log('Clicking the close button closes the place info'); - cy.findByRole('button', {name: buttonLabel.closePlaceDetail}).click(); - cy.findByRole('heading', {name: placeName}).should('not.exist'); + cy.log('...Shows the place on the map'); + cy.findByRole('button', {name: placeName}).should('be.visible'); + cy.log('...Shows the place info'); + cy.findByRole('heading', {name: placeName}).should('be.visible'); + cy.findByText(placeAddress).should('be.visible'); + cy.log( + '...Shows the working link to open Google Maps in a new tab for more detailed information', + ); + cy.findByText(linkText.searchedPlace) + .should('have.attr', 'target', '_blank') + .should('have.attr', 'rel', 'noreferrer') + .then(link => { + cy.request(link.prop('href')).its('status').should('eq', 200); + }); + // TODO #207: Make the following test pass + // cy.log('...Focuses the close button'); + // cy.focused().should( + // 'have.attr', + // 'aria-label', + // buttonLabel.closePlaceDetail, + // ); - cy.log('Clicking the place on the map...'); - cy.findByRole('button', {name: placeName}).click(); - cy.log('...reopens the place info'); - cy.findByRole('heading', {name: placeName}).should('be.visible'); - cy.log('...focuses the close button'); - cy.focused().should( - 'have.attr', - 'aria-label', - buttonLabel.closePlaceDetail, - ); + cy.log('Clicking the close button closes the place info'); + cy.findByRole('button', {name: buttonLabel.closePlaceDetail}).click(); + cy.findByRole('heading', {name: placeName}).should('not.exist'); - cy.log('Pressing Esc key closes the place info'); - cy.get('body').type('{esc}'); - cy.findByRole('heading', {name: placeName}).should('not.exist'); + cy.log('Clicking the place on the map...'); + cy.findByRole('button', {name: placeName}).click(); + cy.log('...reopens the place info'); + cy.findByRole('heading', {name: placeName}).should('be.visible'); + cy.log('...focuses the close button'); + cy.focused().should( + 'have.attr', + 'aria-label', + buttonLabel.closePlaceDetail, + ); - cy.log('Searching another place...'); - cy.findByRole('button', {name: buttonLabel.search}).click(); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(100); // otherwise, Cypress will type 'bc', not 'abc'. This is a known issue. See https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ - cy.focused().realType('fukuda art museum'); - cy.findByRole('option', { - name: /fukuda art museum/i, - timeout: 20000, - }).click(); - cy.log('...removes the place mark for the previous search'); - cy.findByRole('button', {name: placeName}).should('not.exist'); + cy.log('Pressing Esc key closes the place info'); + cy.get('body').type('{esc}'); + cy.findByRole('heading', {name: placeName}).should('not.exist'); + + cy.log('Searching another place...'); + cy.findByRole('button', {name: buttonLabel.search}).click(); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(100); // otherwise, Cypress will type 'bc', not 'abc'. This is a known issue. See https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ + cy.focused().realType('fukuda art museum'); + cy.findByRole('option', { + name: /fukuda art museum/i, + timeout: 20000, + }).click(); + cy.log('...removes the place mark for the previous search'); + cy.findByRole('button', {name: placeName}).should('not.exist'); + }); }); it('keeps displaying autocomplete suggestions when blurring the search box', () => { cy.log('Setup: Type a place name'); diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index 0db63215..e712eede 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -224,7 +224,19 @@ export const SearchedPlace = ({mapObject}) => { if (status === 'closed') { return null; } else if (status === 'loading') { - return null; // TODO #208: render loading spinner or its equivalent + return ( + + + + {loadingMessage.search} + + + + ); } else if (status === 'error') { return null; // TODO #199: Handle error properly } else if (status === 'open' || status === 'closing') { diff --git a/src/utils/uiCopies.js b/src/utils/uiCopies.js index 340dcaac..63c78647 100644 --- a/src/utils/uiCopies.js +++ b/src/utils/uiCopies.js @@ -1,5 +1,6 @@ export const loadingMessage = { create: 'Saving your place note...', + search: 'Getting more information about this place...', update: 'Saving changes...', delete: placeName => `Deleting ${placeName}...`, }; From 4a27a13d76b250c37f399357895f8334f83102ac Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Thu, 30 Mar 2023 08:21:05 +0900 Subject: [PATCH 24/28] refactor(search): deal the happy path first and the sad path next for Place Detail API response improve code readability --- src/components/SearchedPlace.js | 141 ++++++++++++++++---------------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index e712eede..f3bd10e0 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -71,81 +71,82 @@ export const SearchedPlace = ({mapObject}) => { }; service.getDetails(request, handleResponse); function handleResponse(place, status) { - if (status !== 'OK' || !place) { - // TODO #199: Handle error more properly - console.error('Google Maps Place Details API call has failed.'); - setState({status: 'error', error: status}); - } - // Retrieve data to be used - const searchedPlace = { - address: place.formatted_address, - // TODO #197 businessStatus: place.business_status, - coordinates: { - lat: place.geometry.location.lat(), - lng: place.geometry.location.lng(), - }, - name: place.name, - url: place.url, - }; + if (status === 'OK') { + // Retrieve data to be used + const searchedPlace = { + address: place.formatted_address, + // TODO #197 businessStatus: place.business_status, + coordinates: { + lat: place.geometry.location.lat(), + lng: place.geometry.location.lng(), + }, + name: place.name, + url: place.url, + }; - // style marker - const plusSignIcon = { - // source: Material Icons Add: https://fonts.google.com/icons?selected=Material%20Icons%20Outlined%3Aadd%3A - height: 24, - path: ` + // style marker + const plusSignIcon = { + // source: Material Icons Add: https://fonts.google.com/icons?selected=Material%20Icons%20Outlined%3Aadd%3A + height: 24, + path: ` M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z `, - width: 24, - }; - const shapedAsPlusSign = { - path: plusSignIcon.path, - }; - const pinnedAtCenter = { - anchor: new google.maps.Point( - plusSignIcon.width / 2, - plusSignIcon.height / 2, - ), // to pin the icon at its center, rather than at its top-left (default) - }; - const peach = nightMode - ? { - fillColor: '#e31081', // hue 328; chroma 82.75; luminance 4.66 - strokeColor: '#fed0e8', // hue 329; chroma 18.04; luminance 15.4 - } - : { - fillColor: '#cf5673', // hue 346, chroma 47.45, luminance 5.23 - strokeColor: '#82001c', // hue 347, chroma 50.98, luminance 1.97 - }; - const colored = { - fillOpacity: 1, // to disable the default value of 0 - ...peach, - }; - const scaled = { - scale: 2, - }; - marker.current = new google.maps.Marker({ - icon: { - ...shapedAsPlusSign, - ...pinnedAtCenter, - ...colored, - ...scaled, - }, - optimized: false, - position: searchedPlace.coordinates, - title: searchedPlace.name, - }); - // eslint-disable-next-line no-loop-func - marker.current.addListener('click', () => { + width: 24, + }; + const shapedAsPlusSign = { + path: plusSignIcon.path, + }; + const pinnedAtCenter = { + anchor: new google.maps.Point( + plusSignIcon.width / 2, + plusSignIcon.height / 2, + ), // to pin the icon at its center, rather than at its top-left (default) + }; + const peach = nightMode + ? { + fillColor: '#e31081', // hue 328; chroma 82.75; luminance 4.66 + strokeColor: '#fed0e8', // hue 329; chroma 18.04; luminance 15.4 + } + : { + fillColor: '#cf5673', // hue 346, chroma 47.45, luminance 5.23 + strokeColor: '#82001c', // hue 347, chroma 50.98, luminance 1.97 + }; + const colored = { + fillOpacity: 1, // to disable the default value of 0 + ...peach, + }; + const scaled = { + scale: 2, + }; + marker.current = new google.maps.Marker({ + icon: { + ...shapedAsPlusSign, + ...pinnedAtCenter, + ...colored, + ...scaled, + }, + optimized: false, + position: searchedPlace.coordinates, + title: searchedPlace.name, + }); + // eslint-disable-next-line no-loop-func + marker.current.addListener('click', () => { + mapObject.panTo(searchedPlace.coordinates); + mapObject.panBy(0, viewportSize.current.height / 4); + setState({status: 'open'}); + }); + + // render marker + marker.current.setMap(mapObject); + // snap the map to the marker mapObject.panTo(searchedPlace.coordinates); mapObject.panBy(0, viewportSize.current.height / 4); - setState({status: 'open'}); - }); - - // render marker - marker.current.setMap(mapObject); - // snap the map to the marker - mapObject.panTo(searchedPlace.coordinates); - mapObject.panBy(0, viewportSize.current.height / 4); - setState({status: 'open', placeData: searchedPlace}); + setState({status: 'open', placeData: searchedPlace}); + } else { + // TODO #199: Handle error more properly + console.error('Google Maps Place Details API call has failed.'); + setState({status: 'error', error: status}); + } } }, [mapObject, nightMode, placeId, setState]); From dd8b67629d78dad1ed8fa7dd58c68d0ade2bd644 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Sun, 2 Apr 2023 09:16:21 +0900 Subject: [PATCH 25/28] fix(search): show an error message when Place Detail API fails let users know what is going on in this case fix #199 --- cypress/e2e/search.cy.js | 48 ++++++ src/components/PlaceDetailErrorMessage.js | 46 ++++++ .../PlaceDetailErrorMessage.test.js | 146 ++++++++++++++++++ src/components/SearchedPlace.js | 42 ++++- src/elements/DivPlaceInfoBackground.js | 18 ++- src/elements/DivPlaceInfoBackground.test.js | 37 +++++ src/utils/uiCopies.js | 7 + 7 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 src/components/PlaceDetailErrorMessage.js create mode 100644 src/components/PlaceDetailErrorMessage.test.js diff --git a/cypress/e2e/search.cy.js b/cypress/e2e/search.cy.js index a3986e9f..96e3b1d4 100644 --- a/cypress/e2e/search.cy.js +++ b/cypress/e2e/search.cy.js @@ -1,6 +1,7 @@ import {interceptIndefinitely} from '../../test/utils/cypress'; import { buttonLabel, + errorMessage, linkText, loadingMessage, searchBoxLabel, @@ -231,3 +232,50 @@ describe('Place search feature', () => { }).should('not.exist'); }); }); +describe('Sad path', () => { + beforeEach(() => { + cy.log('Loading app'); + cy.auth(); + cy.visit('/'); + cy.waitForMapToLoad(); + cy.log('Opening the search box'); + cy.findByRole('button', {name: buttonLabel.search}).click(); + cy.findByRole('combobox').should('be.visible'); // Wait for search box module to be loaded; Cypress may not wait before starting to type. See https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ + }); + it('Shows error message when Place Detail API fails', () => { + cy.log(`Mocking API failure`); + cy.intercept( + 'https://maps.googleapis.com/maps/api/place/js/PlaceService.GetPlaceDetails*', + request => { + console.log(`cy.intercept is running`); + const searchParams = new URLSearchParams(request.url); + const callbackParam = searchParams.get('callback'); + request.reply( + `${callbackParam} && ${callbackParam}(${JSON.stringify({ + status: 'UNKNOWN_ERROR', + })})`, + ); + }, + ).as('PlaceDetailsAPI'); + cy.log('Typing a place name and...'); + cy.focused() + .realType(searchWords[0].source) + .realPress('Space') + .realType(searchWords[1].source); + cy.log('Selecting one of the autocomplete suggestions'); + cy.findByRole('option', {name: placeName, timeout: 20000}).click(); + cy.log('...shows an error message'); + cy.findByRole('alertdialog', { + name: errorMessage.placeDetails.title, + }).should('be.visible'); + cy.log('...autofocuses the Got It button'); + cy.focused().should('have.attr', 'type', 'button'); + cy.focused().should('have.text', buttonLabel.handleError); + cy.log('...traps focus between link text and the Got It button'); + cy.realPress('Tab'); + cy.focused().should('have.attr', 'target', '_blank'); + cy.realPress('Tab'); + cy.focused().should('have.attr', 'type', 'button'); + cy.focused().should('have.text', buttonLabel.handleError); + }); +}); diff --git a/src/components/PlaceDetailErrorMessage.js b/src/components/PlaceDetailErrorMessage.js new file mode 100644 index 00000000..277c75b2 --- /dev/null +++ b/src/components/PlaceDetailErrorMessage.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const PlaceDetailErrorMessage = ({status}) => { + // Docs on "status": https://developers.google.com/maps/documentation/javascript/reference/places-service#PlacesServiceStatus + // TODO: #422 improve error messages + return status === '' || status === 'OK' ? null : status === 'ZERO_RESULTS' || + status === 'INVALID_REQUEST' || + status === 'NOT_FOUND' ? ( + // TODO: #263 Include "Contact us" button + <> +

+ Google Maps server appears to be not operating correctly at this moment. +

+

+ Please try again. If you have seen this message for the second time, + please contact us so we can fix the problem. +

+ + ) : status === 'OVER_QUERY_LIMIT' || status === 'REQUEST_DENIED' ? ( + // TODO: #263 Include "Contact us" button + <> +

My Ideal Map is currently unable to use Google Maps search.

+

Please contact us so we can fix the problem.

+ + ) : ( + // This last case includes status === "UNKNOWN_ERROR" + <> +

Google Maps server is currently down.

+

+ + Please check its status + + , and try again once they fix the problem (usually within a few hours). +

+ + ); +}; + +PlaceDetailErrorMessage.propTypes = { + status: PropTypes.string, +}; diff --git a/src/components/PlaceDetailErrorMessage.test.js b/src/components/PlaceDetailErrorMessage.test.js new file mode 100644 index 00000000..bc9ca925 --- /dev/null +++ b/src/components/PlaceDetailErrorMessage.test.js @@ -0,0 +1,146 @@ +// eslint-disable-next-line no-unused-vars +import {render, screen} from '@testing-library/react'; +import {axe} from 'jest-axe'; + +import {PlaceDetailErrorMessage} from './PlaceDetailErrorMessage'; + +const mockProps = { + status: '', +}; + +describe(`Display relevant error messages by prop value`, () => { + test(`Default`, () => { + const {container} = render(); + expect(container).toMatchInlineSnapshot(`
`); + }); + test(`OK`, () => { + const {container} = render(); + expect(container).toMatchInlineSnapshot(`
`); + }); + test(`ZERO_RESULTS`, () => { + const {container} = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+

+ Google Maps server appears to be not operating correctly at this moment. +

+

+ Please try again. If you have seen this message for the second time, please contact us so we can fix the problem. +

+
+`); + }); + test(`INVALID_REQUEST`, () => { + const {container} = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+

+ Google Maps server appears to be not operating correctly at this moment. +

+

+ Please try again. If you have seen this message for the second time, please contact us so we can fix the problem. +

+
+`); + }); + test(`NOT_FOUND`, () => { + const {container} = render(); + expect(container).toMatchInlineSnapshot(` +
+

+ Google Maps server appears to be not operating correctly at this moment. +

+

+ Please try again. If you have seen this message for the second time, please contact us so we can fix the problem. +

+
+`); + }); + test(`OVER_QUERY_LIMIT`, () => { + const {container} = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+

+ My Ideal Map is currently unable to use Google Maps search. +

+

+ Please contact us so we can fix the problem. +

+
+`); + }); + test(`REQUEST_DENIED`, () => { + const {container} = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+

+ My Ideal Map is currently unable to use Google Maps search. +

+

+ Please contact us so we can fix the problem. +

+
+`); + }); + test(`UNKNOWN_ERROR`, () => { + const {container} = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+

+ Google Maps server is currently down. +

+

+ + Please check its status + + , and try again once they fix the problem (usually within a few hours). +

+
+`); + }); + test(`Some random text`, () => { + const crypto = require('crypto'); + const {container} = render( + , + ); + expect(container).toMatchInlineSnapshot(` +
+

+ Google Maps server is currently down. +

+

+ + Please check its status + + , and try again once they fix the problem (usually within a few hours). +

+
+`); + }); +}); + +test('Accessibility checks', async () => { + const {container} = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); +}); diff --git a/src/components/SearchedPlace.js b/src/components/SearchedPlace.js index f3bd10e0..64f059e1 100644 --- a/src/components/SearchedPlace.js +++ b/src/components/SearchedPlace.js @@ -9,6 +9,7 @@ import {ButtonDialog} from 'src/elements/ButtonDialog'; import {CloseButton} from './CloseButton'; import {DivPlaceInfoBackground} from 'src/elements/DivPlaceInfoBackground'; import {ParagraphLoading} from 'src/elements/ParagraphLoading'; +import {PlaceDetailErrorMessage} from './PlaceDetailErrorMessage'; import {SpanRipple} from 'src/elements/SpanRipple'; import {useOnClickOutside} from 'src/hooks/useOnClickOutside'; @@ -16,7 +17,12 @@ import {useOnEscKeyDown} from 'src/hooks/useOnEscKeyDown'; import {usePlaces} from './Places'; import {useStateObject} from 'src/hooks/useStateObject'; -import {buttonLabel, linkText, loadingMessage} from 'src/utils/uiCopies'; +import { + buttonLabel, + errorMessage, + linkText, + loadingMessage, +} from 'src/utils/uiCopies'; // import Tiptap only when necessary import dynamic from 'next/dynamic'; @@ -143,7 +149,6 @@ export const SearchedPlace = ({mapObject}) => { mapObject.panBy(0, viewportSize.current.height / 4); setState({status: 'open', placeData: searchedPlace}); } else { - // TODO #199: Handle error more properly console.error('Google Maps Place Details API call has failed.'); setState({status: 'error', error: status}); } @@ -164,7 +169,9 @@ export const SearchedPlace = ({mapObject}) => { ripplePositionLeft, ripplePositionTop, } = {}) => { - mapObject.panTo(placeData.coordinates); + if (placeData) { + mapObject.panTo(placeData.coordinates); + } // to be skipped if there is an error in Place Details API setState({ status: 'closing', ripple: { @@ -239,7 +246,34 @@ export const SearchedPlace = ({mapObject}) => { ); } else if (status === 'error') { - return null; // TODO #199: Handle error properly + return ( + + + +
+

+ {errorMessage.placeDetails.title} +

+
+ +
+ setState({status: 'closed'})} + type="button" + > + {buttonLabel.handleError} + +
+
+
+
+ ); } else if (status === 'open' || status === 'closing') { return ( { width: calc(100% - var(--close-button-width)); } +.c1[role="alertdialog"] { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.c1[role="alertdialog"] div { + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.c1[role="alertdialog"] p { + width: 100%; +} + .c1 h2 { padding-bottom: 0.8252rem; padding-top: 0.7385rem; diff --git a/src/utils/uiCopies.js b/src/utils/uiCopies.js index 63c78647..248d2dff 100644 --- a/src/utils/uiCopies.js +++ b/src/utils/uiCopies.js @@ -5,6 +5,12 @@ export const loadingMessage = { delete: placeName => `Deleting ${placeName}...`, }; +export const errorMessage = { + placeDetails: { + title: 'Unable to get place detail', + }, +}; + export const modal = { delete: { title: placeName => `Deleting ${placeName}`, @@ -104,6 +110,7 @@ export const buttonLabel = { }, delete: 'Delete', edit: 'Edit', + handleError: 'Got it', locator: { default: 'Track your location', activated: 'Snap to your location', From 76245ce855a8aeeaea19d76b0228c2c3a3411888 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 3 Apr 2023 08:48:52 +0900 Subject: [PATCH 26/28] refactor(search,savedplaces): remove H2PlaceName styled component we no longer use it --- src/elements/H2PlaceName.js | 12 ----------- src/elements/H2PlaceName.test.js | 35 -------------------------------- 2 files changed, 47 deletions(-) delete mode 100644 src/elements/H2PlaceName.js delete mode 100644 src/elements/H2PlaceName.test.js diff --git a/src/elements/H2PlaceName.js b/src/elements/H2PlaceName.js deleted file mode 100644 index 508202b9..00000000 --- a/src/elements/H2PlaceName.js +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components'; -import {h2PlaceName} from 'src/utils/designtokens'; -import {remify} from 'src/utils/remify'; - -export const H2PlaceName = styled.h2` - font-family: ${h2PlaceName.fontFamily}; - font-size: ${remify(h2PlaceName.fontSize)}; - font-weight: ${h2PlaceName.fontWeight}; - line-height: ${h2PlaceName.lineHeight}; - padding-bottom: ${remify(h2PlaceName.paddingBottom)}; - padding-top: ${remify(h2PlaceName.paddingTop)}; -`; diff --git a/src/elements/H2PlaceName.test.js b/src/elements/H2PlaceName.test.js deleted file mode 100644 index 6742967c..00000000 --- a/src/elements/H2PlaceName.test.js +++ /dev/null @@ -1,35 +0,0 @@ -// eslint-disable-next-line no-unused-vars -import {render, screen} from '@testing-library/react'; - -import {H2PlaceName} from './H2PlaceName'; - -const mockProps = {}; - -test('renders UI correctly', () => { - const {container} = render(); - expect(container).toMatchInlineSnapshot(` -.c0 { - font-family: 'Noto Sans Display',Georgia,sans-serif; - font-size: 1.3986rem; - font-weight: 700; - line-height: 1.092; - padding-bottom: 0.8252rem; - padding-top: 0.7385rem; -} - -
-

-

-`); -}); - -// describe('Props change style correctly', () => { -// test('testProp', () => { -// render(); -// expect(screen.getByTestId('H2PlaceName')).toHaveStyle( -// `display: block`, -// ); -// }); -// }); From 953d02bae7861f93b3ad91cb6053f8387e0716e6 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Mon, 3 Apr 2023 08:51:45 +0900 Subject: [PATCH 27/28] build(editor): remove PlaceInfoEditor component We no longer use it as we now use TiptapEditor component as our text editor. (We still need slate.js for backward compatibility, though.) --- package-lock.json | 197 ++----------------------- package.json | 2 - src/components/InlineChromiumBugfix.js | 9 -- src/components/PlaceInfoEditor.js | 154 ------------------- 4 files changed, 14 insertions(+), 348 deletions(-) delete mode 100644 src/components/InlineChromiumBugfix.js delete mode 100644 src/components/PlaceInfoEditor.js diff --git a/package-lock.json b/package-lock.json index 321cbbe3..d537c252 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,8 +30,6 @@ "react-dom": "17.0.2", "react-focus-lock": "2.5.2", "slate": "0.71.0", - "slate-history": "0.66.0", - "slate-react": "0.71.0", "stripe": "10.15.0", "styled-components": "^5.2.1" }, @@ -5710,11 +5708,6 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, - "node_modules/@types/is-hotkey": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.5.tgz", - "integrity": "sha512-pZTb6AsG7I56FJgYA8Cbit3cB3NGVwyHgwyUCENjXewTQChOtQaxaV+u6BO4hqtS1o9KT1wML+NRkGhQZ6swtA==" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -5770,11 +5763,6 @@ "keyv": "*" } }, - "node_modules/@types/lodash": { - "version": "4.14.177", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", - "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==" - }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -7875,11 +7863,6 @@ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, - "node_modules/compute-scroll-into-view": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", - "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9236,18 +9219,6 @@ "node": ">=8" } }, - "node_modules/direction": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", - "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", - "bin": { - "direction": "cli.js" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -12724,6 +12695,15 @@ "node": ">=12.0.0" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", @@ -13279,11 +13259,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hotkey": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", - "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" - }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -24292,14 +24267,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/scroll-into-view-if-needed": { - "version": "2.2.28", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", - "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", - "dependencies": { - "compute-scroll-into-view": "^1.0.17" - } - }, "node_modules/semantic-release": { "version": "19.0.5", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", @@ -24860,62 +24827,6 @@ "tiny-warning": "^1.0.3" } }, - "node_modules/slate-history": { - "version": "0.66.0", - "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.66.0.tgz", - "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", - "dependencies": { - "is-plain-object": "^5.0.0" - }, - "peerDependencies": { - "slate": ">=0.65.3" - } - }, - "node_modules/slate-history/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/slate-react": { - "version": "0.71.0", - "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.71.0.tgz", - "integrity": "sha512-UgryclHduQyOUAtoCk/05/4z6CMSURV+WyTwNMJZB10QDVviI78LsLCWstSo0jDlewM8KAX150s3aC2EZwH70w==", - "dependencies": { - "@types/is-hotkey": "^0.1.1", - "@types/lodash": "^4.14.149", - "direction": "^1.0.3", - "is-hotkey": "^0.1.6", - "is-plain-object": "^5.0.0", - "lodash": "^4.17.4", - "scroll-into-view-if-needed": "^2.2.20", - "tiny-invariant": "1.0.6" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0", - "slate": ">=0.65.3" - } - }, - "node_modules/slate-react/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/slate/node_modules/immer": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz", - "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/slate/node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -26318,11 +26229,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/tiny-invariant": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" - }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -31682,11 +31588,6 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, - "@types/is-hotkey": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.5.tgz", - "integrity": "sha512-pZTb6AsG7I56FJgYA8Cbit3cB3NGVwyHgwyUCENjXewTQChOtQaxaV+u6BO4hqtS1o9KT1wML+NRkGhQZ6swtA==" - }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -31741,11 +31642,6 @@ "keyv": "*" } }, - "@types/lodash": { - "version": "4.14.177", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", - "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==" - }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -33383,11 +33279,6 @@ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, - "compute-scroll-into-view": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz", - "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -34422,11 +34313,6 @@ "path-type": "^4.0.0" } }, - "direction": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", - "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==" - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -37088,6 +36974,11 @@ "queue": "6.0.2" } }, + "immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + }, "import-fresh": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", @@ -37497,11 +37388,6 @@ "is-extglob": "^2.1.1" } }, - "is-hotkey": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", - "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" - }, "is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -45767,14 +45653,6 @@ "ajv-keywords": "^3.5.2" } }, - "scroll-into-view-if-needed": { - "version": "2.2.28", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz", - "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==", - "requires": { - "compute-scroll-into-view": "^1.0.17" - } - }, "semantic-release": { "version": "19.0.5", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.5.tgz", @@ -46212,48 +46090,6 @@ "is-plain-object": "^5.0.0", "tiny-warning": "^1.0.3" }, - "dependencies": { - "immer": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz", - "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==" - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - } - } - }, - "slate-history": { - "version": "0.66.0", - "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.66.0.tgz", - "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", - "requires": { - "is-plain-object": "^5.0.0" - }, - "dependencies": { - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - } - } - }, - "slate-react": { - "version": "0.71.0", - "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.71.0.tgz", - "integrity": "sha512-UgryclHduQyOUAtoCk/05/4z6CMSURV+WyTwNMJZB10QDVviI78LsLCWstSo0jDlewM8KAX150s3aC2EZwH70w==", - "requires": { - "@types/is-hotkey": "^0.1.1", - "@types/lodash": "^4.14.149", - "direction": "^1.0.3", - "is-hotkey": "^0.1.6", - "is-plain-object": "^5.0.0", - "lodash": "^4.17.4", - "scroll-into-view-if-needed": "^2.2.20", - "tiny-invariant": "1.0.6" - }, "dependencies": { "is-plain-object": { "version": "5.0.0", @@ -47353,11 +47189,6 @@ } } }, - "tiny-invariant": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" - }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", diff --git a/package.json b/package.json index 8ed50179..49eb4bb4 100644 --- a/package.json +++ b/package.json @@ -86,8 +86,6 @@ "react-dom": "17.0.2", "react-focus-lock": "2.5.2", "slate": "0.71.0", - "slate-history": "0.66.0", - "slate-react": "0.71.0", "stripe": "10.15.0", "styled-components": "^5.2.1" }, diff --git a/src/components/InlineChromiumBugfix.js b/src/components/InlineChromiumBugfix.js deleted file mode 100644 index 048ca3ba..00000000 --- a/src/components/InlineChromiumBugfix.js +++ /dev/null @@ -1,9 +0,0 @@ -// adapted from: https://github.com/ianstormtaylor/slate/blob/main/site/examples/inlines.tsx - -// Put this at the start and end of an inline component to work around this Chromium bug: -// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405 -export const InlineChromiumBugfix = () => ( - - {String.fromCodePoint(160) /* Non-breaking space */} - -); diff --git a/src/components/PlaceInfoEditor.js b/src/components/PlaceInfoEditor.js deleted file mode 100644 index 63602d5b..00000000 --- a/src/components/PlaceInfoEditor.js +++ /dev/null @@ -1,154 +0,0 @@ -// Adapted from https://github.com/ianstormtaylor/slate/blob/main/site/examples/forced-layout.tsx - -import {useRef, useState, useCallback} from 'react'; -import PropTypes from 'prop-types'; - -import {Slate, Editable, withReact} from 'slate-react'; -import {Transforms, createEditor, Node, Element} from 'slate'; -import {withHistory} from 'slate-history'; - -import {InlineChromiumBugfix} from './InlineChromiumBugfix'; -import {H2PlaceName} from 'src/elements/H2PlaceName'; -import {HeaderEditor} from 'src/elements/HeaderEditor'; -import {Heading} from 'src/elements/Heading'; - -import {buttonLabel, editorLabel} from 'src/utils/uiCopies'; - -const withLayout = editor => { - const {normalizeNode} = editor; - - editor.normalizeNode = ([node, path]) => { - // At the top node - if (path.length === 0) { - // If there is no child node at all, create a node of title type with placeholder text - if (editor.children.length < 1) { - const title = { - type: 'title', - children: [{text: 'Unnamed place'}], - }; - Transforms.insertNodes(editor, title, {at: path.concat(0)}); - } - // If there is at most one child node, create a second node of paragraph type - if (editor.children.length < 2) { - const paragraph = { - type: 'paragraph', - children: [{text: ''}], - }; - Transforms.insertNodes(editor, paragraph, {at: path.concat(1)}); - } - - // Convert 0th node into title type, 1st node into paragraph type - for (const [child, childPath] of Node.children(editor, path)) { - const enforceType = type => { - if (Element.isElement(child) && child.type !== type) { - const newProperties = {type}; - Transforms.setNodes(editor, newProperties, { - at: childPath, - }); - } - }; - const slateIndex = childPath[0]; - switch (slateIndex) { - case 0: - enforceType('title'); - break; - case 1: - enforceType('paragraph'); - break; - default: - break; - } - } - } - - return normalizeNode([node, path]); - }; - - return editor; -}; - -export const PlaceInfoEditor = ({ - handleCancel, - placeName, - placeNoteArray, - updateData, -}) => { - const [editor] = useState(() => - withLayout(withReact(withHistory(createEditor()))), - ); - const titleNode = { - type: 'title', - children: [ - { - text: placeName, - }, - ], - }; - const initialContent = [titleNode].concat(placeNoteArray); - const content = useRef(initialContent); - const renderElement = useCallback(({attributes, children, element}) => { - switch (element.type) { - case 'title': - return {children}; - case 'paragraph': - return

{children}

; - case 'link': - return ( - - - {children} - - - ); - default: - return null; - } - }, []); - - const handleClickSave = event => { - event.preventDefault(); - const [title, ...noteArray] = content.current; - updateData([title, noteArray]); - }; - - return ( - - (content.current = value)} - > - - - {editorLabel} - -
- - -
-
- -
- - ); -}; - -PlaceInfoEditor.propTypes = { - handleCancel: PropTypes.func, - placeName: PropTypes.string, - placeNoteArray: PropTypes.arrayOf(Object), - updateData: PropTypes.func, -}; From 7c599d68da915e643af74be437713c06517eb190 Mon Sep 17 00:00:00 2001 From: Masa Kudamatsu Date: Tue, 4 Apr 2023 08:44:21 +0900 Subject: [PATCH 28/28] test(menu): test if focus is returned to menu button after closing the menu with ESC key --- cypress/e2e/to-be-fixed.cy.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/to-be-fixed.cy.js b/cypress/e2e/to-be-fixed.cy.js index 31d25c6d..02b690ec 100644 --- a/cypress/e2e/to-be-fixed.cy.js +++ b/cypress/e2e/to-be-fixed.cy.js @@ -10,9 +10,12 @@ describe(`menu.cy.js`, () => { it.skip('closes menu after pressing ESC key', () => { cy.log(`Clicking menu button and ...`); cy.findByRole('button', {name: buttonLabel.menu}).click(); - cy.log(`Pressing ESC key also closes the menu`); + cy.log(`Pressing ESC key...`); cy.get('body').type('{esc}'); + cy.log('...Hides the search box'); cy.findByRole('dialog', {name: menuLabel}).should('not.exist'); + cy.log('...Focuses the search icon button'); + cy.focused().should('have.attr', 'aria-label', buttonLabel.menu); }); }); describe(`search.cy.js`, () => {