From 9f914a790fd411d609cf811c407a6305f3357049 Mon Sep 17 00:00:00 2001 From: Samuel John <40059405+samueljd@users.noreply.github.com> Date: Thu, 16 Nov 2023 08:45:10 +0200 Subject: [PATCH] Texteditor refactor (#190) * directory change for editor * editor-refactor * popup-rework * fixed insert issues * custom paste function * fixed-ui-bugs * fixed undo redo and insert popup ui --- .eslintrc.json | 3 +- .vscode/settings.json | 10 +- jsconfig.json | 6 +- package.json | 2 +- public/icons/Xelah/XMark.svg | 3 + renderer/jsconfig.json | 11 +- renderer/public/icons/Xelah/XMark.svg | 3 + .../Navigation/reference/SelectVerse.js | 2 - .../ReferenceBible/ReferenceBibleX.js | 2 +- .../components/EditorPage/Scribex/Editor.jsx | 206 -------------- .../EditorPage/Scribex/GraftEditor.jsx | 65 ----- .../EditorPage/Scribex/InsertMenu.jsx | 57 ---- .../components/EditorPage/Scribex/Popup.jsx | 119 --------- .../EditorPage/Scribex/PopupButton.jsx | 46 ---- .../components/EditorPage/Scribex/Scribex.jsx | 201 -------------- .../TextEditor/BibleNavigationX.jsx | 251 ++++++++++++++++++ .../{Scribex => TextEditor}/Buttons.jsx | 54 ++-- .../EditorPage/TextEditor/Editor.jsx | 163 ++++++++++++ .../EditorPage/TextEditor/EditorMenuBar.jsx | 87 ++++++ .../EditorPage/TextEditor/InsertMenu.jsx | 35 +++ .../EditorPage/TextEditor/Popup.jsx | 116 ++++++++ .../RecursiveBlock.jsx | 22 +- .../ReferenceEditor.jsx | 10 +- .../ReferenceRecursiveBlock.jsx | 0 .../ReferenceScribex.jsx | 16 +- .../EditorPage/TextEditor/hooks/htmlmap.js | 55 ++++ .../EditorPage/TextEditor/hooks/saveToFile.js | 36 +++ .../EditorPage/TextEditor/hooks/usePerf.js | 123 +++++++++ .../TextEditor/hooks/useReadUsfmFile.js | 57 ++++ .../EditorPage/TextEditor/index.jsx | 117 ++++++++ .../TextEditor/utils/IntersectionObserver.js | 20 ++ .../utils}/getReferences.js | 0 .../TextEditor/utils/insertFunctionMap.js | 18 ++ renderer/src/components/Popup/Dialog.jsx | 132 +++++++++ .../src/components/Popup/PopupContext.jsx | 28 ++ renderer/src/components/Popup/index.jsx | 43 +++ .../src/components/Projects/CustomList.js | 4 +- .../src/components/Projects/NewProject.js | 38 +-- .../CreateProject/AdvancedSettingsDropdown.js | 2 +- .../src/components/context/ScribexContext.js | 30 +++ renderer/src/hooks/useAutoSaveIndication.js | 24 ++ .../src/layouts/editor/SectionContainer.js | 16 +- .../src/layouts/editor/WebSectionContainer.js | 19 +- renderer/src/modules/projects/CustomList.js | 4 +- renderer/src/util/cursorUtils.js | 117 +++----- styles/globals.css | 3 + supabase.js | 5 +- yarn.lock | 23 +- 48 files changed, 1498 insertions(+), 906 deletions(-) create mode 100644 public/icons/Xelah/XMark.svg create mode 100644 renderer/public/icons/Xelah/XMark.svg delete mode 100644 renderer/src/components/EditorPage/Scribex/Editor.jsx delete mode 100644 renderer/src/components/EditorPage/Scribex/GraftEditor.jsx delete mode 100644 renderer/src/components/EditorPage/Scribex/InsertMenu.jsx delete mode 100644 renderer/src/components/EditorPage/Scribex/Popup.jsx delete mode 100644 renderer/src/components/EditorPage/Scribex/PopupButton.jsx delete mode 100644 renderer/src/components/EditorPage/Scribex/Scribex.jsx create mode 100644 renderer/src/components/EditorPage/TextEditor/BibleNavigationX.jsx rename renderer/src/components/EditorPage/{Scribex => TextEditor}/Buttons.jsx (75%) create mode 100644 renderer/src/components/EditorPage/TextEditor/Editor.jsx create mode 100644 renderer/src/components/EditorPage/TextEditor/EditorMenuBar.jsx create mode 100644 renderer/src/components/EditorPage/TextEditor/InsertMenu.jsx create mode 100644 renderer/src/components/EditorPage/TextEditor/Popup.jsx rename renderer/src/components/EditorPage/{Scribex => TextEditor}/RecursiveBlock.jsx (80%) rename renderer/src/components/EditorPage/{Scribex => TextEditor}/ReferenceEditor.jsx (95%) rename renderer/src/components/EditorPage/{Scribex => TextEditor}/ReferenceRecursiveBlock.jsx (100%) rename renderer/src/components/EditorPage/{Scribex => TextEditor}/ReferenceScribex.jsx (85%) create mode 100644 renderer/src/components/EditorPage/TextEditor/hooks/htmlmap.js create mode 100644 renderer/src/components/EditorPage/TextEditor/hooks/saveToFile.js create mode 100644 renderer/src/components/EditorPage/TextEditor/hooks/usePerf.js create mode 100644 renderer/src/components/EditorPage/TextEditor/hooks/useReadUsfmFile.js create mode 100644 renderer/src/components/EditorPage/TextEditor/index.jsx create mode 100644 renderer/src/components/EditorPage/TextEditor/utils/IntersectionObserver.js rename renderer/src/components/EditorPage/{Scribex => TextEditor/utils}/getReferences.js (100%) create mode 100644 renderer/src/components/EditorPage/TextEditor/utils/insertFunctionMap.js create mode 100644 renderer/src/components/Popup/Dialog.jsx create mode 100644 renderer/src/components/Popup/PopupContext.jsx create mode 100644 renderer/src/components/Popup/index.jsx create mode 100644 renderer/src/hooks/useAutoSaveIndication.js diff --git a/.eslintrc.json b/.eslintrc.json index ce263e66f..673289bed 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -96,7 +96,8 @@ ["@/layouts", "./renderer/src/layouts/"], ["@/modules", "./renderer/src/modules/"], ["@/util", "./renderer/src/util/"], - ["@/core", "./renderer/src/core/"] + ["@/core", "./renderer/src/core/"], + ["@/hooks", "./renderer/src/hooks/"] ], "node": { "extensions": [".js", ".jsx", ".ts", ".tsx"] diff --git a/.vscode/settings.json b/.vscode/settings.json index e59d78323..0ecec27ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,11 +2,15 @@ "[javascript]": { "editor.formatOnSave": false }, - "editor.rulers": [100], + "editor.rulers": [ + 100 + ], "editor.fontLigatures": true, "prettier.tabWidth": 4, "eslint.alwaysShowStatus": true, - "prettier.disableLanguages": ["js"], + "prettier.disableLanguages": [ + "js" + ], "prettier.useTabs": true, "editor.formatOnSave": true, "editor.multiCursorModifier": "alt", @@ -27,4 +31,4 @@ }, "eslint.workingDirectories": [], "editor.tabSize": 2 -} +} \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json index 4138fa416..cf2e97bcf 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -28,5 +28,9 @@ "app/*" ], } - } + }, + "exclude": [ + "node_modules", + "dist" + ] } \ No newline at end of file diff --git a/package.json b/package.json index c56c1a7a4..be4f08ee8 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "electron-log": "4.4.7", "electron-next": "^3.1.5", "electron-updater": "^5.0.1", - "epitelete": "0.2.20-beta.1", + "epitelete": "^0.2.20", "epitelete-html": "0.2.20-beta.2", "eslint-import-resolver-alias": "^1.1.2", "font-detect-rhl": "1.0.5-1", diff --git a/public/icons/Xelah/XMark.svg b/public/icons/Xelah/XMark.svg new file mode 100644 index 000000000..457d8e626 --- /dev/null +++ b/public/icons/Xelah/XMark.svg @@ -0,0 +1,3 @@ + + + diff --git a/renderer/jsconfig.json b/renderer/jsconfig.json index 4f315116b..fb5f8086f 100644 --- a/renderer/jsconfig.json +++ b/renderer/jsconfig.json @@ -23,9 +23,16 @@ "@/illustrations/*": [ "public/illustrations/*" ], - "@/hooks/*":[ + "@/hooks/*": [ "src/components/hooks/*" + ], + "@/hooks2/*": [ + "src/hooks/*" ] } - } + }, + "exclude": [ + "node_modules", + "dist" + ] } \ No newline at end of file diff --git a/renderer/public/icons/Xelah/XMark.svg b/renderer/public/icons/Xelah/XMark.svg new file mode 100644 index 000000000..457d8e626 --- /dev/null +++ b/renderer/public/icons/Xelah/XMark.svg @@ -0,0 +1,3 @@ + + + diff --git a/renderer/src/components/EditorPage/Navigation/reference/SelectVerse.js b/renderer/src/components/EditorPage/Navigation/reference/SelectVerse.js index 09646b4e0..40fa0165a 100644 --- a/renderer/src/components/EditorPage/Navigation/reference/SelectVerse.js +++ b/renderer/src/components/EditorPage/Navigation/reference/SelectVerse.js @@ -50,8 +50,6 @@ export default function SelectVerse({ document.getElementById('editor').querySelector(`#ch${chapter}v${verseNum}`)?.scrollIntoView(); setVerseNumber(verseNum); } - // window.location.href = `#ch${chapter}v${verseNum}`; - // document.getElementById(`ch${chapter}v${verseNum}`).scrollIntoView(); }; const onMultiSelectVerse = async (e, verses) => { diff --git a/renderer/src/components/EditorPage/Reference/ReferenceBible/ReferenceBibleX.js b/renderer/src/components/EditorPage/Reference/ReferenceBible/ReferenceBibleX.js index afc4519f3..472e791c1 100644 --- a/renderer/src/components/EditorPage/Reference/ReferenceBible/ReferenceBibleX.js +++ b/renderer/src/components/EditorPage/Reference/ReferenceBible/ReferenceBibleX.js @@ -6,7 +6,7 @@ import LoadingScreen from '@/components/Loading/LoadingScreen'; import { SnackBar } from '@/components/SnackBar'; import EmptyScreen from '@/components/Loading/EmptySrceen'; import { useReadReferenceUsfmFile } from '@/components/EditorPage/Reference/ReferenceBible/useReadReferenceUsfmFile'; -import ReferenceScribex from '../../Scribex/ReferenceScribex'; +import ReferenceScribex from '../../TextEditor/ReferenceScribex'; const ReferenceBibleX = ({ languageId, diff --git a/renderer/src/components/EditorPage/Scribex/Editor.jsx b/renderer/src/components/EditorPage/Scribex/Editor.jsx deleted file mode 100644 index 5686132b6..000000000 --- a/renderer/src/components/EditorPage/Scribex/Editor.jsx +++ /dev/null @@ -1,206 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { useContext, useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { HtmlPerfEditor } from '@xelah/type-perf-html'; - -import LoadingScreen from '@/components/Loading/LoadingScreen'; -import SaveIndicator from '@/components/Loading/SaveIndicator'; -import { ReferenceContext } from '@/components/context/ReferenceContext'; -import { ProjectContext } from '@/components/context/ProjectContext'; -import EmptyScreen from '@/components/Loading/EmptySrceen'; -import { - insertVerseNumber, insertChapterNumber, insertFootnote, insertXRef, -} from '@/util/cursorUtils'; -import RecursiveBlock from './RecursiveBlock'; - -export default function Editor(props) { - const { - chapterNumber, - sequenceIds, - isSaving, - isLoading, - htmlPerf, - sectionable, - blockable, - editable, - preview, - verbose, - bookName, - bookChange, - setBookChange, - addSequenceId, - saveHtmlPerf, - setGraftSequenceId, - bookAvailable, - setChapterNumber, - setVerseNumber, - triggerVerseInsert, - newVerChapNumber, - insertVerseRChapter, - reference, - insertNewGraft, - selectedText, - setSelectedText, - } = props; - - const [caretPosition, setCaretPosition] = useState(); - const [graftInsert, setGraftInsert] = useState(false); - const { - state: { chapter }, - } = useContext(ReferenceContext); - const { t } = useTranslation(); - - const { - states: { openSideBar, scrollLock }, - actions: { setOpenSideBar, setSideBarTab }, - } = useContext(ProjectContext); - - const [chapters, setChapters] = useState(); - // const [selectedText, setSelectedText] = useState(); - const sequenceId = sequenceIds.at(-1); - const style = isSaving ? { cursor: 'progress' } : {}; - const handlers = { - onBlockClick: ({ content: _content, element }) => { - const _sequenceId = element.dataset.target; - const { tagName } = element; - if (_sequenceId) { - if (tagName === 'SPAN' && element.dataset.subtype === 'footnote') { - setGraftSequenceId(_sequenceId); - setOpenSideBar(!openSideBar); - setSideBarTab('footnotes'); - } - if (tagName === 'SPAN' && element.dataset.subtype === 'xref') { - setGraftSequenceId(_sequenceId); - setOpenSideBar(!openSideBar); - setSideBarTab('xref'); - } - } else { - setSideBarTab(''); - setGraftSequenceId(null); - } - }, - }; - useEffect(() => { - setBookChange(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [htmlPerf]); - - const { - actions: { setEditorSave }, - } = useContext(ProjectContext); - - const autoSaveIndication = () => { - setEditorSave(); - setTimeout(() => { - setEditorSave(t('label-saved')); - }, 1000); - }; - useEffect(() => { - if (isSaving) { - autoSaveIndication(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSaving]); - - function onReferenceSelected({ bookId, chapter, verse }) { - chapter && setChapterNumber(chapter); - verse && setVerseNumber(verse); - } - - function getSelectedText() { - let selectedText = ''; - if (window.getSelection) { - const selection = window.getSelection(); - - selectedText = selection.toString(); - setSelectedText(selectedText); - } else if (document.selection && document.selection.type !== 'Control') { - selectedText = document.selection.createRange().text; - setSelectedText(selectedText); - } - } - - useEffect(() => { - if (insertVerseRChapter === 'Verse') { - insertVerseNumber(caretPosition, newVerChapNumber); - } - if (insertVerseRChapter === 'Chapter') { - insertChapterNumber(caretPosition, newVerChapNumber); - } - if (insertVerseRChapter === 'Footnote') { - // setGraftInsert(true); - insertFootnote(caretPosition, newVerChapNumber, selectedText); - } - if (insertVerseRChapter === 'Cross Reference') { - insertXRef(caretPosition, newVerChapNumber, selectedText); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [triggerVerseInsert]); - - const scrollReference = (chapterNumber) => { - const refEditors = document.getElementsByClassName('ref-editor'); - refEditors.length > 0 && Array.prototype.filter.call(refEditors, (refEditor) => { - const editorInView = refEditor.querySelector(`#ch-${chapterNumber}`); - if (editorInView) { - editorInView.scrollIntoView(); - editorInView.classList.add('scroll-mt-10'); - } - }); - }; - - const onIntersection = (entries) => { - // eslint-disable-next-line no-restricted-syntax - for (const entry of entries) { - if (entry.isIntersecting) { - setChapterNumber(entry.target.dataset.attsNumber); - scrollLock === false ? scrollReference(entry.target.dataset.attsNumber) : {}; - } - } - }; - - const options = { - root: document.querySelector('editor'), - threshold: 0, - rootMargin: '0% 0% -60% 0%', - }; - const observer = new IntersectionObserver(onIntersection, options); - - const watchNodes = document.querySelectorAll('.editor .chapter'); - const watchArr = Array.from(watchNodes); - const reverseArray = watchArr.length > 0 ? watchArr.slice().reverse() : []; - reverseArray.forEach((chapter) => { observer.observe(chapter); }); - - const _props = { - htmlPerf, - // onHtmlPerf: graftInsert ? insertNewGraft : saveHtmlPerf, - onHtmlPerf: saveHtmlPerf, - chapterIndex: chapter, - sequenceIds, - addSequenceId, - components: { - block: (__props) => RecursiveBlock({ - htmlPerf, onHtmlPerf: saveHtmlPerf, sequenceIds, addSequenceId, onReferenceSelected, setCaretPosition, setSelectedText, ...__props, - }), - }, - options: { - sectionable, - blockable, - editable, - preview, - }, - decorators: {}, - verbose, - handlers, - autoSaveIndication, - }; - - return ( -
- {!bookAvailable && } - {bookAvailable && (!sequenceId || bookChange) && } - {bookAvailable && sequenceId && !bookChange && ( - - )} -
- ); -} diff --git a/renderer/src/components/EditorPage/Scribex/GraftEditor.jsx b/renderer/src/components/EditorPage/Scribex/GraftEditor.jsx deleted file mode 100644 index 385f5a58d..000000000 --- a/renderer/src/components/EditorPage/Scribex/GraftEditor.jsx +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable no-unused-vars */ -import { HtmlPerfEditor } from '@xelah/type-perf-html'; - -export default function GraftEditor(props) { - const { - sequenceIds, - isLoading, - htmlPerf, - sectionable, - blockable, - editable, - preview, - verbose, - graftSequenceId, - addSequenceId, - saveHtmlPerf, - setGraftSequenceId, - } = props; - - const sequenceId = sequenceIds.at(-1); - - const style = isLoading || !sequenceId ? { cursor: 'progress' } : {}; - - const handlers = { - onBlockClick: ({ content: _content, element }) => { - const _sequenceId = element.dataset.target; - const { tagName } = element; - const isInline = tagName === 'SPAN'; - // if (_sequenceId && !isInline) addSequenceId(_sequenceId); - if (_sequenceId) { setGraftSequenceId(_sequenceId); } - }, - }; - - const _props = { - htmlPerf, - onHtmlPerf: saveHtmlPerf, - sequenceIds, - sequenceId, - addSequenceId, - options: { - sectionable, - blockable, - editable, - preview, - }, - decorators: {}, - verbose, - handlers, - }; - - const graftProps = { - ..._props, - sequenceIds: [graftSequenceId], - }; - - const graftSequenceEditor = htmlPerf && ( - - ); - - return ( -
- {graftSequenceId ? graftSequenceEditor : ''} -
- ); -} diff --git a/renderer/src/components/EditorPage/Scribex/InsertMenu.jsx b/renderer/src/components/EditorPage/Scribex/InsertMenu.jsx deleted file mode 100644 index aa7f518cd..000000000 --- a/renderer/src/components/EditorPage/Scribex/InsertMenu.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Fragment, useState } from 'react'; -import { Menu, Transition } from '@headlessui/react'; -import PopupButton from './PopupButton'; -import PlusIcon from '@/icons/Xelah/Plus.svg'; - -export default function InsertMenu({ handleClick: handleButtonClick, selectedText }) { - const [isOpen, setIsOpen] = useState(false); - const handleClick = (number, title) => { - handleButtonClick(number, title); - setIsOpen(false); - }; - return ( -
- - setIsOpen(!isOpen)} - // className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - > - - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/renderer/src/components/EditorPage/Scribex/Popup.jsx b/renderer/src/components/EditorPage/Scribex/Popup.jsx deleted file mode 100644 index 7533e8466..000000000 --- a/renderer/src/components/EditorPage/Scribex/Popup.jsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { Dialog, Transition } from '@headlessui/react'; - -const Popup = ({ - handleClose, handleButtonClick, title, isPopupOpen, selectedText, -}) => { - const [number, setNumber] = useState(''); - const handleInputChange = (event) => { - setNumber(event.target.value); - }; - - const handleNumberInputChange = (e) => { - setNumber(e.target.value.replace(/[^0-9]/g, '')); - }; - const handleSubmit = () => { - handleButtonClick(number, title); - handleClose(); - }; - - return ( - - -
- - - - -
-
-
- ); -}; - -Popup.propTypes = { - handleClose: PropTypes.func.isRequired, - handleButtonClick: PropTypes.func.isRequired, -}; - -export default Popup; diff --git a/renderer/src/components/EditorPage/Scribex/PopupButton.jsx b/renderer/src/components/EditorPage/Scribex/PopupButton.jsx deleted file mode 100644 index c0a6e24e5..000000000 --- a/renderer/src/components/EditorPage/Scribex/PopupButton.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useState } from 'react'; -import Popup from './Popup'; - -const PopupButton = ({ - handleClick, title, roundedHover, selectedText, -}) => { - const [isPopupOpen, setIsPopupOpen] = useState(false); - - const handlePopupOpen = () => { - setIsPopupOpen(true); - }; - - const handlePopupClose = () => { - setIsPopupOpen(false); - }; - - const handleButtonClick = (number, title) => { - handleClick(number, title); - }; - - return ( -
e.stopPropagation()} - onClick={(e) => e.stopPropagation()} - onFocus={(e) => e.stopPropagation()} - onMouseOver={(e) => e.stopPropagation()} - > - - {isPopupOpen && ( - - )} -
- ); -}; - -export default PopupButton; diff --git a/renderer/src/components/EditorPage/Scribex/Scribex.jsx b/renderer/src/components/EditorPage/Scribex/Scribex.jsx deleted file mode 100644 index e50fd5ee5..000000000 --- a/renderer/src/components/EditorPage/Scribex/Scribex.jsx +++ /dev/null @@ -1,201 +0,0 @@ -import { - useEffect, useState, useContext, Fragment, -} from 'react'; -import { useProskomma, useImport, useCatalog } from 'proskomma-react-hooks'; -import { useDeepCompareEffect } from 'use-deep-compare'; -import { LockClosedIcon, BookmarkIcon, LockOpenIcon } from '@heroicons/react/24/outline'; -import BibleNavigationX from '@/modules/biblenavigation/BibleNavigationX'; -import usePerf from '@/components/hooks/scribex/usePerf'; -import htmlMap from '@/components/hooks/scribex/htmlmap'; -import { ScribexContext } from '@/components/context/ScribexContext'; -import { ReferenceContext } from '@/components/context/ReferenceContext'; -import { ProjectContext } from '@/components/context/ProjectContext'; -import EditorSideBar from '@/modules/editorsidebar/EditorSideBar'; -import MenuDropdown from '@/components/MenuDropdown/MenuDropdown'; -import Buttons from './Buttons'; -import Editor from './Editor'; -import InsertMenu from './InsertMenu'; - -export default function Scribex(props) { - const { state, actions } = useContext(ScribexContext); - const { verbose } = state; - const { usfmData, bookAvailable } = props; - const [selectedBook, setSelectedBook] = useState(); - const [bookChange, setBookChange] = useState(false); - const [chapterNumber, setChapterNumber] = useState(1); - const [verseNumber, setVerseNumber] = useState(1); - const [triggerVerseInsert, setTriggerVerseInsert] = useState(false); - const [newVerChapNumber, setInsertNumber] = useState(''); - const [insertVerseRChapter, setInsertVerseRChapter] = useState(''); - const [selectedText, setSelectedText] = useState(); - - const handleClick = (number, title) => { - setInsertNumber(number); - setInsertVerseRChapter(title); - setTriggerVerseInsert(!triggerVerseInsert); - }; - - let selectedDocument; - - const { proskomma, stateId, newStateId } = useProskomma({ verbose }); - const { done } = useImport({ - proskomma, - stateId, - newStateId, - documents: usfmData, - }); - - const { - state: { - bookId, selectedFont, fontSize, projectScriptureDir, - }, - actions: { setSelectedFont }, - } = useContext(ReferenceContext); - - const { - states: { scrollLock }, - actions: { setScrollLock }, - } = useContext(ProjectContext); - - const { - states: { openSideBar }, - actions: { setOpenSideBar }, - } = useContext(ProjectContext); - - function closeSideBar(open) { - setOpenSideBar(open); - } - - useEffect(() => { - setSelectedBook(bookId.toUpperCase()); - setBookChange(true); - }, [bookId]); - - const { catalog } = useCatalog({ proskomma, stateId, verbose }); - const { id: docSetId, documents } = (done && catalog.docSets[0]) || {}; - if (done) { - selectedDocument = documents?.find( - (doc) => doc.bookCode === selectedBook, - ); - } - - const { bookCode, h: bookName } = selectedDocument || {}; - const ready = (docSetId && bookCode) || false; - const isLoading = !done || !ready; - const { state: perfState, actions: perfActions } = usePerf({ - proskomma, - ready, - docSetId, - bookCode, - verbose, - htmlMap, - }); - const { htmlPerf } = perfState; - - useDeepCompareEffect(() => { - if (htmlPerf && htmlPerf.mainSequenceId !== state.sequenceIds[0]) { - actions.setSequenceIds([htmlPerf?.mainSequenceId]); - } - }, [htmlPerf, state.sequenceIds, perfState]); - const _props = { - ...state, - ...perfState, - ...actions, - ...perfActions, - triggerVerseInsert, - chapterNumber, - verseNumber, - isLoading, - bookName, - bookChange, - bookAvailable, - setBookChange, - setChapterNumber, - setVerseNumber, - newVerChapNumber, - insertVerseRChapter, - selectedText, - setSelectedText, - }; - return ( - <> - -
-
-
- -
- Editor -
-
-
- {scrollLock === true ? ( -
-
-
-
-
-
-
- -
-
- - -
-
-
- -
1.3) ? 1.5 : '', - direction: `${projectScriptureDir === 'RTL' ? 'rtl' : 'auto'}`, - }} - className="border-l-2 border-r-2 border-secondary pb-16 overflow-auto h-full scrollbars-width leading-8" - > - -
-
- - ); -} diff --git a/renderer/src/components/EditorPage/TextEditor/BibleNavigationX.jsx b/renderer/src/components/EditorPage/TextEditor/BibleNavigationX.jsx new file mode 100644 index 000000000..4be5cb2b0 --- /dev/null +++ b/renderer/src/components/EditorPage/TextEditor/BibleNavigationX.jsx @@ -0,0 +1,251 @@ +import PropTypes from 'prop-types'; +import { Dialog, Transition } from '@headlessui/react'; +import React, { + Fragment, useContext, useEffect, useRef, useState, +} from 'react'; +import { XMarkIcon, ChevronDownIcon } from '@heroicons/react/24/solid'; +import * as localforage from 'localforage'; +import SelectBook from '@/components/EditorPage/Navigation/reference/SelectBook'; +import SelectVerse from '@/components/EditorPage/Navigation/reference/SelectVerse'; + +import { ReferenceContext } from '@/components/context/ReferenceContext'; + +export default function BibleNavigationX(props) { + const { + showVerse, chapterNumber, setChapterNumber, verseNumber, setVerseNumber, + } = props; + const supportedBooks = null; // if empty array or null then all books available + + const { + state: { + bookId, + bookList, + bookName, + chapter, + verse, + chapterList, + verseList, + languageId, + // closeNavigation, + }, actions: { + onChangeBook, + onChangeChapter, + onChangeVerse, + applyBooksFilter, + setCloseNavigation, + }, + } = useContext(ReferenceContext); + + useEffect(() => { + applyBooksFilter(supportedBooks); + }, [applyBooksFilter, supportedBooks]); + + const [openBook, setOpenBook] = useState(false); + const [openVerse, setOpenVerse] = useState(false); + const cancelButtonRef = useRef(null); + + const [multiSelectVerse] = useState(false); + const [multiSelectBook] = useState(false); + const [selectedVerses, setSelectedVerses] = useState([]); + const [selectedBooks, setSelectedBooks] = useState([]); + const [verselectActive, setVerseSelectActive] = useState(false); + + function closeBooks() { + setOpenBook(false); + } + + function openBooks() { + setSelectedBooks([(bookId.toUpperCase())]); + setOpenBook(true); + } + + function closeVerses() { + setOpenVerse(false); + if (multiSelectVerse) { setVerseSelectActive(true); } + } + + function selectBook() { + setOpenBook(false); + setOpenVerse(true); + if (multiSelectVerse) { setSelectedVerses([]); } + } + + useEffect(() => { + const getSupportedBooks = async () => { + const refs = await localforage.getItem('refBibleBurrito'); + refs?.forEach((ref) => { + if (languageId !== null) { + if (ref.value.languages[0].tag === languageId) { + const supportedBooks = []; + Object.entries((ref.value.type.flavorType.currentScope)).forEach( + ([key]) => { + supportedBooks.push(key.toLowerCase()); + }, + ); + applyBooksFilter(supportedBooks); + } + } + }); + }; + getSupportedBooks(); + }, [languageId, applyBooksFilter]); + + useEffect(() => { + async function setReference() { + await localforage.setItem('navigationHistory', [bookId, chapter, verse]); + } + setReference(); + }, [bookId, chapter, verse]); + + useEffect(() => { + if (openBook === false && openVerse === false) { + setCloseNavigation(true); + } + if (openBook || openVerse) { + setCloseNavigation(false); + } + }, [openVerse, openBook, setCloseNavigation]); + + return ( + <> +
+
+ {bookName} + + + {chapterNumber} + + + {verseNumber} + {showVerse + && ( + + {multiSelectVerse + ? selectedVerses.join() + : verse} + + )} +
+
+ + + +
+
+ + + +
+
+ +
+
+ + + + + +
+ +
+ + + +
+
+ +
+
+ + ); +} + +BibleNavigationX.propTypes = { + showVerse: PropTypes.bool, +}; diff --git a/renderer/src/components/EditorPage/Scribex/Buttons.jsx b/renderer/src/components/EditorPage/TextEditor/Buttons.jsx similarity index 75% rename from renderer/src/components/EditorPage/Scribex/Buttons.jsx rename to renderer/src/components/EditorPage/TextEditor/Buttons.jsx index 057559e40..e2fe18eb0 100644 --- a/renderer/src/components/EditorPage/Scribex/Buttons.jsx +++ b/renderer/src/components/EditorPage/TextEditor/Buttons.jsx @@ -20,6 +20,8 @@ export default function Buttons(props) { const [preview, setPreviewState] = useState(false); const { bookCode, + canUndo, + canRedo, undo, redo, setSectionable, @@ -27,7 +29,6 @@ export default function Buttons(props) { setEditable, setPreview, exportUsfm, - setTriggerVerseInsert, } = props; const onSectionable = () => { @@ -55,7 +56,6 @@ export default function Buttons(props) { sectionable ? 'fill-current' : '', 'h-5 mr-2 w-5 text-white cursor-pointer', )} - // aria-hidden="true" onClick={onSectionable} title={sectionable ? 'Expand all Chapters' : 'Collapse Chapters'} /> @@ -66,7 +66,6 @@ export default function Buttons(props) { editable ? 'fill-current' : '', 'h-5 mr-2 w-5 text-white cursor-pointer', )} - // aria-hidden="true" onClick={onEditable} title={editable ? 'Disable Edit' : 'Enable Edit'} /> @@ -74,7 +73,6 @@ export default function Buttons(props) {