Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/scribe/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biblionexus-foundation/scribe-editor",
"version": "0.1.0",
"version": "0.1.1",
"description": "Scripture editor used in Scribe",
"license": "MIT",
"homepage": "https://github.com/BiblioNexus-Foundation/scripture-editors/tree/main/packages/scribe#readme",
Expand Down Expand Up @@ -30,10 +30,12 @@
},
"dependencies": {
"@biblionexus-foundation/scripture-utilities": "workspace:~",
"@floating-ui/dom": "^1.6.13",
"@lexical/mark": "^0.24.0",
"@lexical/react": "^0.24.0",
"@lexical/selection": "^0.24.0",
"@lexical/utils": "^0.24.0",
"autoprefixer": "^10.4.20",
"fast-equals": "^5.2.2",
"lexical": "^0.24.0"
},
Expand Down
8 changes: 3 additions & 5 deletions packages/scribe/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { formattedViewMode as defaultViewMode } from "./plugins/view-mode.model"
import { ScriptureReference } from "shared/utils/get-marker-action.model";
import { UsjNodeOptions } from "shared-react/nodes/scripture/usj/usj-node-options.model";
import { immutableNoteCallerNodeName } from "shared-react/nodes/scripture/usj/ImmutableNoteCallerNode";
import { Usj2Usfm } from "./hooks/usj2Usfm";
// import { Usj2Usfm } from "./hooks/usj2Usfm";
import "shared/styles/nodes-menu.css";

const defaultUsj: Usj = {
type: USJ_TYPE,
Expand Down Expand Up @@ -37,11 +38,8 @@ function App() {
},
};
const viewOptions = useMemo(() => getViewOptions(viewMode), [viewMode]);
// const noteViewOptions = useMemo(() => getViewOptions(noteViewMode), [noteViewMode]);
const onChange = async (usj: Usj) => {
// console.log({ usj });
const usfm = await Usj2Usfm(usj);
console.log(usfm);
console.log(usj);
};
useEffect(() => {
console.log({ scrRef });
Expand Down
27 changes: 21 additions & 6 deletions packages/scribe/src/adaptors/usj-marker-action.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ const markerActions: {
action?: (currentEditor: {
reference: ScriptureReference;
editor: LexicalEditor;
autoNumbering?: boolean;
newVerseRChapterNum?: number;
noteText?: string;
}) => MarkerContent[];
};
} = {
c: {
action: (currentEditor) => {
const { book, chapterNum } = currentEditor.reference;
const nextChapter = chapterNum + 1;
const nextChapter = currentEditor.autoNumbering
? chapterNum + 1
: currentEditor.newVerseRChapterNum;
const content: MarkerContent = {
type: "chapter",
marker: "c",
Expand All @@ -46,7 +51,9 @@ const markerActions: {
v: {
action: (currentEditor) => {
const { book, chapterNum, verseNum, verse } = currentEditor.reference;
const nextVerse = getNextVerse(verseNum, verse);
const nextVerse = currentEditor.autoNumbering
? getNextVerse(verseNum, verse)
: currentEditor.newVerseRChapterNum;
const content: MarkerContent = {
type: "verse",
marker: "v",
Expand All @@ -68,7 +75,7 @@ const markerActions: {
{
type: "char",
marker: "ft",
content: [" "],
content: [currentEditor.noteText ?? " "],
},
],
};
Expand All @@ -87,7 +94,7 @@ const markerActions: {
{
type: "char",
marker: "xt",
content: [" "],
content: [currentEditor.noteText ?? " "],
},
],
};
Expand All @@ -103,9 +110,17 @@ export function getUsjMarkerAction(
viewOptions?: ViewOptions,
): MarkerAction {
const markerAction = getMarkerAction(marker);
const action = (currentEditor: { reference: ScriptureReference; editor: LexicalEditor }) => {
const action = (currentEditor: {
reference: ScriptureReference;
editor: LexicalEditor;
autoNumbering?: boolean;
newVerseRChapterNum?: number;
noteText?: string;
}) => {
currentEditor.editor.update(() => {
const content = markerAction?.action?.(currentEditor);
const content = currentEditor.autoNumbering
? markerAction?.action?.(currentEditor)
: markerAction?.action?.(currentEditor);
if (!content) return;

const serializedLexicalNode = createLexicalUsjNode(content, usjEditorAdaptor, viewOptions);
Expand Down
14 changes: 9 additions & 5 deletions packages/scribe/src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import UpdateStatePlugin from "shared-react/plugins/UpdateStatePlugin";
import editorUsjAdaptor from "../adaptors/editor-usj.adaptor";
import { getViewClassList, ViewOptions } from "../adaptors/view-options.utils";
import usjEditorAdaptor from "../adaptors/usj-editor.adaptor";
import UsjNodesMenuPlugin from "../plugins/UsjNodesMenuPlugin";
import useDeferredState from "../hooks/use-deferred-state.hook";
import { ScriptureReferencePlugin } from "../plugins/ScriptureReferencePlugin";
import editorTheme from "../themes/editor-theme";
import LoadingSpinner from "./LoadingSpinner";
import { blackListedChangeTags } from "shared/nodes/scripture/usj/node-constants";
import { deepEqual } from "fast-equals";
import { getUsjMarkerAction } from "../adaptors/usj-marker-action.utils";

/** Forward reference for the editor. */
export type EditorRef = {
Expand Down Expand Up @@ -73,7 +75,7 @@ const Editor = forwardRef(function Editor(
const [usj, setUsj] = useState(usjInput);
const [loadedUsj, , setEditedUsj] = useDeferredState(usj);
useDefaultNodeOptions(nodeOptions);

const autoNumbering = false;
const initialConfig = {
namespace: "ScribeEditor",
editable: true,
Expand Down Expand Up @@ -127,19 +129,21 @@ const Editor = forwardRef(function Editor(
placeholder={<LoadingSpinner />}
ErrorBoundary={LexicalErrorBoundary}
/>
{/* <UsjNodesMenuPlugin
trigger={NODE_MENU_TRIGGER}
{scrRef && (
<UsjNodesMenuPlugin
trigger={"\\"}
scrRef={scrRef}
getMarkerAction={(marker, markerData) =>
getUsjMarkerAction(marker, markerData, viewOptions)
}
/> */}
autoNumbering={autoNumbering}
/>
)}
<UpdateStatePlugin
scripture={loadedUsj}
nodeOptions={nodeOptions}
editorAdaptor={usjEditorAdaptor}
viewOptions={viewOptions}
// logger={logger}
/>
<OnChangePlugin onChange={handleChange} ignoreSelectionChange={true} />
<NoteNodePlugin nodeOptions={nodeOptions} />
Expand Down
66 changes: 66 additions & 0 deletions packages/scribe/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2433,3 +2433,69 @@ span.read img {
background-repeat: no-repeat;
background-position: center;
}
/* NodesMenu.css */

.user-input-container {
border-radius: 0.5rem;
border: 1px solid rgba(229, 231, 235, 1);
background-color: rgba(252, 252, 252, 1);
padding: 1rem;
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}

.input-header {
margin-bottom: 0.5rem;
font-size: 1.125rem;
font-weight: 500;
color: rgba(25, 25, 25, 1);
}

.user-input-container input {
width: 100%;
border-radius: 0.25rem;
border: 1px solid rgba(209, 213, 219, 1);
padding: 0.5rem;
margin-bottom: 0.75rem;
background-color: white;
color: rgba(25, 25, 25, 1);
}

.user-input-container input:focus {
outline: none;
border-color: rgba(18, 82, 179, 1);
box-shadow: 0 0 0 3px rgba(18, 82, 179, 0.3);
}

.input-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}

.cancel-button {
border-radius: 0.25rem;
background-color: rgba(229, 231, 235, 1);
padding: 0.25rem 0.75rem;
color: rgba(25, 25, 25, 1);
border: none;
cursor: pointer;
}

.cancel-button:hover {
background-color: rgba(209, 213, 219, 1);
}

.apply-button {
border-radius: 0.25rem;
background-color: rgba(18, 82, 179, 1);
color: white;
padding: 0.25rem 0.75rem;
border: none;
cursor: pointer;
}

.apply-button:hover {
background-color: rgba(15, 65, 143, 1);
}
33 changes: 33 additions & 0 deletions packages/scribe/src/plugins/FloatingBox/FloatingBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { forwardRef, ReactNode } from "react";

export type FloatingBoxCoords = { x: number; y: number } | undefined;

type FloatingBoxProps = {
coords: FloatingBoxCoords;
children?: ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

export const FloatingBox = forwardRef<HTMLDivElement, FloatingBoxProps>((props, ref) => {
const { coords, children, style, ...extraProps } = props;
const shouldShow = coords !== undefined;

return (
<div
ref={ref}
className="floating-box"
aria-hidden={!shouldShow}
style={{
...style,
position: "absolute",
zIndex: 1000,
top: coords?.y,
left: coords?.x,
visibility: shouldShow ? "visible" : "hidden",
opacity: shouldShow ? 1 : 0,
}}
{...extraProps}
>
{children}
</div>
);
});
43 changes: 43 additions & 0 deletions packages/scribe/src/plugins/FloatingBox/FloatingBoxAtCursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { memo, ReactNode, useMemo, useRef } from "react";
import { createPortal } from "react-dom";
import { FloatingBox } from "./FloatingBox";
import useCursorCoords from "./useCursorCoords";
import { Placement } from "@floating-ui/dom";

const DOM_ELEMENT = document.body;

const MemoizedFloatingBox = memo(FloatingBox);

export type FloatingMenuCoords = { x: number; y: number } | undefined;

type CursorFloatingBox = {
isOpen?: boolean;
children:
| ReactNode
| ((props: { isOpen: boolean | undefined; placement?: Placement }) => ReactNode);
};

/**
* FloatingBoxAtCursor component is responsible for rendering a floating menu
* at the cursor position when the isOpen prop is true
*/
export default function FloatingBoxAtCursor({ isOpen = false, children }: CursorFloatingBox) {
const floatingBoxRef = useRef<HTMLDivElement>(null);
const { coords, placement } = useCursorCoords({ isOpen, floatingBoxRef });

const renderChildren = useMemo(
() => (coords ? (typeof children === "function" ? children : () => children) : () => null),
[children, coords],
);

return createPortal(
<MemoizedFloatingBox
ref={floatingBoxRef}
coords={coords}
style={coords ? undefined : { display: "none" }}
>
{renderChildren({ isOpen, placement })}
</MemoizedFloatingBox>,
DOM_ELEMENT,
);
}
27 changes: 27 additions & 0 deletions packages/scribe/src/plugins/FloatingBox/useCursorCoords.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useEffect } from "react";
import { useFloatingPosition } from "./useFloatingPosition";

export default function useCursorCoords({
isOpen,
floatingBoxRef,
}: {
isOpen: boolean;
floatingBoxRef: React.RefObject<HTMLDivElement>;
}) {
const { coords, updatePosition, cleanup, placement } = useFloatingPosition();

useEffect(() => {
if (!isOpen || !floatingBoxRef.current) {
cleanup();
return;
}

const domRange = window.getSelection()?.getRangeAt(0);
if (domRange) {
updatePosition(domRange, floatingBoxRef.current);
return cleanup;
}
}, [isOpen, updatePosition, cleanup]);

return { coords, placement };
}
50 changes: 50 additions & 0 deletions packages/scribe/src/plugins/FloatingBox/useFloatingPosition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState, useCallback, useRef, useEffect } from "react";
import { autoUpdate, computePosition, shift, flip, Placement } from "@floating-ui/dom";

export function useFloatingPosition() {
const [coords, setCoords] = useState<{ x: number; y: number } | undefined>(undefined);
const [placement, setPlacement] = useState<Placement>();
const cleanupRef = useRef<(() => void) | null>(null);

const updatePosition = useCallback((domRange: Range, anchorElement: HTMLElement) => {
if (cleanupRef.current) {
cleanupRef.current();
}
const referenceElement =
domRange.commonAncestorContainer.nodeType === domRange.commonAncestorContainer.TEXT_NODE
? domRange
: (domRange.commonAncestorContainer as HTMLElement);

cleanupRef.current = autoUpdate(referenceElement, anchorElement, () => {
computePosition(referenceElement, anchorElement, {
placement: "bottom-start",
middleware: [shift(), flip()],
})
.then((pos) => {
setPlacement(pos.placement);
setCoords((prevCoords) =>
prevCoords?.x === pos.x && prevCoords?.y === pos.y
? prevCoords
: { x: pos.x, y: pos.y },
);
})
.catch(() => {
setCoords(undefined);
});
});
}, []);

const cleanup = useCallback(() => {
if (cleanupRef.current) {
setCoords(undefined);
cleanupRef.current();
cleanupRef.current = null;
}
}, []);

useEffect(() => {
return cleanup;
}, [cleanup]);

return { coords, placement, updatePosition, cleanup };
}
Loading
Loading