diff --git a/CLAUDE.md b/CLAUDE.md index 01276e73..5b8a5667 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ volta run pnpm nx test shared # nx via project-pinned pnpm/node ```bash # Start development server for specific package -nx dev # e.g., nx dev perf-react, nx dev platform, nx dev scribe +nx dev # e.g., nx dev perf-react, nx dev platform # Build packages nx build # Build specific package @@ -80,7 +80,6 @@ nx run-many -t extract-api # Update API reports for all packages nx dev perf-react # React-based PERF editor nx dev perf-vanilla # Vanilla JS PERF editor nx dev platform # Platform.Bible scripture editor -nx dev scribe # Scribe scripture editor ``` ### Package-specific Commands @@ -90,9 +89,6 @@ nx dev scribe # Scribe scripture editor nx dev platform # Development server nx dev:test platform # Development with testing environment -# Scribe package -nx dev scribe # Development server - # PERF packages nx dev perf-react # React PERF editor nx dev perf-vanilla # Vanilla JS PERF editor @@ -107,7 +103,6 @@ This is an Nx monorepo containing multiple scripture editor packages that share - **`libs/shared`**: Core framework-agnostic editor functionality (nodes, plugins, converters) - **`libs/shared-react`**: React-specific components and plugins extending shared functionality - **`packages/platform`**: Scripture editor for Platform.Bible with commenting and collaboration features -- **`packages/scribe`**: Lightweight scripture editor for Scribe application. **Not currently maintained** — do NOT use it as a reference for existing patterns or conventions when investigating the codebase (prefer `platform`, `shared`, `shared-react`). Touch it only when a change explicitly targets scribe. - **`demos/perf-react`**: React-based editor for PERF format with performance optimizations - **`demos/perf-vanilla`**: Vanilla JS editor for PERF format - **`packages/utilities`**: Data format conversion utilities (USJ/USX/USFM) @@ -171,7 +166,7 @@ shared (core editor functionality) ↓ shared-react (React-specific extensions) ↓ -[platform, scribe, perf-react] (application-specific implementations) +[platform, perf-react] (application-specific implementations) ``` ### Development Workflow @@ -272,7 +267,7 @@ Note `.prettierignore` excludes some files that are otherwise staged, notably `p - Order `<*Plugin />` children in `packages/platform/src/editor/Editor.tsx` alphabetically by component name. The alphabetical block starts partway through — initial plugins in the setup section (`OnSelectionChangePlugin`, `DeltaOnChangePlugin`, `ActiveTextPlugin`, …) intentionally precede it. - To find text or marker nodes in a Lexical tree, use `$getRoot().getAllTextNodes()` (`MarkerNode` extends `TextNode`, so markers are included) — the pattern the marker tests already use. For a whole-tree walk that must include element nodes (e.g. `NoteNode`, which extends `ElementNode`), use `$dfs()` from `@lexical/utils`, or `$isElementNode(node)` to type-narrow before `node.getChildren()`. Never duck-type with `typeof node.getChildren === "function"` / `as unknown as { getChildren?: ... }`. To find "the node the caret is in", read the selection's `focus` point (the live cursor end — correct even for a backward range selection), not its `anchor`. - Type Lexical values by their real exported types (`LexicalEditor`, `TextNode`, …) instead of reinventing ad-hoc structural types (e.g. `{ getEditorState: () => { read: ... } }`) that capture only the shape you happen to touch. -- In tests that render the black-box `platform` ``, get its `LexicalEditor` by passing Lexical's `` as a child (`` renders `children` inside its composer) and reading `ref.current` after the render flushes — do NOT reach for `.__lexicalEditor` off the `.editor-input` DOM node. Where the child-plugin handle isn't available — tests that deliberately go end-to-end through the public `` wrapper (it strips `children`), or scribe's `` (no children slot) — fall back to the shared `getEmbeddedLexicalEditor(container)` helper (in `libs/shared-react` `react-test.utils.tsx`), which centralizes the `.__lexicalEditor` DOM reach-in in one place; note at the call site why the child-plugin route wasn't used. Tests that own their composer should instead use `baseTestEnvironment`, which captures the editor via composer context. +- In tests that render the black-box `platform` ``, get its `LexicalEditor` by passing Lexical's `` as a child (`` renders `children` inside its composer) and reading `ref.current` after the render flushes — do NOT reach for `.__lexicalEditor` off the `.editor-input` DOM node. Where the child-plugin handle isn't available — tests that deliberately go end-to-end through the public `` wrapper, which strips `children` — fall back to the shared `getEmbeddedLexicalEditor(container)` helper (in `libs/shared-react` `react-test.utils.tsx`), which centralizes the `.__lexicalEditor` DOM reach-in in one place; note at the call site why the child-plugin route wasn't used. Tests that own their composer should instead use `baseTestEnvironment`, which captures the editor via composer context. - Keep issue-tracker references out of code and comments: no Jira IDs (e.g. `PT-4187`), internal task/QA labels (`Task 8`), or spec section numbers (`§5.5`). Code must stand on its own — those belong in PR descriptions and commit messages. When a comment needs a term of art (e.g. "yank" for a programmatic caret move), define it inline at first use. # Context 7 Library Documentation diff --git a/README.md b/README.md index 1486e242..90aefa62 100644 --- a/README.md +++ b/README.md @@ -76,23 +76,17 @@ NOTE: there is a [known limitation using PNPM with Volta](https://docs.volta.sh/ ## Nx Monorepo Build System -| Source | Demo App | -| ----------------------------------- | ---------------------- | -| [perf-vanilla](/demos/perf-vanilla) | `nx dev perf-vanilla` | -| [perf-react](/demos/perf-react) | `nx dev perf-react` | -| [platform](/demos/platform) | `nx dev platform` | -| [scribe-editor](/packages/scribe)\* | `nx dev scribe-editor` | - -\* This item appears in 2 lists as it has both the Demo App and Package in the same source folder. +| Source | Demo App | +| ----------------------------------- | --------------------- | +| [perf-vanilla](/demos/perf-vanilla) | `nx dev perf-vanilla` | +| [perf-react](/demos/perf-react) | `nx dev perf-react` | +| [platform](/demos/platform) | `nx dev platform` | | Source | Package | | ------------------------------------- | ----------------------------------------------------------------------- | | [platform-editor](/packages/platform) | [![Github Tag][npm-platform-version-image]][npm-platform-version-url] | -| [scribe-editor](/packages/scribe) | [![Github Tag][npm-scribe-version-image]][npm-scribe-version-url]\* | | [utilities](/packages/utilities) | [![Github Tag][npm-utilities-version-image]][npm-utilities-version-url] | -\* This package has not yet been published to the new NPM organization. The previous version of `scribe-editor` is here [![Github Tag][npm-bnf-scribe-version-image]][npm-bnf-scribe-version-url]. - | Source | Library | | ---------------------------------- | ---------------------- | | [shared-react](/libs/shared-react) | internal non-published | @@ -113,9 +107,6 @@ graph TB R --> SR(shared-react) SR --> S SR --> U - SB(scribe-editor) --> SR - SB --> S - SB --> U P(platform) --> PE(platform-editor) P --> T P --> U @@ -504,10 +495,6 @@ to [`eten-tech-foundation/scripture-editors`][eten-repo] possible. [npm-platform-version-image]: https://img.shields.io/github/v/tag/paranext/scripture-editors [npm-platform-version-url]: https://github.com/paranext/scripture-editors/releases -[npm-scribe-version-image]: https://img.shields.io/npm/v/@eten-tech-foundation/scribe-editor -[npm-scribe-version-url]: https://www.npmjs.com/package/@eten-tech-foundation/scribe-editor -[npm-bnf-scribe-version-image]: https://img.shields.io/npm/v/@biblionexus-foundation/scribe-editor -[npm-bnf-scribe-version-url]: https://www.npmjs.com/package/@biblionexus-foundation/scribe-editor [npm-utilities-version-image]: https://img.shields.io/github/v/tag/paranext/scripture-editors [npm-utilities-version-url]: https://github.com/paranext/scripture-editors/releases [github-license]: https://github.com/paranext/scripture-editors/blob/main/LICENSE diff --git a/eslint.config.mjs b/eslint.config.mjs index 55a18f9d..b07fb5e3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -69,7 +69,6 @@ export default [ "demos/platform/**", "libs/shared-react/**", "packages/platform/**", - "packages/scribe/**", ].flatMap((projectDir) => (config.files ?? ["**/*"]).map((pattern) => [projectDir, pattern])), })), { diff --git a/libs/shared-react/src/plugins/usj/UsjNodesMenuPlugin.tsx b/libs/shared-react/src/plugins/usj/UsjNodesMenuPlugin.tsx index 7ab5dfd9..55eeebbe 100644 --- a/libs/shared-react/src/plugins/usj/UsjNodesMenuPlugin.tsx +++ b/libs/shared-react/src/plugins/usj/UsjNodesMenuPlugin.tsx @@ -197,7 +197,7 @@ function toHarnessOptionItem( * The IN-EDITOR marker menu for editable marker modes (standard view): a document-first * `\`/Enter menu mounted by `UsjNodesMenuPlugin` in place of the legacy typeahead when * `editableHarness` is supplied. This is the real marker menu for every host that does not - * bring its own UI — the repo's demos and the scribe package. A host that does + * bring its own UI — the repo's demos. A host that does * (`EditorOptions.hasExternalUI`, e.g. Platform.Bible, which renders marker menus through its * own overlay service) never mounts the plugin, so none of this runs there. * diff --git a/libs/shared/src/nodes/usj/index.ts b/libs/shared/src/nodes/usj/index.ts index 2c0b6306..abb7d0f1 100644 --- a/libs/shared/src/nodes/usj/index.ts +++ b/libs/shared/src/nodes/usj/index.ts @@ -69,9 +69,8 @@ export const usjBaseNodes: readonly (Klass | LexicalNodeReplacement // display runs as AttributeRunNode wrappers, and this package's own self-healing sync // (displayRunSync.utils.ts's shared $syncDisplayRun driver, parameterized by each kind's own // descriptor) constructs one whenever it heals a run forward from a loose or missing shape — - // every USJ-shaped editor needs the class registered, not only shared-react's (a non-react host, - // e.g. packages/scribe's NoteEditor, builds its editor straight from usjBaseNodes with no - // react-specific node list). + // every USJ-shaped editor needs the class registered, not only shared-react's (a non-react host + // builds its editor straight from usjBaseNodes with no react-specific node list). AttributeRunNode, { replace: ParagraphNode, diff --git a/packages/scribe/.babelrc b/packages/scribe/.babelrc deleted file mode 100644 index 1ea870ea..00000000 --- a/packages/scribe/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - [ - "@nx/react/babel", - { - "runtime": "automatic", - "useBuiltIns": "usage" - } - ] - ], - "plugins": [] -} diff --git a/packages/scribe/.gitignore b/packages/scribe/.gitignore deleted file mode 100644 index a547bf36..00000000 --- a/packages/scribe/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/packages/scribe/LICENSE b/packages/scribe/LICENSE deleted file mode 100644 index 46a8177d..00000000 --- a/packages/scribe/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright © 2023-2025 ETEN Tech Foundation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/scribe/README.md b/packages/scribe/README.md deleted file mode 100644 index fa7def6b..00000000 --- a/packages/scribe/README.md +++ /dev/null @@ -1,234 +0,0 @@ -# Scripture Editor for Scribe using USJ - -
- -[![Build Status][github-actions-status]][github-actions-url] -[![CodeQL][gitghub-codeql-status]][gitghub-codeql-url] -[![Github Tag][npm-version-image]][npm-version-url] - -
- -A lightweight Scripture editor React component that works on USJ Scripture data. A utility that converts USFM to USJ is also included. It is expected that data conforms to [USJ v3.1](https://docs.usfm.bible/usfm/3.1/). - -```mermaid ---- -title: Scripture Data — Editor flow ---- -graph TB - USFM[USFM Text] --> C[USFM-USJ converter] - C --> A[USJ-Editor adapter] - A <-- Editor State --> Editor - Editor --> USJ[USJ Output] -``` - -## Install - -```sh -npm install @eten-tech-foundation/scribe-editor -``` - -## Usage - -> [!NOTE] -> This is an [uncontrolled React component](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components). - -```typescript -import { MouseEvent, useState, useMemo, useRef, useEffect } from "react"; -import Editor, { EditorRef } from "@eten-tech-foundation/scribe-editor"; -import { Usj, USJ_TYPE, USJ_VERSION } from "@eten-tech-foundation/scripture-utilities"; -import { - ScriptureReference, - SelectionRange, - UsjNodeOptions, - getViewOptions, - getDefaultViewMode, -} from "shared"; - -const defaultUsj: Usj = { - type: USJ_TYPE, - version: USJ_VERSION, - content: [], -}; - -const defaultScrRef: ScriptureReference = { book: "PSA", chapterNum: 1, verseNum: 1 }; - -const nodeOptions: UsjNodeOptions = { - noteCallerOnClick: (e: MouseEvent) => { - console.log("note node clicked", e); - }, -}; - -export default function App() { - const editorRef = useRef(null!); - const [scrRef, setScrRef] = useState(defaultScrRef); - const [viewMode] = useState(getDefaultViewMode); - const viewOptions = useMemo(() => getViewOptions(viewMode), [viewMode]); - - const onChange = async (usj: Usj) => { - console.log("USJ changed:", usj); - }; - - const onSelectionChange = (selection: SelectionRange | undefined) => { - console.log("Selection changed:", selection); - }; - - // Example: Load USJ data after component mounts - useEffect(() => { - const timeoutId = setTimeout(() => { - // Load your USJ data here - // editorRef.current?.setUsj(yourUsjData); - }, 1000); - return () => clearTimeout(timeoutId); - }, []); - - return ( -
-
-
- -
-
-
- ); -} -``` - -## Features - -- **USJ editor** with USFM support via conversion utilities -- **Read-only and edit mode** - configurable editor state -- **History** - undo & redo functionality -- **Cut, copy, paste** - context menu and keyboard shortcuts -- **Format block type** - change `` markers with a proof-of-concept implementation -- **Insert markers** - type '\\' (backslash) for a marker menu. Selected text will have the marker applied if possible -- **BCV linkage** - change the book/chapter/verse externally and the cursor moves; move the cursor and it updates the external book/chapter/verse -- **Selection tracking** - get and set cursor location or selection range -- **Auto-numbering support** - for verse and chapter numbering -- **Keyboard shortcuts** - common editing shortcuts -- **Arrow navigation** - enhanced navigation between verses and chapters -- **Text direction support** - automatic text direction detection - -### Supported USJ Nodes - -- `` - Book identification -- `` - Chapter markers -- `` - Verse markers and ranges -- `` - Paragraph styles -- `` - Character styles -- `` - Footnotes and cross-references -- `` - Milestone markers - -### Node Options - -- **Note caller customization** - callback for when a `` link is clicked -- **Custom note callers** - specify note caller characters when caller is '+' - -## API Reference - -### Editor Properties - -```typescript -type EditorProps = { - /** Scripture data in USJ form */ - usjInput?: Usj; - /** Callback function when USJ Scripture data has changed */ - onChange?: (usj: Usj) => void; - /** Callback function when the cursor selection changes */ - onSelectionChange?: (selection: SelectionRange | undefined) => void; - /** View options for rendering */ - viewOptions?: ViewOptions; - /** Node-specific options and callbacks */ - nodeOptions?: UsjNodeOptions; - /** Current Scripture reference */ - scrRef: ScriptureReference; - /** Callback function when the Scripture reference changes */ - onScrRefChange: (scrRef: ScriptureReference) => void; -}; -``` - -### Editor Ref - -```typescript -export type EditorRef = { - /** Focus the editor */ - focus(): void; - /** Get current USJ Scripture data */ - getUsj(): Usj | undefined; - /** Set the USJ Scripture data */ - setUsj(usj: Usj): void; - /** - * Get the selection location or range - * @returns the selection location or range, or `undefined` if there is no selection - */ - getSelection(): SelectionRange | undefined; - /** - * Set the selection location or range - * @param selection - A selection location or range - */ - setSelection(selection: SelectionRange): void; -}; -``` - -### Scripture Reference - -```typescript -export interface ScriptureReference { - /** Book code (e.g., "GEN", "PSA", "MAT") */ - book: string; - /** Chapter number */ - chapterNum: number; - /** Verse number */ - verseNum: number; - /** Optional verse range (e.g., "1-3" for verses 1 through 3) */ - verse?: string; -} -``` - -## Demo and Development Environment - -To run the demo app locally, first follow the [Developer Quick Start](/README.md#developer-quick-start), then run: - -```sh -nx dev scribe -``` - -## Develop in App - -To develop the editor in a target application you can use [yalc](https://www.npmjs.com/package/yalc) to link the editor without publishing to NPM every time something changes. - -1. In this monorepo, publish the editor to `yalc`: - ```bash - nx devpub scribe - ``` -2. In the target application repo, link from `yalc`: - ```bash - yalc link @eten-tech-foundation/scribe-editor - ``` -3. In this monorepo, make changes and re-publish the editor (repeat step 1). -4. When finished developing, unlink from `yalc` in the target application: - ```bash - yalc remove @eten-tech-foundation/scribe-editor && npm i - ``` - -## License - -[MIT][github-license] © [ETEN Tech Foundation](https://missionmutual.org) - - - -[github-actions-status]: https://github.com/eten-tech-foundation/scripture-editors/actions/workflows/test-publish.yml/badge.svg -[github-actions-url]: https://github.com/eten-tech-foundation/scripture-editors/actions -[gitghub-codeql-status]: https://github.com/eten-tech-foundation/scripture-editors/actions/workflows/codeql.yml/badge.svg -[gitghub-codeql-url]: https://github.com/eten-tech-foundation/scripture-editors/actions/workflows/codeql.yml -[npm-version-image]: https://img.shields.io/npm/v/@eten-tech-foundation/scribe-editor -[npm-version-url]: https://github.com/eten-tech-foundation/scripture-editors/releases -[github-license]: https://github.com/eten-tech-foundation/scripture-editors/blob/main/packages/scribe/LICENSE diff --git a/packages/scribe/eslint.config.mjs b/packages/scribe/eslint.config.mjs deleted file mode 100644 index 4760ce4a..00000000 --- a/packages/scribe/eslint.config.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import nx from "@nx/eslint-plugin"; -import reactRefreshPlugin from "eslint-plugin-react-refresh"; -import baseConfig from "../../eslint.config.mjs"; - -export default [ - ...baseConfig, - ...nx.configs["flat/react"], - { - files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], - plugins: { "react-refresh": reactRefreshPlugin }, - rules: { - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], - // TODO: remove (or at least review) - added for nx config simplification. - "@typescript-eslint/no-inferrable-types": "warn", - "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], - }, - }, -]; diff --git a/packages/scribe/index.html b/packages/scribe/index.html deleted file mode 100644 index 6e0333b8..00000000 --- a/packages/scribe/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Scribe - - -
- - - diff --git a/packages/scribe/package.json b/packages/scribe/package.json deleted file mode 100644 index a8b611fd..00000000 --- a/packages/scribe/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@eten-tech-foundation/scribe-editor", - "version": "0.2.0", - "description": "Scripture editor used in Scribe", - "license": "MIT", - "homepage": "https://github.com/eten-tech-foundation/scripture-editors/tree/main/packages/scribe#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/eten-tech-foundation/scripture-editors.git" - }, - "bugs": { - "url": "https://github.com/eten-tech-foundation/scripture-editors/issues" - }, - "type": "module", - "module": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "files": [ - "dist/**/*", - "src/index.ts", - "src/editor" - ], - "scripts": { - "dev": "vite", - "prepublishOnly": "cd ../.. && nx build scribe" - }, - "peerDependencies": { - "react": ">=18.3.1", - "react-dom": ">=18.3.1" - }, - "dependencies": { - "@eten-tech-foundation/scripture-utilities": "workspace:~", - "@floating-ui/dom": "^1.7.6", - "@lexical/mark": "^0.43.0", - "@lexical/react": "^0.43.0", - "@lexical/selection": "^0.43.0", - "@lexical/utils": "^0.43.0", - "fast-equals": "^5.4.0", - "lexical": "^0.43.0" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4.2.2", - "postcss": "^8.5.25", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "shared": "workspace:~", - "shared-react": "workspace:~", - "sj-usfm-grammar": "^3.0.8", - "tailwindcss": "^4.2.2" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/packages/scribe/postcss.config.js b/packages/scribe/postcss.config.js deleted file mode 100644 index c2ddf748..00000000 --- a/packages/scribe/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; diff --git a/packages/scribe/prettier.config.js b/packages/scribe/prettier.config.js deleted file mode 100644 index a5231f03..00000000 --- a/packages/scribe/prettier.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const config = { - plugins: ["prettier-plugin-tailwindcss"], - endOfLine: "lf", -}; - -export default config; diff --git a/packages/scribe/project.json b/packages/scribe/project.json deleted file mode 100644 index 86387b59..00000000 --- a/packages/scribe/project.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@eten-tech-foundation/scribe-editor", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "{projectRoot}/src", - "projectType": "library", - "tags": [], - "targets": { - "devpub": { - "executor": "nx:run-commands", - "dependsOn": ["build"], - "options": { - "command": "yalc push", - "cwd": "{projectRoot}" - } - } - } -} diff --git a/packages/scribe/src/App.tsx b/packages/scribe/src/App.tsx deleted file mode 100644 index 69752ee1..00000000 --- a/packages/scribe/src/App.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable no-console */ - -// Reaching inside only for app css. -// eslint-disable-next-line @nx/enforce-module-boundaries -import "../../../libs/shared/src/styles/nodes-menu.css"; -import { Usj, USJ_TYPE, USJ_VERSION } from "@eten-tech-foundation/scripture-utilities"; -import { MouseEvent, useState, useMemo, useRef, useEffect } from "react"; -import { ScriptureReference } from "shared"; -import { getDefaultViewMode, getViewOptions, SelectionRange, UsjNodeOptions } from "shared-react"; -// import { Usj2Usfm } from "@/hooks/usj2Usfm"; -import "@/styles/App.css"; -import Editor, { EditorRef } from "@/editor/Editor"; -import { useUsfm2Usj } from "@/hooks/useUsfm2Usj"; - -const defaultUsj: Usj = { - type: USJ_TYPE, - version: USJ_VERSION, - content: [], -}; -const defaultScrRef: ScriptureReference = { book: "PSA", chapterNum: 1, verseNum: 1 }; -const nodeOptions: UsjNodeOptions = { - noteCallerOnClick: (e: MouseEvent) => { - console.log("note node clicked", e); - }, -}; - -function App() { - const editorRef = useRef(null); - const [scrRef, setScrRef] = useState(defaultScrRef); - const { usj } = useUsfm2Usj(); - - useEffect(() => { - const timeoutId = setTimeout(() => { - if (usj && editorRef.current) editorRef.current.setUsj(usj); - }, 1000); - return () => clearTimeout(timeoutId); - }, [usj]); - - const [viewMode] = useState(getDefaultViewMode); - const viewOptions = useMemo(() => getViewOptions(viewMode), [viewMode]); - - const onChange = async (usj: Usj) => { - console.log(usj); - }; - - const onSelectionChange = (selection: SelectionRange | undefined) => { - console.log("Selection changed:", selection); - }; - - useEffect(() => { - console.log({ scrRef }); - }, [scrRef]); - - return ( -
-
-
- -
-
-
- ); -} -export default App; diff --git a/packages/scribe/src/components/Button.tsx b/packages/scribe/src/components/Button.tsx deleted file mode 100644 index 7efc31f0..00000000 --- a/packages/scribe/src/components/Button.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { LexicalEditor } from "lexical"; -import { ButtonHTMLAttributes, MouseEvent } from "react"; - -export default function Button({ - onClick, - children, - ...props -}: Omit, "onClick"> & { - onClick?: (e: MouseEvent, editor: LexicalEditor) => void; -}) { - const [editor] = useLexicalComposerContext(); - - return ( - - ); -} diff --git a/packages/scribe/src/components/FloatingBox/FloatingBox.tsx b/packages/scribe/src/components/FloatingBox/FloatingBox.tsx deleted file mode 100644 index cd0fcd0b..00000000 --- a/packages/scribe/src/components/FloatingBox/FloatingBox.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { forwardRef, HTMLAttributes, ReactNode } from "react"; - -export type FloatingBoxCoords = { x: number; y: number } | undefined; - -type FloatingBoxProps = { - coords: FloatingBoxCoords; - children?: ReactNode; -} & HTMLAttributes; - -export const FloatingBox = forwardRef((props, ref) => { - const { coords, children, style, ...extraProps } = props; - const shouldShow = coords !== undefined; - - return ( -
- {children} -
- ); -}); diff --git a/packages/scribe/src/components/FloatingBox/FloatingBoxAtCursor.tsx b/packages/scribe/src/components/FloatingBox/FloatingBoxAtCursor.tsx deleted file mode 100644 index 4b7364e9..00000000 --- a/packages/scribe/src/components/FloatingBox/FloatingBoxAtCursor.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { memo, ReactNode, useMemo, useRef } from "react"; -import { createPortal } from "react-dom"; -import { FloatingBox } from "./FloatingBox"; -import useCursorCoords from "@/hooks/useCursorCoords"; -import { Placement } from "@floating-ui/dom"; - -const DOM_ELEMENT = document.body; - -const MemoizedFloatingBox = memo(FloatingBox); - -export type FloatingMenuCoords = { x: number; y: number } | undefined; - -interface 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(null); - const { coords, placement } = useCursorCoords({ isOpen, floatingBoxRef }); - - const renderChildren = useMemo( - () => (coords ? (typeof children === "function" ? children : () => children) : () => null), - [children, coords], - ); - - return createPortal( - - {renderChildren({ isOpen, placement })} - , - DOM_ELEMENT, - ); -} diff --git a/packages/scribe/src/components/Input/Input.css b/packages/scribe/src/components/Input/Input.css deleted file mode 100644 index ce1e3a18..00000000 --- a/packages/scribe/src/components/Input/Input.css +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copied from @see https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/ui/Input.css - */ - -.Input__wrapper { - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 10px; -} -.Input__label { - display: flex; - flex: 1; - color: #666; -} -.Input__input { - display: flex; - flex: 2; - border: 1px solid #999; - padding-top: 7px; - padding-bottom: 7px; - padding-left: 10px; - padding-right: 10px; - font-size: 16px; - border-radius: 5px; - min-width: 0; -} -.scribe-input-wrapper { - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 10px; -} - -.scribe-input-label { - display: flex; - flex: 1; - color: #666; -} - -.scribe-input-field { - display: flex; - flex: 2; - border: 1px solid #999; - padding: 7px 10px; - font-size: 16px; - border-radius: 5px; - min-width: 0; -} - -.scribe-button { - background-color: #1976d2; - color: white; - border: none; - border-radius: 4px; - padding: 8px 16px; - font-size: 14px; - cursor: pointer; - transition: background-color 0.2s; -} - -.scribe-button:hover { - background-color: #1565c0; -} - -.scribe-button:disabled { - background-color: #cccccc; - cursor: not-allowed; -} diff --git a/packages/scribe/src/components/Input/TextInput.tsx b/packages/scribe/src/components/Input/TextInput.tsx deleted file mode 100644 index 620aea5f..00000000 --- a/packages/scribe/src/components/Input/TextInput.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import "./Input.css"; -import { LexicalEditor } from "lexical"; -import { FC, HTMLInputTypeAttribute, ReactNode, useEffect, useState } from "react"; - -interface TextInputProps { - "data-test-id"?: string; - label: string; - onChange: (val: string) => void; - placeholder?: string; - value: string; - type?: HTMLInputTypeAttribute; -} - -const TextInput: FC = ({ - label, - value, - onChange, - placeholder = "", - "data-test-id": dataTestId, - type = "text", -}) => ( -
- - onChange(e.target.value)} - data-test-id={dataTestId} - /> -
-); - -interface InsertDialogProps { - activeEditor: LexicalEditor; - onClose: () => void; - insertFunction: (editor: LexicalEditor, value?: string, noteText?: string) => void; - label: string; - placeholder?: string; -} - -export const InsertDialog: FC = ({ - activeEditor, - onClose, - insertFunction, - label, - placeholder, -}) => { - const [inputValue, setInputValue] = useState(""); - const [isDisabled, setIsDisabled] = useState(true); - - useEffect(() => { - setIsDisabled(inputValue.trim() === ""); - }, [inputValue]); - - const handleInsert = () => { - if (label === "Footnote" || label === "Crossref") { - insertFunction(activeEditor, undefined, inputValue); // noteText as third param - } else { - insertFunction(activeEditor, inputValue); // Regular value - } - setInputValue(""); - onClose(); - }; - - return ( - <> - {label === "Footnote" ? ( - // For footnote with multiple fields - setInputValue(val)} - value={inputValue} - data-test-id="note-ft" - /> - ) : ( - // For simple inputs like verse/chapter numbers - - )} - - - ); -}; - -export const Button: FC<{ - "data-test-id"?: string; - children: ReactNode; - className?: string; - disabled?: boolean; - onClick: () => void; - title?: string; -}> = ({ "data-test-id": dataTestId, children, className, onClick, disabled, title }) => ( - -); diff --git a/packages/scribe/src/components/LoadingSpinner.tsx b/packages/scribe/src/components/LoadingSpinner.tsx deleted file mode 100644 index a4856783..00000000 --- a/packages/scribe/src/components/LoadingSpinner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -export default function LoadingSpinner() { - return ( -
- - - - -
- ); -} diff --git a/packages/scribe/src/components/Modal/Modal.css b/packages/scribe/src/components/Modal/Modal.css deleted file mode 100644 index 281ad313..00000000 --- a/packages/scribe/src/components/Modal/Modal.css +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copied from @see https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/ui/Modal.css - */ - -.Modal__overlay { - display: flex; - justify-content: center; - align-items: center; - position: fixed; - flex-direction: column; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; - background-color: rgba(40, 40, 40, 0.6); - flex-grow: 0px; - flex-shrink: 1px; - z-index: 100; -} -.Modal__modal { - padding: 20px; - min-height: 100px; - min-width: 300px; - display: flex; - flex-grow: 0px; - background-color: #fff; - flex-direction: column; - position: relative; - box-shadow: 0 0 20px 0 #444; - border-radius: 10px; -} -.Modal__title { - color: #444; - margin: 0px; - padding-bottom: 10px; - border-bottom: 1px solid #ccc; -} -.Modal__closeButton { - border: 0px; - position: absolute; - right: 20px; - border-radius: 20px; - justify-content: center; - align-items: center; - display: flex; - width: 30px; - height: 30px; - text-align: center; - cursor: pointer; - background-color: #eee; -} -.Modal__closeButton:hover { - background-color: #ddd; -} -.Modal__content { - padding-top: 20px; -} diff --git a/packages/scribe/src/components/Modal/Modal.tsx b/packages/scribe/src/components/Modal/Modal.tsx deleted file mode 100644 index bc84bacb..00000000 --- a/packages/scribe/src/components/Modal/Modal.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import "./Modal.css"; -import { ReactNode, useEffect, useRef, ReactElement } from "react"; -import { createPortal } from "react-dom"; - -function PortalImpl({ - onClose, - children, - title, - closeOnClickOutside, -}: { - children: ReactNode; - closeOnClickOutside: boolean; - onClose: () => void; - title: string; -}) { - const modalRef = useRef(null); - - useEffect(() => { - if (modalRef.current !== null) { - modalRef.current.focus(); - } - }, []); - - useEffect(() => { - let modalOverlayElement: HTMLElement | null = null; - const handler = (event: KeyboardEvent) => { - if (event.key === "Escape") { - onClose(); - } - }; - const clickOutsideHandler = (event: MouseEvent) => { - const target = event.target; - if ( - modalRef.current !== null && - !modalRef.current.contains(target as Node) && - closeOnClickOutside - ) { - onClose(); - } - }; - const modelElement = modalRef.current; - if (modelElement !== null) { - modalOverlayElement = modelElement.parentElement; - if (modalOverlayElement !== null) { - modalOverlayElement.addEventListener("click", clickOutsideHandler); - } - } - - window.addEventListener("keydown", handler); - - return () => { - window.removeEventListener("keydown", handler); - if (modalOverlayElement !== null) { - modalOverlayElement?.removeEventListener("click", clickOutsideHandler); - } - }; - }, [closeOnClickOutside, onClose]); - - return ( -
-
-

{title}

- -
{children}
-
-
- ); -} - -export default function Modal({ - onClose, - children, - title, - closeOnClickOutside = false, -}: { - children: ReactNode; - closeOnClickOutside?: boolean; - onClose: () => void; - title: string; -}): ReactElement { - return createPortal( - - {children} - , - document.body, - ); -} diff --git a/packages/scribe/src/components/NodesMenu/LexicalMenuNavigation.tsx b/packages/scribe/src/components/NodesMenu/LexicalMenuNavigation.tsx deleted file mode 100644 index 433e5613..00000000 --- a/packages/scribe/src/components/NodesMenu/LexicalMenuNavigation.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useLexicalMenuNavigation } from "@/hooks/useLexicalMenuNavigation"; - -export default function LexicalMenuNavigation() { - useLexicalMenuNavigation(); - return null; -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/MenuContext.tsx b/packages/scribe/src/components/NodesMenu/Menu/MenuContext.tsx deleted file mode 100644 index 3232f406..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/MenuContext.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createContext } from "react"; -import { useMenuCore } from "./useMenuCore"; - -type MenuContextType = ReturnType; - -export const MenuContext = createContext(undefined); diff --git a/packages/scribe/src/components/NodesMenu/Menu/Option.tsx b/packages/scribe/src/components/NodesMenu/Menu/Option.tsx deleted file mode 100644 index 641ed827..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/Option.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { ButtonHTMLAttributes, forwardRef, MouseEvent, ReactNode, useCallback } from "react"; -import { useMenuContext } from "./useMenuContext"; - -type OptionProps = { - index: number; - children: ReactNode; - onClick?: (event: MouseEvent) => void; - onMouseEnter?: (event: MouseEvent) => void; -} & ButtonHTMLAttributes; - -export const MenuOption = forwardRef( - ({ index, children, onMouseEnter, onClick, ...props }, ref) => { - const { - state: { activeIndex }, - setActiveIndex, - setSelectedIndex, - select, - } = useMenuContext(); - - const handleClick = useCallback( - (event: MouseEvent) => { - select(); - setSelectedIndex(-1); - onClick?.(event); - }, - [onClick, select, setSelectedIndex], - ); - - const handleMouseEnter = useCallback( - (event: MouseEvent) => { - setActiveIndex(index); - onMouseEnter?.(event); - }, - [index, setActiveIndex, onMouseEnter], - ); - return ( - - ); - }, -); diff --git a/packages/scribe/src/components/NodesMenu/Menu/Options.tsx b/packages/scribe/src/components/NodesMenu/Menu/Options.tsx deleted file mode 100644 index e2892102..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/Options.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - Children, - cloneElement, - ComponentProps, - HTMLAttributes, - isValidElement, - ReactElement, - useEffect, - useMemo, - useRef, -} from "react"; -import { useMenuContext } from "./useMenuContext"; -import { MenuOption } from "./Option"; -import { OptionItem } from "./types"; - -type OptionElement = ReactElement, typeof MenuOption>; - -type MenuOptionsProps = Omit, "children"> & { - children: - | OptionElement - | OptionElement[] - | ((menuItems: OptionItem[]) => OptionElement | OptionElement[]); - autoIndex?: boolean; -}; - -export function MenuOptions({ children, autoIndex = true, ...divProps }: MenuOptionsProps) { - const menuRef = useRef(null); - const { - state: { activeIndex, menuItems }, - } = useMenuContext(); - - const renderChildren = useMemo( - () => (menuItems ? (typeof children === "function" ? children : () => children) : () => null), - [children, menuItems], - ); - - const mappedChildren = useMemo(() => { - const children = renderChildren(menuItems); - if (!autoIndex) return children; - - return Children.map(children, (child, index) => { - if ( - isValidElement>(child) && - child.type === MenuOption && - child.props.index === undefined - ) { - return cloneElement(child, { index }); - } - return child; - }); - }, [renderChildren, autoIndex, menuItems]); - - useEffect(() => { - if (menuRef.current) { - const menuElement = menuRef.current; - const selectedElement = menuElement.children[activeIndex] as HTMLElement; - if (selectedElement) { - const menuRect = menuElement.getBoundingClientRect(); - const selectedRect = selectedElement.getBoundingClientRect(); - - if (selectedRect.bottom > menuRect.bottom) { - menuElement.scrollTop += selectedRect.bottom - menuRect.bottom; - } else if (selectedRect.top < menuRect.top) { - menuElement.scrollTop -= menuRect.top - selectedRect.top; - } - } - } - }, [activeIndex]); - - return ( -
- {mappedChildren} -
- ); -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/Root.tsx b/packages/scribe/src/components/NodesMenu/Menu/Root.tsx deleted file mode 100644 index 2fdd639e..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/Root.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { HTMLAttributes, ReactNode } from "react"; -import { MenuContext } from "./MenuContext"; -import { useMenuCore } from "./useMenuCore"; -import { OptionItem } from "./types"; - -type MenuRootProps = { - children: ReactNode; - menuItems?: OptionItem[]; - onSelectOption?: (option: OptionItem) => void; - autoIndex?: boolean; -} & HTMLAttributes; - -export function MenuRoot({ children, menuItems, onSelectOption, ...divProps }: MenuRootProps) { - const menuContext = useMenuCore(menuItems, onSelectOption); - return ( - -
{children}
-
- ); -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/filterAndRankItems.ts b/packages/scribe/src/components/NodesMenu/Menu/filterAndRankItems.ts deleted file mode 100644 index 7f2bf08c..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/filterAndRankItems.ts +++ /dev/null @@ -1,109 +0,0 @@ -export interface Item { - [key: string]: unknown; -} - -// Default filter function -const defaultFilter = (item: T, query: string, filterBy: string): boolean => { - return getSafeValue(item, filterBy).toLowerCase().includes(query.toLowerCase()); -}; - -// Helper function to get the first string key of an object -const getFirstStringKey = (obj: T): string => { - return Object.keys(obj).find((key) => typeof obj[key] === "string") || ""; -}; - -// Helper function to safely get a string value from an item -const getSafeValue = (item: T, key: string): string => { - const value = item[key]; - return typeof value === "string" ? value : String(value); -}; - -export interface SortingOptions { - caseSensitive?: boolean; - priorityOrder?: ("exact" | "startsWith" | "contains")[]; -} - -export interface FilterAndRankItems { - query: string; - items: T[]; - filterBy?: keyof Pick; - filter?: (item: T, query: string) => boolean; - sortBy?: keyof Pick; - sortingOptions?: SortingOptions; -} - -export function filterAndRankItems( - options: - | (Omit, "filter"> & { filterBy: keyof Pick }) - | (Omit, "filterBy"> & { - filter: (item: T, query: string) => boolean; - }) - | FilterAndRankItems, -): T[] { - const { query, items, filterBy, filter, sortBy, sortingOptions } = - options as FilterAndRankItems; - const { caseSensitive = false, priorityOrder = ["exact", "startsWith", "contains"] } = - sortingOptions || {}; - - const compareQuery = caseSensitive ? query : query.toLowerCase(); - - let actualFilterBy: string; - let actualFilter: (item: T, query: string) => boolean; - - if (filter) { - actualFilter = filter; - actualFilterBy = items.length > 0 ? getFirstStringKey(items[0]) : ""; - } else { - actualFilterBy = filterBy || (items.length > 0 ? getFirstStringKey(items[0]) : ""); - actualFilter = (item: T, query: string) => defaultFilter(item, query, actualFilterBy); - } - - const actualSortBy = sortBy || actualFilterBy; - - // Create a Map to cache lowercase versions of sortBy values - const sortByCache = new Map(); - - return items - .filter((item) => { - try { - return actualFilter(item, query); - } catch (error) { - console.warn(`Error filtering item:`, item, error); - return false; - } - }) - .sort((a, b) => { - const getTextLower = (item: T) => { - if (!sortByCache.has(item)) { - sortByCache.set(item, getSafeValue(item, actualSortBy).toLowerCase()); - } - return sortByCache.get(item) ?? ""; - }; - - const textA = caseSensitive ? getSafeValue(a, actualSortBy) : getTextLower(a); - const textB = caseSensitive ? getSafeValue(b, actualSortBy) : getTextLower(b); - - for (const priority of priorityOrder) { - switch (priority) { - case "exact": - if (textA === compareQuery && textB !== compareQuery) return -1; - if (textB === compareQuery && textA !== compareQuery) return 1; - break; - case "startsWith": - if (textA.startsWith(compareQuery) && !textB.startsWith(compareQuery)) return -1; - if (textB.startsWith(compareQuery) && !textA.startsWith(compareQuery)) return 1; - break; - case "contains": { - const indexA = textA.indexOf(compareQuery); - const indexB = textB.indexOf(compareQuery); - if (indexA !== -1 && indexB === -1) return -1; - if (indexB !== -1 && indexA === -1) return 1; - if (indexA !== -1 && indexB !== -1) return indexA - indexB; - break; - } - } - } - - return textA.localeCompare(textB); - }); -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/index.tsx b/packages/scribe/src/components/NodesMenu/Menu/index.tsx deleted file mode 100644 index f85ecbe1..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { MenuRoot } from "./Root"; -import { MenuOptions } from "./Options"; -import { MenuOption } from "./Option"; - -export type { OptionItem } from "./types"; - -export default { - Root: MenuRoot, - Options: MenuOptions, - Option: MenuOption, -}; diff --git a/packages/scribe/src/components/NodesMenu/Menu/types.ts b/packages/scribe/src/components/NodesMenu/Menu/types.ts deleted file mode 100644 index 032e5531..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { LexicalEditor } from "lexical"; -import { Item } from "./filterAndRankItems"; - -export interface OptionItem extends Item { - name: string; - label: string; - description: string; - action: ({ - editor, - newVerseRChapterNum, - noteText, - }: { - editor: LexicalEditor; - newVerseRChapterNum?: number; - noteText?: string; - }) => void; -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/useFilteredItems.tsx b/packages/scribe/src/components/NodesMenu/Menu/useFilteredItems.tsx deleted file mode 100644 index 70d97819..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/useFilteredItems.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useMemo } from "react"; -import { filterAndRankItems, Item, FilterAndRankItems } from "./filterAndRankItems"; - -export function useFilteredItems( - props: - | (Omit, "filter"> & { filterBy: keyof Pick }) - | (Omit, "filterBy"> & { - filter: (item: T, query: string) => boolean; - }), -): T[] { - const { query, items, filterBy, filter, sortBy, sortingOptions } = props as FilterAndRankItems; - - const filteredItems = useMemo(() => { - return filterAndRankItems({ - query, - items, - filterBy, - filter, - sortBy, - sortingOptions, - }); - }, [query, items, filterBy, filter, sortBy, sortingOptions]); - - return filteredItems; -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/useMenuActions.tsx b/packages/scribe/src/components/NodesMenu/Menu/useMenuActions.tsx deleted file mode 100644 index f75506f5..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/useMenuActions.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useMemo } from "react"; -import { useMenuContext } from "./useMenuContext"; - -export function useMenuActions() { - const { moveUp, moveDown, select } = useMenuContext(); - - return useMemo( - () => ({ - moveUp, - moveDown, - select, - }), - [moveUp, moveDown, select], - ); -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/useMenuContext.tsx b/packages/scribe/src/components/NodesMenu/Menu/useMenuContext.tsx deleted file mode 100644 index fa1c5231..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/useMenuContext.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useContext } from "react"; -import { MenuContext } from "./MenuContext"; - -export function useMenuContext() { - const context = useContext(MenuContext); - if (!context) { - throw new Error("useMenuContext must be used within a MenuProvider"); - } - return context; -} diff --git a/packages/scribe/src/components/NodesMenu/Menu/useMenuCore.tsx b/packages/scribe/src/components/NodesMenu/Menu/useMenuCore.tsx deleted file mode 100644 index 8f1cca19..00000000 --- a/packages/scribe/src/components/NodesMenu/Menu/useMenuCore.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useState, useCallback, useMemo } from "react"; -import { OptionItem } from "./types"; - -export interface State { - menuItems: OptionItem[]; - activeIndex: number; - selectedIndex: number; - onSelectOption: (option: OptionItem) => void; -} - -export function useMenuCore( - initialMenuItems?: OptionItem[], - onSelectOption?: (option: OptionItem) => void, -) { - const [activeIndex, setActiveIndex] = useState(0); - const [selectedIndex, setSelectedIndex] = useState(-1); - const menuItems = useMemo(() => initialMenuItems ?? [], [initialMenuItems]); - - const state: State = { - menuItems, - activeIndex, - selectedIndex, - onSelectOption: onSelectOption ?? (() => undefined), - }; - - const moveUp = useCallback(() => { - setActiveIndex((prev) => { - const optionsCount = menuItems.length; - return optionsCount ? (prev - 1 + optionsCount) % optionsCount : 0; - }); - }, [menuItems.length]); - - const moveDown = useCallback(() => { - setActiveIndex((prev) => { - const optionsCount = menuItems.length; - return optionsCount ? (prev + 1) % optionsCount : 0; - }); - }, [menuItems.length]); - - const select = useCallback(() => { - const optionsCount = menuItems.length; - if (activeIndex >= 0 && activeIndex < optionsCount) { - const selectedOption = menuItems[activeIndex]; - onSelectOption?.(selectedOption); - setSelectedIndex(activeIndex); - } - }, [activeIndex, menuItems, onSelectOption]); - - return { - state, - moveUp, - moveDown, - select, - setActiveIndex, - setSelectedIndex, - }; -} diff --git a/packages/scribe/src/components/NodesMenu/NodeSelectionMenu.tsx b/packages/scribe/src/components/NodesMenu/NodeSelectionMenu.tsx deleted file mode 100644 index 389dccc4..00000000 --- a/packages/scribe/src/components/NodesMenu/NodeSelectionMenu.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useEffect, useState } from "react"; -import Menu, { OptionItem } from "./Menu"; -import { useFilteredItems } from "./Menu/useFilteredItems"; -import { COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND } from "lexical"; -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import LexicalMenuNavigation from "./LexicalMenuNavigation"; - -interface NodeSelectionMenuProps { - options: OptionItem[]; - onSelectOption?: (option: OptionItem) => void; - onClose?: () => void; - inverse?: boolean; - query?: string; -} - -export function NodeSelectionMenu({ - options, - onSelectOption, - onClose, - inverse, - query: controlledQuery, -}: NodeSelectionMenuProps) { - const [editor] = useLexicalComposerContext(); - const isControlled = controlledQuery !== undefined; - const [query, setQuery] = useState(""); - const localQuery = isControlled ? controlledQuery : query; - - const filteredOptions = useFilteredItems({ query: localQuery, items: options, filterBy: "name" }); - - const handleOptionSelection = (option: OptionItem) => { - onClose?.(); - if (onSelectOption) onSelectOption(option); - else option.action({ editor }); - }; - - useEffect(() => { - return editor.registerCommand( - KEY_DOWN_COMMAND, - (event) => { - if (isControlled) return false; - const actions: { [key: string]: () => void } = { - Escape: () => onClose?.(), - Backspace: () => { - if (localQuery.length === 0) { - onClose?.(); - } else { - setQuery((prev) => prev.slice(0, -1)); - } - }, - }; - const action = actions[event.key]; - if (action) { - event.stopPropagation(); - event.preventDefault(); - action(); - return true; - } else if (event.key.length === 1) { - event.stopPropagation(); - event.preventDefault(); - setQuery((prev) => prev + event.key); - return true; - } - return false; - }, - COMMAND_PRIORITY_HIGH, - ); - }, [editor, isControlled, localQuery, onClose]); - - return ( - handleOptionSelection(item)} - > - {!isControlled && } - - - {(options) => { - const mappedOptions = options.map((option, index) => ( - - {option.label ?? option.name} - {option.description} - - )); - return mappedOptions; - }} - - - ); -} diff --git a/packages/scribe/src/components/NodesMenu/NodesMenu.tsx b/packages/scribe/src/components/NodesMenu/NodesMenu.tsx deleted file mode 100644 index d4974f51..00000000 --- a/packages/scribe/src/components/NodesMenu/NodesMenu.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { $getSelection, $isRangeSelection } from "lexical"; -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { useCallback, useEffect, useState } from "react"; -import FloatingBoxAtCursor from "@/components/FloatingBox/FloatingBoxAtCursor"; -import { NodeSelectionMenu } from "./NodeSelectionMenu"; -import { OptionItem } from "./Menu"; - -export default function NodesMenu({ - trigger, - items, - autoNumbering = true, -}: { - trigger: string; - items?: OptionItem[]; - autoNumbering?: boolean; -}) { - const [editor] = useLexicalComposerContext(); - const [isOpen, setIsOpen] = useState(false); - const [userInputValue, setUserInputValue] = useState(""); - const [selectedOption, setSelectedOption] = useState(null); - const [isRequestingInput, setIsRequestingInput] = useState(false); - - const handleInputSubmit = useCallback(() => { - if (selectedOption && userInputValue.trim()) { - try { - if (selectedOption.name === "c" || selectedOption.name === "v") { - const newVerseRChapterNum = parseInt(userInputValue); - if (isNaN(newVerseRChapterNum)) { - console.error("Invalid number input"); - return; - } - selectedOption.action({ editor, newVerseRChapterNum }); - } else if (selectedOption.name === "f" || selectedOption.name === "x") { - selectedOption.action({ editor, noteText: userInputValue }); - } else { - selectedOption.action({ editor }); - } - - console.log("Submitted: ", selectedOption.name, userInputValue); - } catch (error) { - console.error("Error processing input:", error); - } - - setIsRequestingInput(false); - setUserInputValue(""); - setSelectedOption(null); - editor.focus(); - } - }, [editor, selectedOption, userInputValue]); - - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.key === "Escape" && (isOpen || isRequestingInput)) { - setIsOpen(false); - setIsRequestingInput(false); - setUserInputValue(""); - editor.focus(); - } else if (e.key === trigger && !isOpen && !isRequestingInput) { - e.preventDefault(); - setIsOpen(true); - } else if (e.key === "Enter") { - if (isRequestingInput) { - e.preventDefault(); - handleInputSubmit(); - } - } - }, - [editor, handleInputSubmit, isOpen, isRequestingInput, trigger], - ); - - useEffect(() => { - return editor.registerRootListener((root) => { - if (!root) return; - root.addEventListener("keydown", handleKeyDown); - return () => { - root.removeEventListener("keydown", handleKeyDown); - }; - }); - }, [editor, handleKeyDown]); - - useEffect(() => { - return editor.registerUpdateListener(({ prevEditorState, editorState }) => { - const prevSelection = prevEditorState.read(() => { - const selection = $getSelection(); - if (!$isRangeSelection(selection)) return; - return selection; - }); - editorState.read(() => { - const selection = $getSelection(); - if (!$isRangeSelection(selection) || prevSelection?.is(selection)) return; - setIsOpen(false); - setIsRequestingInput(false); - }); - }); - }, [editor]); - - const handleOptionSelection = useCallback( - (option: OptionItem) => { - const needsUserInput = - (!autoNumbering && (option.name === "c" || option.name === "v")) || - option.name === "f" || - option.name === "x"; - console.log({ needsUserInput }, !autoNumbering, option.name); - if (needsUserInput) { - setSelectedOption(option); - setIsRequestingInput(true); - setIsOpen(false); - - // Prevent any default actions from executing immediately - setTimeout(() => { - // Focus on input when it appears - const inputElement = document.querySelector(".user-input-container input"); - if (inputElement) { - (inputElement as HTMLInputElement).focus(); - } - }, 0); - } else { - option.action({ editor }); - setIsOpen(false); - } - }, - [autoNumbering, editor], - ); - - return ( - <> - {items && isOpen && ( - - {({ placement }) => ( - setIsOpen(false)} - inverse={placement === "top-start"} - /> - )} - - )} - - {/* User Input Dialog - shown when input is needed */} - {isRequestingInput && selectedOption && ( - - {() => ( -
-
- {selectedOption.name === "c" - ? "Enter chapter number:" - : selectedOption.name === "v" - ? "Enter verse number:" - : selectedOption.name === "f" - ? "Enter footnote text:" - : selectedOption.name === "x" - ? "Enter cross-reference:" - : "Enter text:"} -
- setUserInputValue(e.target.value)} - autoFocus - className="mb-3 w-full rounded border border-gray-300 p-2" - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - handleInputSubmit(); - } - }} - /> -
- - -
-
- )} -
- )} - - ); -} diff --git a/packages/scribe/src/editor/CommandMenuPlugin.gate.test.tsx b/packages/scribe/src/editor/CommandMenuPlugin.gate.test.tsx deleted file mode 100644 index 9971b55b..00000000 --- a/packages/scribe/src/editor/CommandMenuPlugin.gate.test.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import Editor from "./Editor"; -import { act, render } from "@testing-library/react"; -import { Usj } from "@eten-tech-foundation/scripture-utilities"; -import { ScriptureReference } from "shared"; -import { KEY_DOWN_COMMAND, LexicalEditor } from "lexical"; -import { FORMATTED_VIEW_MODE, getViewOptions, STANDARD_VIEW_MODE } from "shared-react"; -// Reaching inside only for tests. -// eslint-disable-next-line @nx/enforce-module-boundaries -import { getEmbeddedLexicalEditor } from "../../../../libs/shared-react/src/plugins/usj/react-test.utils"; - -// The Editor-level gate controls shared-react's `CommandMenuPlugin`, which `preventDefault`s a -// typed or pasted `\` and `/` so those characters never land in the document. Editable marker -// modes (Standard view) need a literal `\` to reach the editor - the marker-edit engine and the -// `\` marker menu both consume it - so the gate leaves CommandMenuPlugin UNMOUNTED there. In -// non-editable views (Formatted) a literal `\`/`/` is garbage, so the gate keeps CommandMenuPlugin -// mounted to swallow it. (Mirrors packages/platform/src/editor/CommandMenuPlugin.gate.test.tsx.) -// -// These tests assert the gate's real, user-facing effect rather than merely which plugin mounts: -// they render the actual Editor (no mock) and dispatch a real `\`/`/` keydown through Lexical's -// command pipeline, then check whether a handler called `preventDefault`. The key is allowed to -// land in Standard view and is blocked in Formatted view. - -// jsdom implements `getBoundingClientRect` on Element but not on Range. Scribe's Editor mounts -// `AutoFocusPlugin`, which focuses the editor root as soon as it renders; once focused, Lexical's -// post-commit scroll-into-view step reads a native `Range`'s bounding rect to decide whether to -// scroll, and jsdom's missing method throws (as an unhandled async rejection, after the render -// has already committed). Stub it the same way jsdom already stubs Element's version and the -// platform marker-edit tests already do (a zero rect nothing here asserts on) - see -// packages/platform/src/editor/markerEdit/markerEditDeletion.utils.test.tsx. -if (typeof Range.prototype.getBoundingClientRect !== "function") { - Range.prototype.getBoundingClientRect = function (): DOMRect { - return { - x: 0, - y: 0, - width: 0, - height: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - toJSON() { - return this; - }, - }; - }; -} - -const sampleUsj: Usj = { - type: "USJ", - version: "3.1", - content: [ - { type: "book", marker: "id", code: "GEN", content: ["Test Book"] }, - { type: "chapter", marker: "c", number: "1" }, - { - type: "para", - marker: "p", - content: [{ type: "verse", marker: "v", number: "1" }, "first verse text"], - }, - ], -}; -const scrRef: ScriptureReference = { book: "GEN", chapterNum: 1, verseNum: 1 }; - -/** Renders the real Editor in `viewMode` and returns its underlying Lexical editor. */ -async function renderEditor(viewMode: string): Promise { - let container: HTMLElement | undefined; - await act(async () => { - const result = render( - undefined} - />, - ); - container = result.container; - }); - // scribe's has no `children` slot, so the cleaner EditorRefPlugin-child handle isn't - // available — read the editor off the mounted DOM instead. - return getEmbeddedLexicalEditor(container); -} - -/** - * Dispatches a cancelable `keydown` for `key` through the editor's command pipeline and reports - * whether a handler blocked it (called `preventDefault`). - */ -async function keyWasBlocked(editor: LexicalEditor, key: string): Promise { - const event = new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }); - await act(async () => { - editor.dispatchCommand(KEY_DOWN_COMMAND, event); - }); - return event.defaultPrevented; -} - -describe("CommandMenuPlugin editable-mode gate", () => { - it("lets a literal \\ and / land in editable marker mode (Standard view)", async () => { - const editor = await renderEditor(STANDARD_VIEW_MODE); - expect(await keyWasBlocked(editor, "\\")).toBe(false); - expect(await keyWasBlocked(editor, "/")).toBe(false); - }); - - it("blocks a literal \\ and / in a non-editable marker mode (Formatted view)", async () => { - const editor = await renderEditor(FORMATTED_VIEW_MODE); - expect(await keyWasBlocked(editor, "\\")).toBe(true); - expect(await keyWasBlocked(editor, "/")).toBe(true); - }); -}); diff --git a/packages/scribe/src/editor/Editor.tsx b/packages/scribe/src/editor/Editor.tsx deleted file mode 100644 index faf44337..00000000 --- a/packages/scribe/src/editor/Editor.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { isBlockVerseLayout } from "shared-react"; -import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin"; -import { LexicalComposer } from "@lexical/react/LexicalComposer"; -import { ContentEditable } from "@lexical/react/LexicalContentEditable"; -import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary"; -import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; -import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"; -import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; -import { Usj } from "@eten-tech-foundation/scripture-utilities"; -import { deepEqual } from "fast-equals"; -import { EditorState, LexicalEditor } from "lexical"; -import { - ForwardedRef, - forwardRef, - ReactElement, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, -} from "react"; -import { ScriptureReference, blackListedChangeTags } from "shared"; -import { - $getUsjSelectionFromEditor, - ArrowNavigationPlugin, - ClipboardPlugin, - CommandMenuPlugin, - ContextMenuPlugin, - getViewClassList, - LoadStatePlugin, - NoteNodePlugin, - OnSelectionChangePlugin, - ParaNodePlugin, - SelectionRange, - TextDirectionPlugin, - TextSpacingPlugin, - UsjNodeOptions, - usjReactNodes, - ViewOptions, -} from "shared-react"; -import editorUsjAdaptor from "./adaptors/editor-usj.adaptor"; -import usjEditorAdaptor from "./adaptors/usj-editor.adaptor"; -import { getUsjMarkerAction } from "./adaptors/usj-marker-action.utils"; -import useDeferredState from "@/hooks/use-deferred-state.hook"; -import KeyboardShortcutPlugin from "./plugins/KeyboardShortcutPlugin"; -import { ScriptureReferencePlugin } from "./plugins/ScriptureReferencePlugin"; -import UsjNodesMenuPlugin from "./plugins/UsjNodesMenuPlugin"; -import editorTheme from "./themes/editor-theme"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import { Toolbar } from "./Toolbar"; - -/** Forward reference for the editor. */ -export interface EditorRef { - /** Method to focus the editor. */ - focus(): void; - /** Method to get the USJ Scripture data. */ - getUsj(): Usj | undefined; - /** Method to set the USJ Scripture data. */ - setUsj(usj: Usj): void; - /** - * Get the selection location or range. - * @returns the selection location or range, or `undefined` if there is no selection. - */ - getSelection(): SelectionRange | undefined; - /** - * Set the selection location or range. - * @param selection - A selection location or range. - */ - setSelection(selection: SelectionRange): void; -} - -/** Options to configure the editor. */ -export interface EditorOptions { - /** Is the editor readonly or editable. */ - isReadonly?: boolean; - /** Is the editor enabled for spell checking. */ - hasSpellCheck?: boolean; - /** View options. */ - view?: ViewOptions; - /** Options for each editor node: - * @param nodes[].noteCallers - Possible note callers to use when caller is '+' for - * ImmutableNoteCallerNode. - * @param nodes[].onClick - Click handler method for ImmutableNoteCallerNode. - */ - nodes?: UsjNodeOptions; -} - -interface EditorProps { - /** Scripture data in USJ form */ - usjInput?: Usj; - onChange?: (usj: Usj) => void; - /** Callback function when the cursor selection changes. */ - onSelectionChange?: (selection: SelectionRange | undefined) => void; - viewOptions?: ViewOptions; - nodeOptions?: UsjNodeOptions; - scrRef: ScriptureReference; - onScrRefChange: (scrRef: ScriptureReference) => void; -} -// const NODE_MENU_TRIGGER = "//"; - -const Editor = forwardRef(function Editor( - { - usjInput, - onChange, - onSelectionChange, - viewOptions, - nodeOptions = {}, - scrRef, - onScrRefChange, - }: EditorProps, - ref: ForwardedRef, -): ReactElement { - const editorRef = useRef(null); - const editedUsjRef = useRef(undefined); - const expandedNoteKeyRef = useRef(undefined); - const [usj, setUsj] = useState(usjInput); - const [loadedUsj] = useDeferredState(usj); - const autoNumbering = false; - // Scribe shares the `ViewOptions` type but not the block verse implementation: its adaptor does - // not regroup verses and its editor does not register `VerseBlockNode`. Say so rather than - // silently rendering an ordinary editable editor for a layout the host asked to be read-only. - // From an effect so one misconfiguration is reported once rather than on every render. - const isUnsupportedBlockVerse = isBlockVerseLayout(viewOptions); - useEffect(() => { - if (isUnsupportedBlockVerse) - // eslint-disable-next-line no-console -- scribe's editor has no logger to report through. - console.error( - "Scribe editor: `verseLayout: 'block'` is not supported here and is ignored. The block " + - "verse layout is implemented in @eten-tech-foundation/platform-editor.", - ); - }, [isUnsupportedBlockVerse]); - - const initialConfig = { - namespace: "ScribeEditor", - editable: true, - editorState: undefined, - theme: editorTheme, - onError(error: Error) { - throw error; - }, - nodes: usjReactNodes, - }; - - useImperativeHandle(ref, () => ({ - focus() { - editorRef.current?.focus(); - }, - getUsj() { - return editedUsjRef.current; - }, - setUsj(editedUsj) { - if (!deepEqual(editedUsjRef.current, editedUsj) && !deepEqual(usj, editedUsj)) { - editedUsjRef.current = editedUsj; - setUsj(editedUsj); - } - }, - getSelection() { - return editorRef.current?.read(() => $getUsjSelectionFromEditor()); - }, - setSelection(_selection: SelectionRange) { - // Implementation needed - will be added later - }, - })); - - const handleChange = useCallback( - (editorState: EditorState, _editor: LexicalEditor, tags: Set) => { - if (blackListedChangeTags.some((tag) => tags.has(tag))) return; - - // const serializedState = editor.parseEditorState(usjEditorAdaptor.serializeEditorState(usj)); - // console.log({ serializedState }); - const newUsj = editorUsjAdaptor.deserializeEditorState(editorState); - if (newUsj) { - const isEdited = !deepEqual(editedUsjRef.current, newUsj); - if (isEdited) editedUsjRef.current = newUsj; - if (isEdited || !deepEqual(usj, newUsj)) onChange?.(newUsj); - } - }, - [onChange, usj], - ); - - return ( - - - - } - placeholder={} - ErrorBoundary={LexicalErrorBoundary} - /> - - {scrRef && ( - - getUsjMarkerAction(marker, markerData, viewOptions) - } - autoNumbering={autoNumbering} - /> - )} - - - - - - - - - - - {/* Editable marker modes require literal backslash input (the marker-edit engine and the - `\` marker menu consume it), so CommandMenuPlugin - which preventDefaults typed or pasted - `\` and `/` - only guards the non-editable views (mirrors - packages/platform/src/editor/Editor.tsx). */} - {viewOptions?.markerMode !== "editable" && } - - - - - - ); -}); - -export default Editor; diff --git a/packages/scribe/src/editor/NoteEditor.tsx b/packages/scribe/src/editor/NoteEditor.tsx deleted file mode 100644 index 0873fb9b..00000000 --- a/packages/scribe/src/editor/NoteEditor.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import editorUsjAdaptor from "./adaptors/editor-usj.adaptor"; -import usjNoteEditorAdapter from "./adaptors/note-usj-editor.adaptor"; -import editorTheme from "./themes/editor-theme"; -import { Usj } from "@eten-tech-foundation/scripture-utilities"; -import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin"; -import { LexicalComposer } from "@lexical/react/LexicalComposer"; -import { ContentEditable } from "@lexical/react/LexicalContentEditable"; -import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary"; -import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; -import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"; -import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; -import { EditorState } from "lexical"; -import { useCallback, useEffect, useRef } from "react"; -import { usjBaseNodes } from "shared"; -import { - ImmutableNoteCallerNode, - LoadStatePlugin, - NoteNodePlugin, - UsjNodeOptions, - ViewOptions, -} from "shared-react"; - -interface NoteEditorProps { - /** Scripture data in USJ form */ - usj?: Usj; - onChange?: (usj: Usj) => void; - viewOptions?: ViewOptions; - nodeOptions?: UsjNodeOptions; - scrollId?: string; -} - -export const NoteEditor = ({ - usj, - onChange, - viewOptions, - nodeOptions = {}, - scrollId, -}: NoteEditorProps) => { - const expandedNoteKeyRef = useRef(undefined); - - const initialConfig = { - namespace: "ScribeNoteEditor", - editable: true, - editorState: null, - theme: editorTheme, - onError(error: Error) { - throw error; - }, - nodes: [ImmutableNoteCallerNode, ...usjBaseNodes], - }; - - const handleChange = useCallback( - (editorState: EditorState) => { - const usj = editorUsjAdaptor.deserializeEditorState(editorState); - if (usj && onChange) { - onChange(usj); - } - }, - [onChange], - ); - useEffect(() => { - const noteEditor = document.getElementById("noteEditor"); - if (scrollId && noteEditor) { - const element = noteEditor.querySelector(`[data-caller-id="${scrollId}"]`); - if (element) { - console.log("scrolling", element); - element.scrollIntoView({ behavior: "smooth", block: "center" }); - } - } - }, [scrollId]); - - return ( - - } - placeholder={
Enter some text...
} - ErrorBoundary={LexicalErrorBoundary} - /> - - - - - - {/* */} -
- ); -}; diff --git a/packages/scribe/src/editor/Toolbar.css b/packages/scribe/src/editor/Toolbar.css deleted file mode 100644 index 6944cf62..00000000 --- a/packages/scribe/src/editor/Toolbar.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Toolbar.css */ -.toolbar { - position: fixed; - left: 0; - right: 0; - top: 0; - z-index: 10; - display: flex; - align-items: center; - justify-content: flex-start; - background-color: #e5e7eb; /* equivalent to bg-gray-200 */ - padding: 0.5rem; /* equivalent to px-2 py-2 */ -} - -.toolbar-buttons { - display: flex; - gap: 0.5rem; /* equivalent to space-x-2 */ -} - -.scribe-toolbar { - display: flex; - justify-content: space-between; - align-items: center; - background-color: #f0f0f0; - padding: 8px; - border-bottom: 1px solid #ddd; -} - -.scribe-toolbar-group { - display: flex; - gap: 8px; - position: relative; -} - -.scribe-active { - background-color: #e0e0e0; - border-color: #aaa; -} - -.scribe-dropdown { - position: absolute; - top: 100%; - left: 0; - background-color: white; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - z-index: 1000; - min-width: 180px; - margin-top: 4px; -} - -.scribe-dropdown-item { - display: block; - width: 100%; - padding: 8px 12px; - text-align: left; - background: none; - border: none; - cursor: pointer; -} - -.scribe-dropdown-item:hover { - background-color: #f5f5f5; -} - -.scribe-status { - padding: 4px 8px; - background-color: #e8f5e9; - border-radius: 4px; - color: #388e3c; - font-size: 12px; - font-weight: 500; -} diff --git a/packages/scribe/src/editor/Toolbar.tsx b/packages/scribe/src/editor/Toolbar.tsx deleted file mode 100644 index 023b8ed2..00000000 --- a/packages/scribe/src/editor/Toolbar.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { useState, useRef, useEffect } from "react"; -import { REDO_COMMAND, UNDO_COMMAND } from "lexical"; -import { ScriptureReference } from "shared"; -import { ViewOptions } from "shared-react"; -import "./Toolbar.css"; -import { getUsjMarkerAction } from "./adaptors/usj-marker-action.utils"; -import useModal from "@/hooks/useModal"; -import Button from "@/components/Button"; -import { InsertDialog } from "@/components/Input/TextInput"; - -export const Toolbar = ({ - scrRef, - viewOptions, - autoNumbering = false, -}: { - scrRef: ScriptureReference; - viewOptions?: ViewOptions; - autoNumbering?: boolean; -}) => { - const [modal, showModal] = useModal(); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const dropdownRef = useRef(null); - const [editor] = useLexicalComposerContext(); - const [isEditable, setIsEditable] = useState(true); - - interface InsertOption { - title: string; - marker: string; - requiresValue: boolean; - placeholder?: string; // Optional since not all items have it - } - - const insertOptions: InsertOption[] = [ - { - title: "Verse", - marker: "v", - requiresValue: true, - placeholder: "Verse Number", - }, - { - title: "Chapter", - marker: "c", - requiresValue: true, - placeholder: "Chapter Number", - }, - { - title: "Footnote", - marker: "f", - requiresValue: true, - }, - { - title: "Cross Reference", - marker: "x", - requiresValue: true, - }, - ]; - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsDropdownOpen(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - const handleCopy = () => { - document.execCommand("copy"); - }; - - const handleCut = () => { - document.execCommand("cut"); - }; - - const handlePaste = () => { - document.execCommand("paste"); - }; - - const handleUndo = () => { - editor.dispatchCommand(UNDO_COMMAND, undefined); - }; - - const handleRedo = () => { - editor.dispatchCommand(REDO_COMMAND, undefined); - }; - - const handleInsertOption = (option: InsertOption) => { - setIsDropdownOpen(false); - - if (option.requiresValue) { - // For options that need input, show a modal - showModal(`Insert ${option.title}`, (onClose) => ( - { - const markerAction = getUsjMarkerAction(option.marker, undefined, viewOptions); - if (markerAction && markerAction.action) { - markerAction.action({ - reference: scrRef, - editor: editor, - autoNumbering: autoNumbering, - newVerseRChapterNum: value ? parseInt(value) : undefined, - noteText: noteText, - }); - } - }} - /> - )); - } else { - // For options that don't need input, insert directly - const markerAction = getUsjMarkerAction(option.marker, undefined, viewOptions); - if (markerAction && markerAction.action) { - markerAction.action({ - reference: scrRef, - editor: editor, - autoNumbering: autoNumbering, - }); - } - } - }; - - return ( -
-
- - -
- -
- - - -
- -
- - - {isDropdownOpen && ( -
- {insertOptions.map((option) => ( - - ))} -
- )} -
- -
- -
- - {modal} -
- ); -}; - -export default Toolbar; diff --git a/packages/scribe/src/editor/adaptors/editor-usj.adaptor.ts b/packages/scribe/src/editor/adaptors/editor-usj.adaptor.ts deleted file mode 100644 index 8a77feed..00000000 --- a/packages/scribe/src/editor/adaptors/editor-usj.adaptor.ts +++ /dev/null @@ -1,456 +0,0 @@ -import { - BookCode, - MarkerContent, - MarkerObject, - USJ_TYPE, - USJ_VERSION, - Usj, -} from "@eten-tech-foundation/scripture-utilities"; -import { - EditorState, - LineBreakNode, - SerializedLexicalNode, - SerializedTextNode, - TextNode, -} from "lexical"; -import { - BookNode, - ChapterNode, - CharNode, - COMMENT_MARK_TYPE, - ENDING_MS_COMMENT_MARKER, - getEditableCallerText, - ImmutableChapterNode, - ImmutableTypedTextNode, - ImmutableUnmatchedNode, - isSerializedImpliedParaNode, - isSerializedTypedMarkNode, - LoggerBasic, - MarkerNode, - MILESTONE_VERSION, - MilestoneNode, - NBSP, - NoteNode, - ParaNode, - parseNumberFromMarkerText, - removeUndefinedProperties, - SerializedBookNode, - SerializedChapterNode, - SerializedCharNode, - SerializedImmutableChapterNode, - SerializedImmutableUnmatchedNode, - SerializedImpliedParaNode, - SerializedMilestoneNode, - SerializedNoteNode, - SerializedParaNode, - SerializedTypedMarkNode, - SerializedUnknownNode, - SerializedVerseNode, - STARTING_MS_COMMENT_MARKER, - TypedMarkNode, - UnknownNode, - UNMATCHED_TAG_NAME, - VerseNode, -} from "shared"; -import { - ImmutableNoteCallerNode, - ImmutableVerseNode, - SerializedImmutableVerseNode, -} from "shared-react"; - -interface EditorUsjAdaptor { - initialize: typeof initialize; - deserializeEditorState: typeof deserializeEditorState; -} - -/** Logger instance */ -let _logger: LoggerBasic; - -export function initialize(logger: LoggerBasic | undefined) { - if (logger) _logger = logger; -} - -export function deserializeEditorState(editorState: EditorState): Usj | undefined { - if (editorState.isEmpty()) return { type: USJ_TYPE, version: USJ_VERSION, content: [] }; - - const serializedEditorState = editorState.toJSON(); - if (!serializedEditorState.root || !serializedEditorState.root.children) return; - - const rootChildren = serializedEditorState.root.children; - // check for default empty para node - if ( - rootChildren.length === 1 && - rootChildren[0].type === "para" && - (rootChildren[0] as SerializedParaNode).marker === "p" && - (!(rootChildren[0] as SerializedParaNode).children || - (rootChildren[0] as SerializedParaNode).children.length === 0) - ) - return { type: USJ_TYPE, version: USJ_VERSION, content: [] }; - - const children = removeImpliedParasRecurse(rootChildren); - const content = recurseNodes(children); - if (!content) return; - - const usj: Usj = { type: USJ_TYPE, version: USJ_VERSION, content }; - return usj; -} - -function createBookMarker( - node: SerializedBookNode, - content: MarkerContent[] | undefined, -): MarkerObject { - const { type, marker, unknownAttributes } = node; - let code: BookCode | undefined; - if (node.code !== "") code = node.code; - return removeUndefinedProperties({ - type, - marker, - code, - ...unknownAttributes, - content, - }); -} - -function createImmutableChapterMarker(node: SerializedImmutableChapterNode): MarkerObject { - const { marker, number, sid, altnumber, pubnumber, unknownAttributes } = node; - return removeUndefinedProperties({ - type: ChapterNode.getType(), - marker, - number, - sid, - altnumber, - pubnumber, - ...unknownAttributes, - }); -} - -function createChapterMarker( - node: SerializedChapterNode, - content: MarkerContent[] | undefined, -): MarkerObject { - const { marker, sid, altnumber, pubnumber, unknownAttributes } = node; - const text = content && typeof content[0] === "string" ? content[0] : undefined; - let { number } = node; - number = parseNumberFromMarkerText(marker, text, number); - return removeUndefinedProperties({ - type: ChapterNode.getType(), - marker, - number, - sid, - altnumber, - pubnumber, - ...unknownAttributes, - }); -} - -function createVerseMarker(node: SerializedImmutableVerseNode | SerializedVerseNode): MarkerObject { - const { marker, sid, altnumber, pubnumber, unknownAttributes } = node; - const { text } = node as SerializedVerseNode; - let { number } = node; - number = parseNumberFromMarkerText(marker, text, number); - return removeUndefinedProperties({ - type: VerseNode.getType(), - marker, - number, - sid, - altnumber, - pubnumber, - ...unknownAttributes, - }); -} - -function createCharMarker( - node: SerializedCharNode, - content: MarkerContent[] | undefined, -): MarkerObject { - const { type, marker: nodeMarker, unknownAttributes } = node; - const marker = nodeMarker === "" ? undefined : nodeMarker; - // Remove NBSP at the start of the child text nodes. - content?.forEach((c, i) => { - if (typeof c === "string" && c.startsWith(NBSP)) { - content[i] = c.slice(1); - } - }); - return removeUndefinedProperties({ - type, - marker, - ...unknownAttributes, - content, - }); -} - -function createParaMarker( - node: SerializedParaNode, - content: MarkerContent[] | undefined, -): MarkerObject { - const { type, marker, unknownAttributes } = node; - return removeUndefinedProperties({ - type, - marker, - ...unknownAttributes, - content, - }); -} - -function createNoteMarker( - node: SerializedNoteNode, - content: MarkerContent[] | undefined, -): MarkerObject { - const { type, marker, caller, category, unknownAttributes } = node; - return removeUndefinedProperties({ - type, - marker, - caller, - category, - ...unknownAttributes, - content, - }); -} - -function createMilestoneMarker(node: SerializedMilestoneNode): MarkerObject { - const { type, marker: nodeMarker, sid, eid, unknownAttributes } = node; - const marker = nodeMarker === "" ? undefined : nodeMarker; - return removeUndefinedProperties({ - type, - marker, - sid, - eid, - ...unknownAttributes, - }); -} - -function createTextMarker(node: SerializedTextNode): string { - return node.text; -} - -function createUnknownMarker( - node: SerializedUnknownNode, - content: MarkerContent[] | undefined, -): MarkerObject { - const { tag, marker, unknownAttributes } = node; - return removeUndefinedProperties({ - type: tag, - marker, - ...unknownAttributes, - content, - }); -} - -function createUnmatchedMarker(node: SerializedImmutableUnmatchedNode): MarkerObject { - const { marker: nodeMarker } = node; - const marker = nodeMarker === "" ? undefined : nodeMarker; - return { - type: UNMATCHED_TAG_NAME, - marker, - }; -} - -/** - * If the last added content is text then combine the new text content to it, otherwise add the new - * text content. - * @param markers - Markers accumulated so far. - * @param textContent - New text content. - */ -function combineTextContentOrAdd(markers: MarkerContent[], textContent: string) { - const lastContent: MarkerContent | undefined = markers[markers.length - 1]; - if (lastContent && typeof lastContent === "string") - markers[markers.length - 1] = lastContent + textContent; - else markers.push(textContent); -} - -/** - * Strip the mark and insert its children enclosed in milestone mark markers. - * @param childMarkers - Children of the mark. - * @param ids - Comment IDs from the current mark. - * @param pids - Comment IDs from the previous mark. - * @param nextNode - Next serialized node. - * @param markers - Markers accumulated so far. - */ -function replaceMarkWithMilestones( - childMarkers: MarkerContent[], - ids: string[], - pids: string[], - nextNode: SerializedLexicalNode | undefined, - markers: MarkerContent[], -) { - // add the milestones in front of the children - const type = MilestoneNode.getType(); - const sids = ids.filter((id) => !pids.includes(id)); - const eids = pids.filter((id) => !ids.includes(id)); - eids.forEach((eid) => { - const milestone = createMilestoneMarker({ - type, - marker: ENDING_MS_COMMENT_MARKER, - eid, - version: MILESTONE_VERSION, - }); - markers.push(milestone); - }); - sids.forEach((sid) => { - const milestone = createMilestoneMarker({ - type, - marker: STARTING_MS_COMMENT_MARKER, - sid, - version: MILESTONE_VERSION, - }); - markers.push(milestone); - }); - if (ids.length === 0) { - const milestone = createMilestoneMarker({ - type, - marker: STARTING_MS_COMMENT_MARKER, - version: MILESTONE_VERSION, - }); - markers.push(milestone); - } - // add the children - markers.push(...childMarkers); - // add any milestones needed after the children - if (ids.length === 0) { - const milestone = createMilestoneMarker({ - type, - marker: ENDING_MS_COMMENT_MARKER, - version: MILESTONE_VERSION, - }); - markers.push(milestone); - } - const isLastEnd = !nextNode || !isSerializedTypedMarkNode(nextNode); - if (isLastEnd) { - ids.forEach((eid) => { - const milestone = createMilestoneMarker({ - type, - marker: ENDING_MS_COMMENT_MARKER, - eid, - version: MILESTONE_VERSION, - }); - markers.push(milestone); - }); - } -} - -export function recurseNodes( - nodes: SerializedLexicalNode[], - noteCaller?: string, -): MarkerContent[] | undefined { - const markers: MarkerContent[] = []; - let childMarkers: MarkerContent[] | undefined; - /** Previous comment IDs from TypedMarkNodes. */ - let pids: string[] = []; - nodes.forEach((node, index) => { - const serializedBookNode = node as SerializedBookNode; - const serializedChapterNode = node as SerializedChapterNode; - const serializedCharNode = node as SerializedCharNode; - const serializedParaNode = node as SerializedParaNode; - const serializedNoteNode = node as SerializedNoteNode; - const serializedTextNode = node as SerializedTextNode; - const serializedMarkNode = node as SerializedTypedMarkNode; - const serializedUnknownNode = node as SerializedUnknownNode; - switch (node.type) { - case BookNode.getType(): - markers.push( - createBookMarker(serializedBookNode, recurseNodes(serializedBookNode.children)), - ); - break; - case ImmutableChapterNode.getType(): - markers.push(createImmutableChapterMarker(node as SerializedImmutableChapterNode)); - break; - case ChapterNode.getType(): - markers.push( - createChapterMarker(serializedChapterNode, recurseNodes(serializedChapterNode.children)), - ); - break; - case ImmutableVerseNode.getType(): - case VerseNode.getType(): - markers.push(createVerseMarker(node as SerializedImmutableVerseNode | SerializedVerseNode)); - break; - case CharNode.getType(): - markers.push( - createCharMarker(serializedCharNode, recurseNodes(serializedCharNode.children)), - ); - break; - case ParaNode.getType(): - markers.push( - createParaMarker(serializedParaNode, recurseNodes(serializedParaNode.children)), - ); - break; - case NoteNode.getType(): - markers.push( - createNoteMarker( - serializedNoteNode, - recurseNodes(serializedNoteNode.children, serializedNoteNode.caller), - ), - ); - break; - case ImmutableTypedTextNode.getType(): - case ImmutableNoteCallerNode.getType(): - case LineBreakNode.getType(): - case MarkerNode.getType(): - // These nodes are for presentation only so they don't go into the USJ. - break; - case TypedMarkNode.getType(): - childMarkers = recurseNodes(serializedMarkNode.children); - if (childMarkers) { - const commentIDs = serializedMarkNode.typedIDs[COMMENT_MARK_TYPE]; - if (commentIDs) { - replaceMarkWithMilestones(childMarkers, commentIDs, pids, nodes[index + 1], markers); - pids = commentIDs; - } else { - // Strip the mark and insert its children. - const firstChild = childMarkers.shift(); - if (firstChild) { - if (typeof firstChild === "string") combineTextContentOrAdd(markers, firstChild); - else markers.push(firstChild); - } - if (childMarkers.length > 0) markers.push(...childMarkers); - } - } - break; - case MilestoneNode.getType(): - markers.push(createMilestoneMarker(node as SerializedMilestoneNode)); - break; - case TextNode.getType(): - if ( - serializedTextNode.text && - serializedTextNode.text !== NBSP && - (!noteCaller || serializedTextNode.text !== getEditableCallerText(noteCaller)) - ) { - combineTextContentOrAdd(markers, createTextMarker(serializedTextNode)); - } - break; - case UnknownNode.getType(): - markers.push( - createUnknownMarker(serializedUnknownNode, recurseNodes(serializedUnknownNode.children)), - ); - break; - case ImmutableUnmatchedNode.getType(): - markers.push(createUnmatchedMarker(node as SerializedImmutableUnmatchedNode)); - break; - default: - _logger?.error(`Unexpected node type '${node.type}'!`); - } - }); - // Ensure empty arrays are removed. - return markers && markers.length > 0 ? markers : undefined; -} - -/** - * Remove implied paras. - * @param nodes - serialized nodes. - * @returns nodes with all implied paras removed. - */ -function removeImpliedParasRecurse(nodes: SerializedLexicalNode[]): SerializedLexicalNode[] { - const impliedParaIndex = nodes.findIndex((node) => isSerializedImpliedParaNode(node)); - if (impliedParaIndex >= 0) { - const nodesBefore = nodes.slice(0, impliedParaIndex); - const nodesFromImpliedPara = (nodes[impliedParaIndex] as SerializedImpliedParaNode).children; - const nodesAfter = removeImpliedParasRecurse(nodes.slice(impliedParaIndex + 1)); - nodes = [...nodesBefore, ...nodesFromImpliedPara, ...nodesAfter]; - } - return nodes; -} - -const editorUsjAdaptor: EditorUsjAdaptor = { - initialize, - deserializeEditorState, -}; -export default editorUsjAdaptor; diff --git a/packages/scribe/src/editor/adaptors/note-usj-editor.adaptor.ts b/packages/scribe/src/editor/adaptors/note-usj-editor.adaptor.ts deleted file mode 100644 index 71dac7fb..00000000 --- a/packages/scribe/src/editor/adaptors/note-usj-editor.adaptor.ts +++ /dev/null @@ -1,646 +0,0 @@ -import { - BookCode, - MarkerContent, - MarkerObject, - USJ_TYPE, - USJ_VERSION, - Usj, -} from "@eten-tech-foundation/scripture-utilities"; -import { MarkNode, SerializedMarkNode } from "@lexical/mark"; -import { - LineBreakNode, - SerializedEditorState, - SerializedLexicalNode, - SerializedLineBreakNode, - SerializedTextNode, - TextModeType, - TextNode, -} from "lexical"; -import { - BOOK_MARKER, - BOOK_MARKER_OBJECT_PROPS, - BOOK_VERSION, - BookMarker, - BookNode, - CHAPTER_MARKER, - CHAPTER_MARKER_OBJECT_PROPS, - CHAPTER_VERSION, - ChapterMarker, - ChapterNode, - CHAR_MARKER_OBJECT_PROPS, - CHAR_VERSION, - CharNode, - DEFAULT_NOTE_MARKER, - EditorAdaptor, - ENDING_MS_COMMENT_MARKER, - getEditableCallerText, - getPreviewTextFromSerializedNodes, - getUnknownAttributes, - getVisibleOpenMarkerText, - IMMUTABLE_CHAPTER_VERSION, - ImmutableChapterNode, - IMPLIED_PARA_VERSION, - ImpliedParaNode, - isMilestoneCommentMarker, - isSerializedTextNode, - LoggerBasic, - MarkerNode, - MarkerSyntax, - MILESTONE_VERSION, - MilestoneNode, - MS_MARKER_OBJECT_PROPS, - NBSP, - NOTE_MARKER_OBJECT_PROPS, - NOTE_VERSION, - NoteNode, - PARA_MARKER_DEFAULT, - PARA_MARKER_OBJECT_PROPS, - PARA_VERSION, - ParaNode, - removeUndefinedProperties, - SerializedBookNode, - SerializedChapterNode, - SerializedCharNode, - SerializedImmutableChapterNode, - SerializedImpliedParaNode, - SerializedMarkerNode, - SerializedMilestoneNode, - SerializedNoteNode, - SerializedParaNode, - SerializedUnknownNode, - SerializedVerseNode, - STARTING_MS_COMMENT_MARKER, - UNKNOWN_MARKER_OBJECT_PROPS, - UNKNOWN_VERSION, - UnknownNode, - VERSE_MARKER, - VERSE_MARKER_OBJECT_PROPS, - VERSE_VERSION, - VerseMarker, - VerseNode, -} from "shared"; -import { - CallerData, - getDefaultViewOptions, - getVerseNodeClass, - IMMUTABLE_NOTE_CALLER_VERSION, - IMMUTABLE_VERSE_VERSION, - ImmutableNoteCallerNode, - ImmutableVerseNode, - isCollapsedNoteMode, - NoteCallerOnClick, - SerializedImmutableNoteCallerNode, - SerializedImmutableVerseNode, - UsjNodeOptions, - ViewOptions, -} from "shared-react"; - -interface UsjNoteEditorAdapter extends EditorAdaptor { - initialize: typeof initialize; - reset: typeof reset; - serializeEditorState: typeof serializeEditorState; -} - -const serializedLineBreakNode: SerializedLineBreakNode = { - type: LineBreakNode.getType(), - version: 1, -}; -const callerData: CallerData = { - /** Count used for note callers. */ - count: 0, -}; - -/** View options - view mode parameters */ -let _viewOptions: ViewOptions | undefined; -/** Options for each node */ -let _nodeOptions: UsjNodeOptions | undefined; -/** Logger instance */ -let _logger: LoggerBasic | undefined; - -export function initialize( - nodeOptions: UsjNodeOptions | undefined, - logger: LoggerBasic | undefined, -) { - setNodeOptions(nodeOptions); - setLogger(logger); -} - -export function reset(callerCountValue = 0) { - //Reset the caller count used for note callers. - callerData.count = callerCountValue; -} - -function getNotesFromUsj(usjContent: MarkerContent[]) { - const notes: MarkerObject[] = []; - usjContent.forEach((content) => { - if (typeof content !== "string") { - content.content?.forEach((note) => { - if (typeof note !== "string" && note.type === "note") { - notes.push(note); - } - }); - } - }); - return notes; -} - -export function serializeEditorState( - usj: Usj | undefined, - viewOptions?: ViewOptions, -): SerializedEditorState { - // use default view options if no `viewOptions` - _viewOptions = viewOptions ?? getDefaultViewOptions(); - /** empty para node for an 'empty' editor */ - const emptyParaNode: SerializedParaNode = createPara({ - type: ParaNode.getType(), - marker: PARA_MARKER_DEFAULT, - }); - let children: SerializedLexicalNode[]; - if (usj) { - if (usj.type !== USJ_TYPE) - _logger?.warn(`This USJ type '${usj.type}' didn't match the expected type '${USJ_TYPE}'.`); - if (usj.version !== USJ_VERSION) - _logger?.warn( - `This USJ version '${usj.version}' didn't match the expected version '${USJ_VERSION}'.`, - ); - - const notes = getNotesFromUsj(usj.content); - if (notes.length > 0) children = insertImpliedNotes(recurseNodes(notes)); - else children = [emptyParaNode]; - } else { - children = [emptyParaNode]; - } - - return { - root: { - children, - direction: null, - format: "", - indent: 0, - type: "root", - version: 1, - }, - }; -} - -/** - * Set the node options. - * @param nodeOptions - Node options. - */ -function setNodeOptions(nodeOptions: UsjNodeOptions | undefined) { - if (nodeOptions) _nodeOptions = nodeOptions; -} - -/** - * Set the logger to use if needed when loading Scripture data to editor state. - * @param logger - Logger to use. - */ -function setLogger(logger: LoggerBasic | undefined) { - if (logger) _logger = logger; -} - -function getTextContent(markers: MarkerContent[] | undefined): string { - if (!markers || markers.length !== 1 || typeof markers[0] !== "string") return ""; - - return markers[0]; -} - -function createBook(markerObject: MarkerObject): SerializedBookNode { - let { marker } = markerObject; - if (marker !== BOOK_MARKER) { - _logger?.warn(`Unexpected book marker '${marker}'!`); - } - marker = marker ?? BOOK_MARKER; - const { code } = markerObject; - if (!code || !BookNode.isValidBookCode(code)) { - _logger?.warn(`Unexpected book code '${code}'!`); - } - const unknownAttributes = getUnknownAttributes(markerObject, BOOK_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: BookNode.getType(), - marker: marker as BookMarker, - code: code ?? ("" as BookCode), - unknownAttributes, - children: [createText(getTextContent(markerObject.content))], - direction: null, - format: "", - indent: 0, - version: BOOK_VERSION, - }); -} - -function createChapter( - markerObject: MarkerObject, -): SerializedChapterNode | SerializedImmutableChapterNode { - let { marker } = markerObject; - if (marker !== CHAPTER_MARKER) { - _logger?.warn(`Unexpected chapter marker '${marker}'!`); - } - marker = marker ?? CHAPTER_MARKER; - const { number, sid, altnumber, pubnumber } = markerObject; - const unknownAttributes = getUnknownAttributes(markerObject, CHAPTER_MARKER_OBJECT_PROPS); - let showMarker: boolean | undefined; - if (_viewOptions?.markerMode === "visible") showMarker = true; - - return _viewOptions?.markerMode === "editable" - ? removeUndefinedProperties({ - type: ChapterNode.getType(), - marker: marker as ChapterMarker, - number: number ?? "", - sid, - altnumber, - pubnumber, - unknownAttributes, - children: [createText(getVisibleOpenMarkerText(marker, number) ?? "")], - direction: null, - format: "", - indent: 0, - version: CHAPTER_VERSION, - }) - : removeUndefinedProperties({ - type: ImmutableChapterNode.getType(), - marker: marker as ChapterMarker, - number: number ?? "", - showMarker, - sid, - altnumber, - pubnumber, - unknownAttributes, - version: IMMUTABLE_CHAPTER_VERSION, - }); -} - -function createVerse( - markerObject: MarkerObject, -): SerializedVerseNode | SerializedImmutableVerseNode { - let { marker } = markerObject; - if (marker !== VERSE_MARKER) { - _logger?.warn(`Unexpected verse marker '${marker}'!`); - } - marker = marker ?? VERSE_MARKER; - const { number, sid, altnumber, pubnumber } = markerObject; - const VerseNodeClass = getVerseNodeClass(_viewOptions) ?? ImmutableVerseNode; - const type = VerseNodeClass.getType(); - const version = _viewOptions?.markerMode === "editable" ? VERSE_VERSION : IMMUTABLE_VERSE_VERSION; - let text: string | undefined; - let showMarker: boolean | undefined; - if (_viewOptions?.markerMode === "editable") text = getVisibleOpenMarkerText(marker, number); - else if (_viewOptions?.markerMode === "visible") showMarker = true; - const unknownAttributes = getUnknownAttributes(markerObject, VERSE_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type, - text, - marker: marker as VerseMarker, - number: number ?? "", - sid, - altnumber, - pubnumber, - showMarker, - unknownAttributes, - version, - }); -} - -function createChar( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[] = [], -): SerializedCharNode { - let { marker } = markerObject; - if (!CharNode.isValidMarker(marker)) { - _logger?.warn(`Unexpected char marker '${marker}'!`); - } - marker = marker ?? ""; - if (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") - childNodes.forEach((node) => { - if (isSerializedTextNode(node)) node.text = NBSP + node.text; - }); - const unknownAttributes = getUnknownAttributes(markerObject, CHAR_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: CharNode.getType(), - marker, - unknownAttributes, - children: [...childNodes], - direction: null, - format: "", - indent: 0, - version: CHAR_VERSION, - }); -} - -function createImpliedPara(children: SerializedLexicalNode[]): SerializedImpliedParaNode { - return { - type: ImpliedParaNode.getType(), - children, - direction: null, - format: "", - indent: 0, - textFormat: 0, - textStyle: "", - version: IMPLIED_PARA_VERSION, - }; -} - -function createPara( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[] = [], -): SerializedParaNode { - let { marker } = markerObject; - if (!ParaNode.isValidMarker(marker)) { - _logger?.warn(`Unexpected para marker '${marker}'!`); - } - marker = marker ?? PARA_MARKER_DEFAULT; - const children: SerializedLexicalNode[] = []; - if (_viewOptions?.markerMode === "editable") - children.push(createMarker(marker), createText(NBSP)); - children.push(...childNodes); - const unknownAttributes = getUnknownAttributes(markerObject, PARA_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: ParaNode.getType(), - marker, - unknownAttributes, - children, - direction: null, - format: "", - indent: 0, - textFormat: 0, - textStyle: "", - version: PARA_VERSION, - }); -} - -function createNoteCaller( - caller: string, - childNodes: SerializedLexicalNode[], -): SerializedImmutableNoteCallerNode { - const previewText = getPreviewTextFromSerializedNodes(childNodes); - let onClick: NoteCallerOnClick = () => undefined; - if (_nodeOptions?.noteCallerOnClick) onClick = _nodeOptions.noteCallerOnClick; - - return removeUndefinedProperties({ - type: ImmutableNoteCallerNode.getType(), - caller, - previewText, - onClick, - version: IMMUTABLE_NOTE_CALLER_VERSION, - }); -} - -function createNote( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[], -): SerializedNoteNode { - let { marker } = markerObject; - if (!NoteNode.isValidMarker(marker)) _logger?.warn(`Unexpected note marker '${marker}'!`); - marker = marker ?? DEFAULT_NOTE_MARKER; - const { category } = markerObject; - const caller = markerObject.caller ?? "*"; - let callerNode: SerializedImmutableNoteCallerNode | SerializedTextNode; - if (_viewOptions?.markerMode === "editable") { - callerNode = createText(getEditableCallerText(caller)); - } else { - callerNode = createNoteCaller(caller, childNodes); - } - const isCollapsed = isCollapsedNoteMode(_viewOptions?.noteMode); - const unknownAttributes = getUnknownAttributes(markerObject, NOTE_MARKER_OBJECT_PROPS); - - let openingMarkerNode: SerializedTextNode | undefined; - let closingMarkerNode: SerializedTextNode | undefined; - if (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") { - openingMarkerNode = createMarker(marker); - closingMarkerNode = createMarker(marker, "closing"); - } - const children: SerializedLexicalNode[] = []; - if (openingMarkerNode) children.push(openingMarkerNode); - children.push(callerNode, ...childNodes); - if (closingMarkerNode) children.push(closingMarkerNode); - - return removeUndefinedProperties({ - type: NoteNode.getType(), - marker, - caller, - isCollapsed, - category, - unknownAttributes, - children, - direction: null, - format: "", - indent: 0, - version: NOTE_VERSION, - }); -} - -function createMilestone(markerObject: MarkerObject): SerializedMilestoneNode { - let { marker } = markerObject; - if (!marker || !MilestoneNode.isValidMarker(marker)) { - _logger?.warn(`Unexpected milestone marker '${marker}'!`); - } - marker = marker ?? ""; - const { sid, eid } = markerObject; - const unknownAttributes = getUnknownAttributes(markerObject, MS_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: MilestoneNode.getType(), - marker, - sid, - eid, - unknownAttributes, - version: MILESTONE_VERSION, - }); -} - -function createMark(children: SerializedLexicalNode[], ids: string[] = []): SerializedMarkNode { - return { - type: MarkNode.getType(), - ids, - children, - direction: null, - format: "", - indent: 0, - version: 1, - }; -} - -function createUnknown( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[], -): SerializedUnknownNode { - const { marker } = markerObject; - const tag = markerObject.type; - const unknownAttributes = getUnknownAttributes(markerObject, UNKNOWN_MARKER_OBJECT_PROPS); - const children: SerializedLexicalNode[] = [...childNodes]; - return removeUndefinedProperties({ - type: UnknownNode.getType(), - tag, - marker, - unknownAttributes, - children, - direction: null, - format: "", - indent: 0, - version: UNKNOWN_VERSION, - }); -} - -function createMarker( - marker: string, - markerSyntax: MarkerSyntax = "opening", -): SerializedMarkerNode { - return { - type: MarkerNode.getType(), - marker, - markerSyntax, - text: "", - detail: 0, - format: 0, - mode: "normal", - style: "", - version: 1, - }; -} - -function createText(text: string, mode: TextModeType = "normal"): SerializedTextNode { - return { - type: TextNode.getType(), - text, - detail: 0, - format: 0, - mode, - style: "", - version: 1, - }; -} - -function addOpeningMarker(marker: string, nodes: SerializedLexicalNode[]) { - if (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") { - nodes.push(createMarker(marker)); - } -} - -function addClosingMarker(marker: string, nodes: SerializedLexicalNode[]) { - if ( - (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") && - !(CharNode.isValidFootnoteMarker(marker) || CharNode.isValidCrossReferenceMarker(marker)) - ) { - nodes.push(createMarker(marker, "closing")); - } -} - -function reIndex(indexes: number[], offset: number): number[] { - if (indexes.length <= 0 || offset === 0) return indexes; - - return indexes.map((index) => index - offset); -} - -function removeValueFromArray(arr: T[], value: T) { - const index = arr.indexOf(value, 0); - if (index > -1) { - arr.splice(index, 1); - } -} - -function updateSids(sids: string[], msMarkNode: SerializedMilestoneNode) { - if (msMarkNode.marker === STARTING_MS_COMMENT_MARKER && msMarkNode.sid !== undefined) - sids.push(msMarkNode.sid); - if (msMarkNode.marker === ENDING_MS_COMMENT_MARKER && msMarkNode.eid !== undefined) - removeValueFromArray(sids, msMarkNode.eid); -} - -function insertMilestoneMarksRecurse( - nodes: SerializedLexicalNode[], - msMarkIndexes: number[], - isPreviousMsStarting = false, - sids: string[] = [], -): SerializedLexicalNode[] { - if (msMarkIndexes.length <= 0 || msMarkIndexes[0] >= nodes.length) return nodes; - - const firstIndex = msMarkIndexes.shift(); - const secondIndex = msMarkIndexes.length > 0 ? msMarkIndexes.shift() : nodes.length - 1; - if (!firstIndex || !secondIndex) return nodes; - - const startNodes = nodes.slice(0, firstIndex); - const nodesBefore = isPreviousMsStarting ? [createMark(startNodes, [...sids])] : startNodes; - const firstMSMarkNode = nodes[firstIndex] as SerializedMilestoneNode; - updateSids(sids, firstMSMarkNode); - const markedNodes = insertMilestoneMarksRecurse( - nodes.slice(firstIndex + 1, secondIndex), - reIndex(msMarkIndexes, firstIndex + 1), - firstMSMarkNode.marker === STARTING_MS_COMMENT_MARKER, - sids, - ); - const markNode = createMark(markedNodes, [...sids]); - const secondMSMarkNode = nodes[secondIndex] as SerializedMilestoneNode; - updateSids(sids, secondMSMarkNode); - const nodesAfter = insertMilestoneMarksRecurse( - nodes.slice(secondIndex + 1), - reIndex(msMarkIndexes, secondIndex + 1), - secondMSMarkNode.marker === STARTING_MS_COMMENT_MARKER, - sids, - ); - return [...nodesBefore, firstMSMarkNode, markNode, secondMSMarkNode, ...nodesAfter]; -} - -function recurseNodes(markers: MarkerContent[] | undefined): SerializedLexicalNode[] { - const msMarkIndexes: number[] = []; - const nodes: SerializedLexicalNode[] = []; - markers?.forEach((markerContent) => { - if (typeof markerContent === "string") { - nodes.push(createText(markerContent)); - } else if (!markerContent.type) { - _logger?.error(`Marker type is missing!`); - } else { - switch (markerContent.type) { - case BookNode.getType(): - nodes.push(createBook(markerContent)); - break; - case ChapterNode.getType(): - nodes.push(createChapter(markerContent)); - break; - case VerseNode.getType(): - if (!_viewOptions?.hasSpacing) nodes.push(serializedLineBreakNode); - nodes.push(createVerse(markerContent)); - break; - case CharNode.getType(): - addOpeningMarker(markerContent.marker ?? "", nodes); - nodes.push(createChar(markerContent, recurseNodes(markerContent.content))); - addClosingMarker(markerContent.marker ?? "", nodes); - break; - case ParaNode.getType(): - nodes.push(createPara(markerContent, recurseNodes(markerContent.content))); - break; - case NoteNode.getType(): - nodes.push(createNote(markerContent, recurseNodes(markerContent.content))); - break; - case MilestoneNode.getType(): - if (isMilestoneCommentMarker(markerContent.marker ?? "")) - msMarkIndexes.push(nodes.length); - nodes.push(createMilestone(markerContent)); - break; - default: - _logger?.warn(`Unknown type-marker '${markerContent.type}-${markerContent.marker}'!`); - nodes.push(createUnknown(markerContent, recurseNodes(markerContent.content))); - } - } - }); - return insertMilestoneMarksRecurse(nodes, msMarkIndexes); -} - -/** - * Insert implied paras around any other set of nodes that contain a text element at the root. - * @param nodes - Serialized nodes. - * @returns nodes with any needed implied paras inserted. - */ -function insertImpliedNotes(nodes: SerializedLexicalNode[]): SerializedLexicalNode[] { - const impliedNoteNodes = nodes.map((node) => createImpliedPara([node])); - return impliedNoteNodes; -} - -const usjNoteEditorAdapter: UsjNoteEditorAdapter = { - initialize, - reset, - serializeEditorState, -}; -export default usjNoteEditorAdapter; diff --git a/packages/scribe/src/editor/adaptors/usj-editor.adaptor.ts b/packages/scribe/src/editor/adaptors/usj-editor.adaptor.ts deleted file mode 100644 index 81f10671..00000000 --- a/packages/scribe/src/editor/adaptors/usj-editor.adaptor.ts +++ /dev/null @@ -1,705 +0,0 @@ -import { - BookCode, - MarkerContent, - MarkerObject, - USJ_TYPE, - USJ_VERSION, - Usj, -} from "@eten-tech-foundation/scripture-utilities"; -import { - LineBreakNode, - SerializedEditorState, - SerializedLexicalNode, - SerializedLineBreakNode, - SerializedTextNode, - TextModeType, - TextNode, -} from "lexical"; -import { - BOOK_MARKER, - BOOK_MARKER_OBJECT_PROPS, - BOOK_VERSION, - BookMarker, - BookNode, - CHAPTER_MARKER, - CHAPTER_MARKER_OBJECT_PROPS, - CHAPTER_VERSION, - ChapterMarker, - ChapterNode, - CHAR_MARKER_OBJECT_PROPS, - CHAR_VERSION, - CharNode, - COMMENT_MARK_TYPE, - DEFAULT_NOTE_MARKER, - EditorAdaptor, - ENDING_MS_COMMENT_MARKER, - getEditableCallerText, - getPreviewTextFromSerializedNodes, - getUnknownAttributes, - getVisibleOpenMarkerText, - IMMUTABLE_CHAPTER_VERSION, - IMMUTABLE_UNMATCHED_VERSION, - ImmutableChapterNode, - ImmutableUnmatchedNode, - IMPLIED_PARA_VERSION, - ImpliedParaNode, - isMilestoneCommentMarker, - isSerializedBookNode, - isSerializedParaNode, - isSerializedTextNode, - isSomeSerializedChapterNode, - LoggerBasic, - MarkerNode, - MarkerSyntax, - MILESTONE_VERSION, - MilestoneNode, - MS_MARKER_OBJECT_PROPS, - NBSP, - NOTE_MARKER_OBJECT_PROPS, - NOTE_VERSION, - NoteNode, - PARA_MARKER_DEFAULT, - PARA_MARKER_OBJECT_PROPS, - PARA_VERSION, - ParaNode, - removeUndefinedProperties, - SerializedBookNode, - SerializedChapterNode, - SerializedCharNode, - SerializedImmutableChapterNode, - SerializedImmutableUnmatchedNode, - SerializedImpliedParaNode, - SerializedMarkerNode, - SerializedMilestoneNode, - SerializedNoteNode, - SerializedParaNode, - SerializedTypedMarkNode, - SerializedUnknownNode, - SerializedVerseNode, - STARTING_MS_COMMENT_MARKER, - TypedMarkNode, - UNKNOWN_MARKER_OBJECT_PROPS, - UNKNOWN_VERSION, - UnknownNode, - unmatchedGlyphText, - VERSE_MARKER, - VERSE_MARKER_OBJECT_PROPS, - VERSE_VERSION, - VerseMarker, - VerseNode, -} from "shared"; -import { - AddMissingComments, - CallerData, - IMMUTABLE_NOTE_CALLER_VERSION, - IMMUTABLE_VERSE_VERSION, - ImmutableNoteCallerNode, - ImmutableVerseNode, - NoteCallerOnClick, - SerializedImmutableNoteCallerNode, - SerializedImmutableVerseNode, - UsjNodeOptions, - ViewOptions, - getDefaultViewOptions, - getVerseNodeClass, - isCollapsedNoteMode, - isSomeSerializedVerseNode, -} from "shared-react"; - -interface UsjEditorAdaptor extends EditorAdaptor { - initialize: typeof initialize; - reset: typeof reset; - serializeEditorState: typeof serializeEditorState; -} - -const serializedLineBreakNode: SerializedLineBreakNode = { - type: LineBreakNode.getType(), - version: 1, -}; -const callerData: CallerData = { - /** Count used for note callers. */ - count: 0, -}; - -/** Comment IDs in the USJ. */ -let commentIds: string[] = []; - -/** View options - view mode parameters. */ -let _viewOptions: ViewOptions | undefined; -/** Options for each node. */ -let _nodeOptions: UsjNodeOptions | undefined; -/** Method to add missing comments. */ -let addMissingComments: AddMissingComments | undefined; -/** Logger instance. */ -let _logger: LoggerBasic | undefined; - -export function initialize( - nodeOptions: UsjNodeOptions | undefined, - logger: LoggerBasic | undefined, -) { - commentIds = []; - setNodeOptions(nodeOptions); - setLogger(logger); -} - -export function reset(callerCountValue = 0) { - //Reset the caller count used for note callers. - callerData.count = callerCountValue; -} - -export function serializeEditorState( - usj: Usj | undefined, - viewOptions?: ViewOptions, -): SerializedEditorState { - // use default view options if no `viewOptions` - _viewOptions = viewOptions ?? getDefaultViewOptions(); - /** empty para node for an 'empty' editor */ - const emptyParaNode: SerializedParaNode = createPara({ - type: ParaNode.getType(), - marker: PARA_MARKER_DEFAULT, - }); - let children: SerializedLexicalNode[]; - if (usj) { - if (usj.type !== USJ_TYPE) - _logger?.warn(`This USJ type '${usj.type}' didn't match the expected type '${USJ_TYPE}'.`); - if (usj.version !== USJ_VERSION) - _logger?.warn( - `This USJ version '${usj.version}' didn't match the expected version '${USJ_VERSION}'.`, - ); - - if (usj.content.length > 0) children = insertImpliedParasRecurse(recurseNodes(usj.content)); - else children = [emptyParaNode]; - } else { - children = [emptyParaNode]; - } - - addMissingComments?.(commentIds); - return { - root: { - children, - direction: null, - format: "", - indent: 0, - type: "root", - version: 1, - }, - }; -} - -/** - * Set the node options. - * @param nodeOptions - Node options. - */ -function setNodeOptions(nodeOptions: UsjNodeOptions | undefined) { - if (nodeOptions) _nodeOptions = nodeOptions; - - // Set the `addMissingComments` method. - if (nodeOptions?.addMissingComments) { - addMissingComments = nodeOptions.addMissingComments; - } -} - -/** - * Set the logger to use if needed when loading Scripture data to editor state. - * @param logger - Logger to use. - */ -function setLogger(logger: LoggerBasic | undefined) { - if (logger) _logger = logger; -} - -function getTextContent(markers: MarkerContent[] | undefined): string { - if (!markers || markers.length !== 1 || typeof markers[0] !== "string") return ""; - - return markers[0]; -} - -function createBook(markerObject: MarkerObject): SerializedBookNode { - let { marker } = markerObject; - if (marker !== BOOK_MARKER) { - _logger?.warn(`Unexpected book marker '${marker}'!`); - } - marker = marker ?? BOOK_MARKER; - const { code } = markerObject; - if (!code || !BookNode.isValidBookCode(code)) { - _logger?.warn(`Unexpected book code '${code}'!`); - } - const unknownAttributes = getUnknownAttributes(markerObject, BOOK_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: BookNode.getType(), - marker: marker as BookMarker, - code: code ?? ("" as BookCode), - unknownAttributes, - children: [createText(getTextContent(markerObject.content))], - direction: null, - format: "", - indent: 0, - version: BOOK_VERSION, - }); -} - -function createChapter( - markerObject: MarkerObject, -): SerializedChapterNode | SerializedImmutableChapterNode { - let { marker } = markerObject; - if (marker !== CHAPTER_MARKER) { - _logger?.warn(`Unexpected chapter marker '${marker}'!`); - } - marker = marker ?? CHAPTER_MARKER; - const { number, sid, altnumber, pubnumber } = markerObject; - const unknownAttributes = getUnknownAttributes(markerObject, CHAPTER_MARKER_OBJECT_PROPS); - let showMarker: boolean | undefined; - if (_viewOptions?.markerMode === "visible") showMarker = true; - - return _viewOptions?.markerMode === "editable" - ? removeUndefinedProperties({ - type: ChapterNode.getType(), - marker: marker as ChapterMarker, - number: number ?? "", - sid, - altnumber, - pubnumber, - unknownAttributes, - children: [createText(getVisibleOpenMarkerText(marker, number) ?? "")], - direction: null, - format: "", - indent: 0, - version: CHAPTER_VERSION, - }) - : removeUndefinedProperties({ - type: ImmutableChapterNode.getType(), - marker: marker as ChapterMarker, - number: number ?? "", - showMarker, - sid, - altnumber, - pubnumber, - unknownAttributes, - version: IMMUTABLE_CHAPTER_VERSION, - }); -} - -function createVerse( - markerObject: MarkerObject, -): SerializedVerseNode | SerializedImmutableVerseNode { - let { marker } = markerObject; - if (marker !== VERSE_MARKER) { - _logger?.warn(`Unexpected verse marker '${marker}'!`); - } - marker = marker ?? VERSE_MARKER; - const { number, sid, altnumber, pubnumber } = markerObject; - const VerseNodeClass = getVerseNodeClass(_viewOptions) ?? ImmutableVerseNode; - const type = VerseNodeClass.getType(); - const version = _viewOptions?.markerMode === "editable" ? VERSE_VERSION : IMMUTABLE_VERSE_VERSION; - let text: string | undefined; - let showMarker: boolean | undefined; - if (_viewOptions?.markerMode === "editable") text = getVisibleOpenMarkerText(marker, number); - else if (_viewOptions?.markerMode === "visible") showMarker = true; - const unknownAttributes = getUnknownAttributes(markerObject, VERSE_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type, - text, - marker: marker as VerseMarker, - number: number ?? "", - sid, - altnumber, - pubnumber, - showMarker, - unknownAttributes, - version, - }); -} - -function createChar( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[] = [], -): SerializedCharNode { - let { marker } = markerObject; - if (!CharNode.isValidMarker(marker)) { - _logger?.warn(`Unexpected char marker '${marker}'!`); - } - marker = marker ?? ""; - if (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") - childNodes.forEach((node) => { - if (isSerializedTextNode(node)) node.text = NBSP + node.text; - }); - const unknownAttributes = getUnknownAttributes(markerObject, CHAR_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: CharNode.getType(), - marker, - unknownAttributes, - children: [...childNodes], - direction: null, - format: "", - indent: 0, - version: CHAR_VERSION, - }); -} - -function createImpliedPara(children: SerializedLexicalNode[]): SerializedImpliedParaNode { - return { - type: ImpliedParaNode.getType(), - children, - direction: null, - format: "", - indent: 0, - textFormat: 0, - textStyle: "", - version: IMPLIED_PARA_VERSION, - }; -} - -function createPara( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[] = [], -): SerializedParaNode { - let { marker } = markerObject; - if (!ParaNode.isValidMarker(marker)) { - _logger?.warn(`Unexpected para marker '${marker}'!`); - } - marker = marker ?? PARA_MARKER_DEFAULT; - const children: SerializedLexicalNode[] = []; - if (_viewOptions?.markerMode === "editable") - children.push(createMarker(marker), createText(NBSP)); - children.push(...childNodes); - const unknownAttributes = getUnknownAttributes(markerObject, PARA_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: ParaNode.getType(), - marker, - unknownAttributes, - children, - direction: null, - format: "", - indent: 0, - textFormat: 0, - textStyle: "", - version: PARA_VERSION, - }); -} - -function createNoteCaller( - caller: string, - childNodes: SerializedLexicalNode[], -): SerializedImmutableNoteCallerNode { - const previewText = getPreviewTextFromSerializedNodes(childNodes); - let onClick: NoteCallerOnClick = () => undefined; - if (_nodeOptions?.noteCallerOnClick) onClick = _nodeOptions.noteCallerOnClick; - - return removeUndefinedProperties({ - type: ImmutableNoteCallerNode.getType(), - caller, - previewText, - onClick, - version: IMMUTABLE_NOTE_CALLER_VERSION, - }); -} - -function createNote( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[], -): SerializedNoteNode { - let { marker } = markerObject; - if (!NoteNode.isValidMarker(marker)) _logger?.warn(`Unexpected note marker '${marker}'!`); - marker = marker ?? DEFAULT_NOTE_MARKER; - const { category } = markerObject; - const caller = markerObject.caller ?? "*"; - let callerNode: SerializedImmutableNoteCallerNode | SerializedTextNode; - if (_viewOptions?.markerMode === "editable") { - callerNode = createText(getEditableCallerText(caller)); - } else { - callerNode = createNoteCaller(caller, childNodes); - } - const isCollapsed = isCollapsedNoteMode(_viewOptions?.noteMode); - const unknownAttributes = getUnknownAttributes(markerObject, NOTE_MARKER_OBJECT_PROPS); - - let openingMarkerNode: SerializedTextNode | undefined; - let closingMarkerNode: SerializedTextNode | undefined; - if (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") { - openingMarkerNode = createMarker(marker); - closingMarkerNode = createMarker(marker, "closing"); - } - const children: SerializedLexicalNode[] = []; - if (openingMarkerNode) children.push(openingMarkerNode); - children.push(callerNode, ...childNodes); - if (closingMarkerNode) children.push(closingMarkerNode); - - return removeUndefinedProperties({ - type: NoteNode.getType(), - marker, - caller, - isCollapsed, - category, - unknownAttributes, - children, - direction: null, - format: "", - indent: 0, - version: NOTE_VERSION, - }); -} - -function createMilestone(markerObject: MarkerObject): SerializedMilestoneNode { - let { marker } = markerObject; - if (!marker || !MilestoneNode.isValidMarker(marker)) { - _logger?.warn(`Unexpected milestone marker '${marker}'!`); - } - marker = marker ?? ""; - const { sid, eid } = markerObject; - const unknownAttributes = getUnknownAttributes(markerObject, MS_MARKER_OBJECT_PROPS); - - return removeUndefinedProperties({ - type: MilestoneNode.getType(), - marker, - sid, - eid, - unknownAttributes, - version: MILESTONE_VERSION, - }); -} - -function createCommentMark( - children: SerializedLexicalNode[], - ids: string[] = [], -): SerializedTypedMarkNode { - return { - type: TypedMarkNode.getType(), - typedIDs: { [COMMENT_MARK_TYPE]: ids }, - children, - direction: null, - format: "", - indent: 0, - version: 1, - }; -} - -function createUnknown( - markerObject: MarkerObject, - childNodes: SerializedLexicalNode[], -): SerializedUnknownNode { - const { marker } = markerObject; - const tag = markerObject.type; - const unknownAttributes = getUnknownAttributes(markerObject, UNKNOWN_MARKER_OBJECT_PROPS); - const children: SerializedLexicalNode[] = [...childNodes]; - children.forEach((node) => { - if (isSerializedTextNode(node)) node.mode = "token"; - }); - return removeUndefinedProperties({ - type: UnknownNode.getType(), - tag, - marker, - unknownAttributes, - children, - direction: null, - format: "", - indent: 0, - version: UNKNOWN_VERSION, - }); -} - -function createUnmatched(marker: string): SerializedImmutableUnmatchedNode { - return { - type: ImmutableUnmatchedNode.getType(), - marker, - text: unmatchedGlyphText(marker), - detail: 0, - format: 0, - // Atomic: scribe has no marker-edit engine to settle an in-place edit of the flagged bytes. - mode: "token", - style: "", - version: IMMUTABLE_UNMATCHED_VERSION, - }; -} - -function createMarker( - marker: string, - markerSyntax: MarkerSyntax = "opening", -): SerializedMarkerNode { - return { - type: MarkerNode.getType(), - marker, - markerSyntax, - text: "", - detail: 0, - format: 0, - mode: "normal", - style: "", - version: 1, - }; -} - -function createText(text: string, mode: TextModeType = "normal"): SerializedTextNode { - return { - type: TextNode.getType(), - text, - detail: 0, - format: 0, - mode, - style: "", - version: 1, - }; -} - -function addOpeningMarker(marker: string, nodes: SerializedLexicalNode[]) { - if (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") { - nodes.push(createMarker(marker)); - } -} - -function addClosingMarker(marker: string, nodes: SerializedLexicalNode[]) { - if ( - (_viewOptions?.markerMode === "visible" || _viewOptions?.markerMode === "editable") && - !(CharNode.isValidFootnoteMarker(marker) || CharNode.isValidCrossReferenceMarker(marker)) - ) { - nodes.push(createMarker(marker, "closing")); - } -} - -function reIndex(indexes: number[], offset: number): number[] { - if (indexes.length <= 0 || offset === 0) return indexes; - - return indexes.map((index) => index - offset); -} - -function removeValueFromArray(arr: T[], value: T) { - const index = arr.indexOf(value, 0); - if (index > -1) { - arr.splice(index, 1); - } -} - -function updateIds(ids: string[], msCommentNode: SerializedMilestoneNode) { - if (msCommentNode.marker === STARTING_MS_COMMENT_MARKER && msCommentNode.sid !== undefined) - ids.push(msCommentNode.sid); - if (msCommentNode.marker === ENDING_MS_COMMENT_MARKER && msCommentNode.eid !== undefined) - removeValueFromArray(ids, msCommentNode.eid); -} - -function replaceMilestonesWithMarkRecurse( - nodes: SerializedLexicalNode[], - msCommentIndexes: number[], - isPreviousMsStarting = false, - ids: string[] = [], -): SerializedLexicalNode[] { - if (msCommentIndexes.length <= 0 || msCommentIndexes[0] >= nodes.length) return nodes; - - // get the pair of indexes for the mark - const firstIndex: number | undefined = msCommentIndexes.shift(); - const secondIndex: number | undefined = - msCommentIndexes.length > 0 ? msCommentIndexes.shift() : nodes.length - 1; - if ( - firstIndex === undefined || - secondIndex === undefined || - secondIndex >= nodes.length || - nodes.length <= 0 - ) - return nodes; - - // get the nodes before the mark - const startNodes = nodes.slice(0, firstIndex); - const nodesBefore = isPreviousMsStarting ? [createCommentMark(startNodes, [...ids])] : startNodes; - // get the nodes inside the mark - const firstMSCommentNode = nodes[firstIndex] as SerializedMilestoneNode; - updateIds(ids, firstMSCommentNode); - const markedNodes = replaceMilestonesWithMarkRecurse( - nodes.slice(firstIndex + 1, secondIndex), - reIndex(msCommentIndexes, firstIndex + 1), - firstMSCommentNode.marker === STARTING_MS_COMMENT_MARKER, - ids, - ); - const markNode = createCommentMark(markedNodes, [...ids]); - // get the nodes after the mark - const secondMSCommentNode = nodes[secondIndex] as SerializedMilestoneNode; - updateIds(ids, secondMSCommentNode); - const nodesAfter = replaceMilestonesWithMarkRecurse( - nodes.slice(secondIndex + 1), - reIndex(msCommentIndexes, secondIndex + 1), - secondMSCommentNode.marker === STARTING_MS_COMMENT_MARKER, - ids, - ); - return [...nodesBefore, markNode, ...nodesAfter]; -} - -export function recurseNodes(markers: MarkerContent[] | undefined): SerializedLexicalNode[] { - const msCommentIndexes: number[] = []; - const nodes: SerializedLexicalNode[] = []; - markers?.forEach((markerContent) => { - if (typeof markerContent === "string") { - nodes.push(createText(markerContent)); - } else if (!markerContent.type) { - _logger?.error(`Marker type is missing!`); - } else { - switch (markerContent.type) { - case BookNode.getType(): - nodes.push(createBook(markerContent)); - break; - case ChapterNode.getType(): - nodes.push(createChapter(markerContent)); - break; - case VerseNode.getType(): - if (!_viewOptions?.hasSpacing) nodes.push(serializedLineBreakNode); - nodes.push(createVerse(markerContent)); - break; - case CharNode.getType(): - addOpeningMarker(markerContent.marker ?? "", nodes); - nodes.push(createChar(markerContent, recurseNodes(markerContent.content))); - addClosingMarker(markerContent.marker ?? "", nodes); - break; - case ParaNode.getType(): - nodes.push(createPara(markerContent, recurseNodes(markerContent.content))); - break; - case NoteNode.getType(): - nodes.push(createNote(markerContent, recurseNodes(markerContent.content))); - break; - case MilestoneNode.getType(): - if (isMilestoneCommentMarker(markerContent.marker ?? "")) { - msCommentIndexes.push(nodes.length); - if (markerContent.sid !== undefined) commentIds?.push(markerContent.sid); - } - nodes.push(createMilestone(markerContent)); - break; - case ImmutableUnmatchedNode.getType(): - nodes.push(createUnmatched(markerContent.marker ?? "")); - break; - default: - _logger?.warn(`Unknown type-marker '${markerContent.type}-${markerContent.marker}'!`); - nodes.push(createUnknown(markerContent, recurseNodes(markerContent.content))); - } - } - }); - return replaceMilestonesWithMarkRecurse(nodes, msCommentIndexes); -} - -/** - * Insert implied paras around any other set of nodes that contain a text or verse element at the root. - * @param nodes - Serialized nodes. - * @returns nodes with any needed implied paras inserted. - */ -function insertImpliedParasRecurse(nodes: SerializedLexicalNode[]): SerializedLexicalNode[] { - const validRootNodeIndex = nodes.findIndex( - (node) => - isSerializedBookNode(node) || isSomeSerializedChapterNode(node) || isSerializedParaNode(node), - ); - const isValidRootNodeFound = validRootNodeIndex >= 0; - if (isValidRootNodeFound) { - const nodesBefore = insertImpliedParasRecurse(nodes.slice(0, validRootNodeIndex)); - const validRootNode = nodes[validRootNodeIndex]; - const nodesAfter = insertImpliedParasRecurse(nodes.slice(validRootNodeIndex + 1)); - return [...nodesBefore, validRootNode, ...nodesAfter]; - } else if ( - nodes.some((node) => ("text" in node && "mode" in node) || isSomeSerializedVerseNode(node)) - ) { - // If there are any text or verse nodes as a child of this root, enclose in an implied para node. - return [createImpliedPara(nodes)]; - } - return nodes; -} - -const usjEditorAdaptor: UsjEditorAdaptor = { - initialize, - reset, - serializeEditorState, -}; -export default usjEditorAdaptor; diff --git a/packages/scribe/src/editor/adaptors/usj-marker-action.utils.ts b/packages/scribe/src/editor/adaptors/usj-marker-action.utils.ts deleted file mode 100644 index 210dceb0..00000000 --- a/packages/scribe/src/editor/adaptors/usj-marker-action.utils.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { MarkerContent } from "@eten-tech-foundation/scripture-utilities"; -import { - $createTextNode, - $getSelection, - $isElementNode, - $isRangeSelection, - $isTextNode, - EditorUpdateOptions, - LexicalEditor, - LexicalNode, - RangeSelection, - TextNode, -} from "lexical"; -import { - $createNodeFromSerializedNode, - $isNoteNode, - $isTypedMarkNode, - CharNode, - createLexicalUsjNode, - GENERATOR_NOTE_CALLER, - getNextVerse, - Marker, - MarkerAction, - ParaNode, - ScriptureReference, -} from "shared"; -import { - $addTrailingSpace, - $isSomeVerseNode, - $removeLeadingSpace, - ViewOptions, -} from "shared-react"; -import usjEditorAdaptor from "./usj-editor.adaptor"; - -const markerActions: { - [marker: string]: { - label?: string; - action?: (currentEditor: { - reference: ScriptureReference; - editor: LexicalEditor; - autoNumbering?: boolean; - newVerseRChapterNum?: number; - noteText?: string; - }) => MarkerContent[]; - }; -} = { - c: { - action: (currentEditor) => { - const { book, chapterNum } = currentEditor.reference; - const nextChapter = currentEditor.autoNumbering - ? chapterNum + 1 - : currentEditor.newVerseRChapterNum; - const content: MarkerContent = { - type: "chapter", - marker: "c", - number: `${nextChapter}`, - sid: `${book} ${nextChapter}`, - }; - return [content]; - }, - }, - v: { - action: (currentEditor) => { - const { book, chapterNum, verseNum, verse } = currentEditor.reference; - const nextVerse = currentEditor.autoNumbering - ? getNextVerse(verseNum, verse) - : currentEditor.newVerseRChapterNum; - const content: MarkerContent = { - type: "verse", - marker: "v", - number: `${nextVerse}`, - sid: `${book} ${chapterNum}:${nextVerse}`, - }; - return [content]; - }, - }, - f: { - action: (currentEditor) => { - const { chapterNum, verseNum } = currentEditor.reference; - const content: MarkerContent = { - type: "note", - marker: "f", - caller: GENERATOR_NOTE_CALLER, - content: [ - { type: "char", marker: "fr", content: [`${chapterNum}:${verseNum}`] }, - { - type: "char", - marker: "ft", - content: [currentEditor.noteText ?? "-"], - }, - ], - }; - return [content]; - }, - }, - x: { - action: (currentEditor) => { - const { chapterNum, verseNum } = currentEditor.reference; - const content: MarkerContent = { - type: "note", - marker: "x", - caller: GENERATOR_NOTE_CALLER, - content: [ - { type: "char", marker: "xo", content: [`${chapterNum}:${verseNum}`] }, - { - type: "char", - marker: "xt", - content: [currentEditor.noteText ?? "-"], - }, - ], - }; - return [content]; - }, - }, -}; - -/** A function that returns a marker action for a given USJ marker */ -export function getUsjMarkerAction( - marker: string, - _markerData?: Marker, - viewOptions?: ViewOptions, - /** Included for tests, e.g. `{ discrete: true }` */ - editorUpdateOptions?: EditorUpdateOptions, -): MarkerAction { - const markerAction = getMarkerAction(marker); - const action = (currentEditor: { - reference: ScriptureReference; - editor: LexicalEditor; - autoNumbering?: boolean; - newVerseRChapterNum?: number; - noteText?: string; - }) => { - currentEditor.editor.update(() => { - const content = markerAction?.action?.(currentEditor); - if (!content) return; - - const serializedLexicalNode = createLexicalUsjNode(content, usjEditorAdaptor, viewOptions); - const nodeToInsert = $createNodeFromSerializedNode(serializedLexicalNode); - - const selection = $getSelection(); - if ($isRangeSelection(selection)) { - // If the selection has text content, wrap the text selection in an inline node - if (selection.getTextContent().length > 0) { - $wrapTextSelectionInInlineNode(selection, () => - $createNodeFromSerializedNode(serializedLexicalNode), - ); - } else if ($isElementNode(nodeToInsert) && !nodeToInsert.isInline()) { - // If the selection is empty, insert a new paragraph and replace it with the USJ node - const paragraph = selection.insertParagraph(); - if (paragraph) { - // Transfer the content of the paragraph to the USJ node - const paragraphContent = paragraph.getChildren(); - nodeToInsert.append(...paragraphContent); - paragraph.replace(nodeToInsert); - nodeToInsert.selectStart(); - } - } else { - selection.insertNodes([nodeToInsert]); - $moveVerseFollowingSpaceToPreviousNode(nodeToInsert); - const nextNode = nodeToInsert.getNextSibling(); - if (nextNode) nextNode.selectStart(); - else nodeToInsert.selectStart(); - } - } else { - // Insert the node directly - selection?.insertNodes([nodeToInsert]); - } - }, editorUpdateOptions); - }; - return { action, label: markerAction?.label }; -} - -function getMarkerAction(marker: string): { - label?: string; - action?: (currentEditor: { - reference: ScriptureReference; - editor: LexicalEditor; - }) => MarkerContent[]; -} { - let markerAction = markerActions[marker]; - if (!markerAction) { - if (ParaNode.isValidMarker(marker)) { - markerAction = { - action: () => { - const content: MarkerContent = { - type: ParaNode.getType(), - marker, - content: [], - }; - return [content]; - }, - }; - } else if (CharNode.isValidMarker(marker)) { - markerAction = { - action: () => { - const content: MarkerContent = { - type: CharNode.getType(), - marker, - content: ["-"], - }; - return [content]; - }, - }; - } - } - return markerAction; -} - -function $wrapTextSelectionInInlineNode( - selection: RangeSelection, - createNode: () => LexicalNode, -): void { - const nodes = selection.getNodes(); - const [startOffset, endOffset] = getSelectionOffsets(selection); - - let currentWrapper: LexicalNode | undefined; - - nodes.forEach((node, index) => { - // Skip if node is already wrapped - if ($isElementNode(currentWrapper) && currentWrapper.isParentOf(node)) { - return; - } - - // Get the target node to wrap - const targetNode = $getTargetNode( - node, - index === 0, - index === nodes.length - 1, - startOffset, - endOffset, - ); - - if (!targetNode) { - currentWrapper = undefined; - return; - } - - // Create or reuse wrapper node - if (!currentWrapper) { - currentWrapper = createNode(); - targetNode.insertBefore(currentWrapper); - } - - // Wrap the target node - $wrapNode(targetNode, currentWrapper); - }); - - // Update selection - if ($isTextNode(currentWrapper) || $isElementNode(currentWrapper)) currentWrapper.selectEnd(); -} - -// #region Helper functions for $wrapTextSelectionInInlineNode - -/** - * Get the start and end offsets of a selection. - * @param selection - The selection to get the offsets from. - * @returns the start and end offsets of the selection. - */ -function getSelectionOffsets(selection: RangeSelection): [number, number] { - const anchorOffset = selection.anchor.offset; - const focusOffset = selection.focus.offset; - return selection.isBackward() ? [focusOffset, anchorOffset] : [anchorOffset, focusOffset]; -} - -function $getTargetNode( - node: LexicalNode, - isFirst: boolean, - isLast: boolean, - startOffset: number, - endOffset: number, -): LexicalNode | undefined { - // Skip mark nodes and note nodes - if ($isTypedMarkNode(node) || $isNoteNode(node) || $isNoteNode(node.getParent())) { - return undefined; - } - - // Handle text nodes - if ($isTextNode(node)) { - return handleTextNode(node, isFirst, isLast, startOffset, endOffset); - } - - // Handle inline elements - if ($isElementNode(node) && node.isInline()) { - return node; - } - return undefined; -} - -function handleTextNode( - node: TextNode, - isFirst: boolean, - isLast: boolean, - startOffset: number, - endOffset: number, -): TextNode | undefined { - const textLength = node.getTextContentSize(); - const start = isFirst ? startOffset : 0; - const end = isLast ? endOffset : textLength; - - if (start === 0 && end === 0) return; - - const splitNodes = node.splitText(start, end); - - if (splitNodes.length === 1) return splitNodes[0]; - - return splitNodes.length === 3 || end === textLength ? splitNodes[1] : splitNodes[0]; -} - -function $wrapNode(node: LexicalNode, wrapper: LexicalNode): void { - if ($isTextNode(wrapper)) { - const text = $moveLeadingSpaceToPreviousNode(node, wrapper); - wrapper.setTextContent(text); - node.remove(); - } else if ($isElementNode(wrapper)) { - const wrapperChildrenCount = wrapper.getChildrenSize(); - wrapper.append(node); - for (let i = 0; i < wrapperChildrenCount; i++) wrapper.getFirstChild()?.remove(); - $moveLeadingSpaceToPreviousNode(node, wrapper); - } -} - -function $moveLeadingSpaceToPreviousNode(node: LexicalNode, wrapper: LexicalNode): string { - let text = node.getTextContent(); - if ($isTextNode(node) && wrapper.isInline() && text.startsWith(" ")) { - text = text.trimStart(); - node.setTextContent(text); - const previousNode = wrapper.getPreviousSibling(); - $addTrailingSpace(previousNode); - if (!$isTextNode(previousNode)) wrapper.insertBefore($createTextNode(" ")); - } - return text; -} - -// #endregion - -/** - * Moves the leading space of a node following a verse node to the previous node. - * - * This function checks if the previous node ends in a space and adds one if needed. It then checks - * if the following node starts with a space and removes it. - * - * @param node - The node to check for leading space. - */ -function $moveVerseFollowingSpaceToPreviousNode(node: LexicalNode) { - if (!$isSomeVerseNode(node)) return; - - $addTrailingSpace(node.getPreviousSibling()); - $removeLeadingSpace(node.getNextSibling()); -} diff --git a/packages/scribe/src/editor/data/tit.usfm.ts b/packages/scribe/src/editor/data/tit.usfm.ts deleted file mode 100644 index 03c0695a..00000000 --- a/packages/scribe/src/editor/data/tit.usfm.ts +++ /dev/null @@ -1,18 +0,0 @@ -export default String.raw`\id TIT TITFLS1910.PTX, Louis Segond 1910, French, 31-10-2011, Moon Sun Kim. Updated for DBL by E. Canales, September 2012. -\ide UTF-8 -\h TITE -\toc1 Épître de Paul à Tite -\toc2 Tite -\mt2 ÉPÎTRE DE PAUL -\mt1 À TITE -\imt1 INTRODUCTION À L’ÉPÎTRE À TITE -\imt3 (1 Ti 3:15. 1 Pi 2:15.) - -\c 1 -\s1 Adresse et salutation -\r V. 1-4: cf. 1 Ti 1:1, 2. 2 Pi 1:1-4. -\d A psalm of David, when he fled from the face of Absalom his son. -\p -\v 1 verse1 \f + \ft The Hebrew text can be read either as \fqa established praise \fqa* or \fqa established strength \fqa* . \f* text -\v 2 verse 2 -\v 3 verse`; diff --git a/packages/scribe/src/editor/data/titus.usfm.ts b/packages/scribe/src/editor/data/titus.usfm.ts deleted file mode 100644 index 032d4f68..00000000 --- a/packages/scribe/src/editor/data/titus.usfm.ts +++ /dev/null @@ -1,92 +0,0 @@ -export default String.raw`\id TIT TITFLS1910.PTX, Louis Segond 1910, French, 31-10-2011, Moon Sun Kim. Updated for DBL by E. Canales, September 2012. -\ide UTF-8 -\h TITE -\toc1 Épître de Paul à Tite -\toc2 Tite -\mt2 ÉPÎTRE DE PAUL -\mt1 À TITE -\imt1 INTRODUCTION À L’ÉPÎTRE À TITE -\imt3 (1 Ti 3:15. 1 Pi 2:15.) -\ip L’Épître à Tite fait partie, avec les deux précédentes, du groupe des pastorales, et partage donc avec elles certaines caractéristiques (voir l’introduction à 1 Timothée): il s’agit principalement d’instructions sur la direction des communautés ecclésiales. Voici la structure de cette épître: -\ib -\io1 1. Adresse et salutations \ior 1:1-40\ior* -\io1 2. Mission d’organiser l’Église de Crète: des anciens pour la lutte contre les faussaires \ior 1:5-16\ior* -\io1 3. Exhortation des fidèles toutes catégories confondues; être exemple dans la communauté \ior 2:1–3:11\ior* -\io1 4. Recommandations particulières et salutations \ior 3:12-15\ior* -\ib -\ip Tite est d’origine inconnue. Selon Gal 2:3, il serait né païen et grec. Devenu chrétien, il ne fut pas contraint de se faire circoncire. Tite n’est pas nommé dans le livre des Actes des Apôtres. Ce que nous savons de lui se trouve uniquement dans les épîtres. -\ip L’objet de la lettre est donné en 1:5ss en ces termes: \it «Je t’ai laissé en Crète afin que tu mettes en ordre ce qui reste à régler, et que, selon mes instructions, tu établisses des anciens dans chaque ville…»\it* L’auteur lui précise ainsi sa mission. C’est donc pour cette raison fondamentale qu’il lui adresse des encouragements pour s’assurer que les choses se passent comme voulu. -\ip L’originalité du message de l’auteur apparaît précisément dans cette mission. Bien que Tite et Timothée soient appelés à exercer le même ministère, il incombe à Tite une tâche bien déterminée. -\ip Même si le message de Tite reprend les sujets déjà traités dans 1 Timothée, on notera que les communautés tout comme les chrétiens sont différents. En effet dans la communauté crétoise il y a des incirconcis et des rebelles. L’auteur en avertit Tite: \it «L’un d’entre eux, leur propre prophète, a dit: Crétois toujours menteurs, méchantes bêtes, ventres paresseux… C’est pourquoi reprends-les sévèrement, afin qu’ils aient une foi saine…»\it* (Tite 1:12-13). -\ip Tite est lui aussi un fils spirituel de l’auteur comme Timothée, cela depuis leur entrevue dont parle Gal 2:1. Cependant cette épître semble moins personnelle que celles à Timothée. Elle est toutefois ornée de quelques passages particulièrement riches et denses. Elle comporte une introduction théologique, un rappel de la mission d’installation d’anciens chargés de contrecarrer les contradictions, et un résumé des exhortations aux fidèles. -\ie -\c 1 -\s1 Adresse et salutation -\r V. 1-4: cf. 1 Ti 1:1, 2. 2 Pi 1:1-4. -\d A psalm of David, when he fled from the face of Absalom his son. -\po -\v 1 \w Paul|strong="G3972"\w*, \w serviteur|strong="G1401"\w* \w de|strong="G2532"\w* \w Dieu|strong="G2316"\w*, \w et|strong="G2532"\w* apôtre \w de|strong="G2532"\w* Jésus-Christ \w pour|strong="G1161"\w* \w la|strong="G2532"\w* \w foi|strong="G4102"\w* \w des|strong="G2532"\w* \w élus|strong="G1588"\w* \w de|strong="G2532"\w* \w Dieu|strong="G2316"\w* \w et|strong="G2532"\w* \w la|strong="G2532"\w* \w connaissance|strong="G1922"\w* \w de|strong="G2532"\w* \w la|strong="G2532"\w* vérité \w qui|strong="G3588"\w* \w est|strong="G3588"\w* \w selon|strong="G2596"\w* \w la|strong="G2532"\w* \w piété|strong="G2150"\w*, -\m -\v 2 \w lesquelles|strong="G3739"\w* reposent \w sur|strong="G1909"\w* l’espérance \w de|strong="G1909"\w* \w la|strong="G1909"\w* \w vie|strong="G2222"\w* éternelle, \w promise|strong="G1861"\w* \x a \xo 1.2 \xt No 23:19. 2 Ti 2:13.\x*\w dès|strong="G1909"\w* \w les|strong="G1909"\w* \w plus|strong="G2532"\w* \w anciens|strong="G3588"\w* \w temps|strong="G5550"\w* \w par|strong="G1909"\w* \w le|strong="G1909"\w* \w Dieu|strong="G2316"\w* \x b \xo 1.2 \xt Ro 16:25. Ép 1:9; 3:9. Col 1:26. 1 Pi 1:20.\x*\w qui|strong="G3588"\w* \w ne|strong="G3739"\w* ment \w point|strong="G3361"\w*, -\v 3 \w et|strong="G1161"\w* \w qui|strong="G3588"\w* \w a|strong="G1722"\w* \w manifesté|strong="G5319"\w* \w sa|strong="G1722"\w* \w parole|strong="G3056"\w* \w en|strong="G1722"\w* \w son|strong="G2398"\w* \w temps|strong="G2540"\w* \w par|strong="G1722"\w* \w la|strong="G1722"\w* \w prédication|strong="G2782"\w* \x c \xo 1.3 \xt Ac 20:24. Ga 1:1.\x*\w qui|strong="G3588"\w* m’a \w été|strong="G1161"\w* \w confiée|strong="G4100"\w* d’après l’ordre \w de|strong="G1722"\w* \w Dieu|strong="G2316"\w* \w notre|strong="G2249"\w* \w Sauveur|strong="G4990"\w*, -\v 4 \x d \xo 1.4 \xt 2 Co 2:12; 7:14; 8:6, 16. Ga 2:3.\x*\w à|strong="G2532"\w* \w Tite|strong="G5103"\w*, \w mon|strong="G2532"\w* \w enfant|strong="G5043"\w* \w légitime|strong="G1103"\w* \w en|strong="G2532"\w* \w notre|strong="G2532"\w* \w commune|strong="G2839"\w* \w foi|strong="G4102"\w*: \x e \xo 1.4 \xt Ép 1:2. Col 1:2. 2 Ti 1:2. 1 Pi 1:2.\x*\w que|strong="G2532"\w* \w la|strong="G2532"\w* \w grâce|strong="G5485"\w* \w et|strong="G2532"\w* \w la|strong="G2532"\w* \w paix|strong="G1515"\w* \w te|strong="G2532"\w* \w soient|strong="G2316"\w* \w données|strong="G5613"\w* \w de|strong="G2532"\w* \w la|strong="G2532"\w* \w part|strong="G2596"\w* \w de|strong="G2532"\w* \w Dieu|strong="G2316"\w* \w le|strong="G2532"\w* \w Père|strong="G3962"\w* \w et|strong="G2532"\w* \w de|strong="G2532"\w* Jésus-Christ \w notre|strong="G2532"\w* \w Sauveur|strong="G4990"\w*! -\s1 Tite chargé d’organiser l’Église de Crète, d’établir des conducteurs fidèles, et de lutter contre ceux qui enseignent de fausses doctrines -\r V. 5-9: cf. 1 Ti 3:1-7. 1 Pi 5:1-4. -\p -\v 5 \w Je|strong="G1473"\w* t’ai laissé \w en|strong="G1722"\w* \w Crète|strong="G2914"\w*, \w afin|strong="G2443"\w* \w que|strong="G2443"\w* \w tu|strong="G4771"\w* mettes \w en|strong="G1722"\w* \w ordre|strong="G1299"\w* \w ce|strong="G3778"\w* \w qui|strong="G3588"\w* \w reste|strong="G5100"\w* \w à|strong="G1722"\w* \w régler|strong="G3007"\w*, \w et|strong="G2532"\w* \w que|strong="G2443"\w*, \w selon|strong="G2596"\w* \w mes|strong="G1473"\w* \w instructions|strong="G1299"\w*, \x f \xo 1.5 \xt 2 Ti 2:2.\x*\w tu|strong="G4771"\w* \w établisses|strong="G2525"\w* \w des|strong="G2532"\w* \w anciens|strong="G4245"\w* \w dans|strong="G1722"\w* \w chaque|strong="G2596"\w* \w ville|strong="G4172"\w*, -\v 6 \x g \xo 1.6 \xt 1 Ti 3:2.\x*s’il s’y \w trouve|strong="G1722"\w* \w quelque|strong="G5100"\w* \w homme|strong="G5100"\w* irréprochable, mari d’une \w seule|strong="G1520"\w* \w femme|strong="G1135"\w*, \w ayant|strong="G2192"\w* \w des|strong="G2192"\w* \w enfants|strong="G5043"\w* \w fidèles|strong="G4103"\w*, \w qui|strong="G5101"\w* \w ne|strong="G3361"\w* \w soient|strong="G1722"\w* \w ni|strong="G2228"\w* \w accusés|strong="G2724"\w* \w de|strong="G1722"\w* débauche \w ni|strong="G2228"\w* rebelles. -\v 7 \w Car|strong="G1063"\w* \w il|strong="G1063"\w* \w faut|strong="G1163"\w* \w que|strong="G5613"\w* l’évêque \w soit|strong="G2316"\w* irréprochable, \x h \xo 1.7 \xt Mt 24:45. 1 Co 4:1. 1 Ti 3:15.\x*\w comme|strong="G5613"\w* \w économe|strong="G3623"\w* \w de|strong="G3361"\w* \w Dieu|strong="G2316"\w*; qu’il \w ne|strong="G3361"\w* \w soit|strong="G2316"\w* \w ni|strong="G3361"\w* arrogant, \x i \xo 1.7 \xt Lé 10:9. Ép 5:18.\x*\w ni|strong="G3361"\w* \w colère|strong="G3711"\w*, \w ni|strong="G3361"\w* adonné \w au|strong="G5613"\w* \w vin|strong="G3943"\w*, \w ni|strong="G3361"\w* \w violent|strong="G4131"\w*, \x j \xo 1.7 \xt 1 Ti 3:3. 1 Pi 5:2.\x*\w ni|strong="G3361"\w* \w porté|strong="G2192"\w* \w à|strong="G2316"\w* \w un|strong="G5613"\w* \w gain|strong="G2771"\w* déshonnête; -\v 8 \w mais|strong="G1161"\w* \x k \xo 1.8 \xt 1 Ti 3:2.\x*qu’il \w soit|strong="G5547"\w* \w hospitalier|strong="G5382"\w*, ami \w des|strong="G2532"\w* \w gens|strong="G4183"\w* \w de|strong="G1342"\w* \w bien|strong="G5358"\w*, \w modéré|strong="G4998"\w*, \w juste|strong="G1342"\w*, \w saint|strong="G3741"\w*, \w tempérant|strong="G1468"\w*, -\v 9 \w attaché|strong="G1210"\w* \w à|strong="G1722"\w* \w la|strong="G1722"\w* \w vraie|strong="G4103"\w* \w parole|strong="G3056"\w* \w telle|strong="G2596"\w* qu’elle \w a|strong="G1722"\w* \w été|strong="G1510"\w* \w enseignée|strong="G1322"\w*, \w afin|strong="G2443"\w* d’être \w capable|strong="G1415"\w* d’exhorter \w selon|strong="G2596"\w* \w la|strong="G1722"\w* \w saine|strong="G5198"\w* \w doctrine|strong="G1322"\w* \w et|strong="G2532"\w* \w de|strong="G2532"\w* \w réfuter|strong="G1651"\w* \w les|strong="G2532"\w* contradicteurs. -\r V. 10-16: cf. Ro 16:17, 18. 1 Ti 1:3-7. 2 Ti 2:16-18, 23-26; 3:5-9. -\p -\v 10 \x l \xo 1.10 \xt Ac 15:1.\x*\w Il|strong="G2532"\w* \w y|strong="G2532"\w* \w a|strong="G2532"\w*, \w en|strong="G2532"\w* \w effet|strong="G1063"\w*, \w surtout|strong="G3122"\w* \w parmi|strong="G1537"\w* \w les|strong="G2532"\w* \w circoncis|strong="G4061"\w*, \w beaucoup|strong="G4183"\w* \w de|strong="G1537"\w* \w gens|strong="G4183"\w* rebelles, \w de|strong="G1537"\w* vains \w discoureurs|strong="G3151"\w* \w et|strong="G2532"\w* \w de|strong="G1537"\w* \w séducteurs|strong="G5423"\w*, -\v 11 \w auxquels|strong="G3739"\w* \w il|strong="G3739"\w* \w faut|strong="G1163"\w* fermer \w la|strong="G3361"\w* \w bouche|strong="G1993"\w*. \w Ils|strong="G3748"\w* bouleversent \x m \xo 1.11 \xt Mt 23:14. 2 Ti 3:6.\x*\w des|strong="G3361"\w* \w familles|strong="G3624"\w* \w entières|strong="G3650"\w*, \w enseignant|strong="G1321"\w* \w pour|strong="G3739"\w* \w un|strong="G3739"\w* \w gain|strong="G2771"\w* honteux \w ce|strong="G3739"\w* qu’on \w ne|strong="G3361"\w* \w doit|strong="G1163"\w* \w pas|strong="G3361"\w* \w enseigner|strong="G1321"\w*. -\v 12 L’un d’entre \f + \fq if you will not redeem it \ft or perhaps \fqa if he will not redeem it \ft (variant Hebrew manuscripts)\f*\f + \fq so that I may know \ft or perhaps \fqa and let me know \ft (Hebrew Qere)\f* \w eux|strong="G2398"\w*, \w leur|strong="G2398"\w* \w propre|strong="G2398"\w* \w prophète|strong="G4396"\w*, \w a|strong="G3004"\w* \w dit|strong="G3004"\w*: \w Crétois|strong="G2912"\w* \w toujours|strong="G3956"\w* \w menteurs|strong="G5583"\w*, \w méchantes|strong="G2556"\w* \w bêtes|strong="G2342"\w*, \w ventres|strong="G1064"\w* paresseux. -\v 13 \w Ce|strong="G3739"\w* \w témoignage|strong="G3141"\w* \w est|strong="G3588"\w* vrai. C’est \w pourquoi|strong="G1223"\w* reprends-les \w sévèrement|strong="G4183"\w*, \w afin|strong="G2443"\w* qu’ils \w aient|strong="G2192"\w* \w une|strong="G1722"\w* \w foi|strong="G4102"\w* \w saine|strong="G5198"\w*, -\v 14 \x n \xo 1.14 \xt És 29:13. Mt 15:9. Col 2:22. 1 Ti 1:4; 4:7; 6:20.\x*\w et|strong="G2532"\w* qu’ils \w ne|strong="G2532"\w* s’attachent \w pas|strong="G3361"\w* \w à|strong="G2532"\w* \w des|strong="G2532"\w* \w fables|strong="G3454"\w* \w judaïques|strong="G2451"\w* \w et|strong="G2532"\w* \w à|strong="G2532"\w* \w des|strong="G2532"\w* \w commandements|strong="G1785"\w* d’hommes \w qui|strong="G3588"\w* \w se|strong="G2532"\w* détournent \w de|strong="G2532"\w* \w la|strong="G2532"\w* vérité. -\v 15 \x o \xo 1.15 \xt Mt 15:11. Ac 10:15. Ro 14:20.\x*\w Tout|strong="G3956"\w* \w est|strong="G3588"\w* \w pur|strong="G2513"\w* \w pour|strong="G1161"\w* \w ceux|strong="G3588"\w* \w qui|strong="G3588"\w* \w sont|strong="G3588"\w* \w purs|strong="G2513"\w*; \x p \xo 1.15 \xt Mt 23:25. Ro 14:23.\x*\w mais|strong="G1161"\w* \w rien|strong="G3762"\w* n’est \w pur|strong="G2513"\w* \w pour|strong="G1161"\w* \w ceux|strong="G3588"\w* \w qui|strong="G3588"\w* \w sont|strong="G3588"\w* \w souillés|strong="G3392"\w* \w et|strong="G2532"\w* \w incrédules|strong="G3361"\w*, \w leur|strong="G2532"\w* \w intelligence|strong="G3563"\w* \w et|strong="G2532"\w* \w leur|strong="G2532"\w* \w conscience|strong="G4893"\w* \w sont|strong="G3588"\w* \w souillées|strong="G3392"\w*. -\v 16 \w Ils|strong="G2532"\w* \w font|strong="G5613"\w* \w profession|strong="G3670"\w* \w de|strong="G2532"\w* \w connaître|strong="G1492"\w* \w Dieu|strong="G2316"\w*, \w mais|strong="G1161"\w* \w ils|strong="G2532"\w* \w le|strong="G2532"\w* renient \w par|strong="G2532"\w* \w leurs|strong="G2532"\w* \w œuvres|strong="G2041"\w*, \w étant|strong="G2532"\w* abominables, rebelles, \w et|strong="G2532"\w* incapables d’aucune \w bonne|strong="G1063"\w* \w œuvre|strong="G2041"\w*. -\c 2 -\s1 Comment Tite doit exhorter les vieillards, les femmes, les jeunes hommes, les serviteurs, et quel exemple il doit donner lui-même -\r V. 1-10: cf. (1 Ti 2:9-15. 1 Pi 3:1-6.) Ps 119:9. (Ép 6:5-8. 1 Pi 2:18-20.) -\p -\v 1 \w Pour|strong="G1161"\w* \w toi|strong="G4771"\w*, \w dis|strong="G2980"\w* \w les|strong="G1161"\w* \w choses|strong="G3588"\w* \w qui|strong="G3588"\w* \w sont|strong="G3588"\w* \w conformes|strong="G4241"\w* \w à|strong="G1161"\w* \w la|strong="G1161"\w* \w saine|strong="G5198"\w* \w doctrine|strong="G1319"\w*. -\v 2 \w Dis|strong="G3004"\w* \w que|strong="G3588"\w* \w les|strong="G4102"\w* \w vieillards|strong="G4246"\w* \w doivent|strong="G4246"\w* \w être|strong="G2443"\w* \w sobres|strong="G3524"\w*, \w honnêtes|strong="G4586"\w*, \w modérés|strong="G4998"\w*, \w sains|strong="G5198"\w* \w dans|strong="G3588"\w* \w la|strong="G1510"\w* \w foi|strong="G4102"\w*, \w dans|strong="G3588"\w* \w la|strong="G1510"\w* charité, \w dans|strong="G3588"\w* \w la|strong="G1510"\w* \w patience|strong="G5281"\w*. -\v 3 \w Dis|strong="G3004"\w* \w que|strong="G1722"\w* \w les|strong="G1722"\w* \w femmes|strong="G2192"\w* \w âgées|strong="G4247"\w* \x a \xo 2.3 \xt 1 Ti 2:9. 1 Pi 3:3.\x*\w doivent|strong="G4247"\w* \w aussi|strong="G5615"\w* \w avoir|strong="G3361"\w* l’extérieur \w qui|strong="G1722"\w* \w convient|strong="G4241"\w* \w à|strong="G1722"\w* \w la|strong="G1722"\w* \w sainteté|strong="G2412"\w*, \x b \xo 2.3 \xt 1 Ti 5:13.\x*n’être \w ni|strong="G3366"\w* \w médisantes|strong="G1228"\w*, \w ni|strong="G3366"\w* \w adonnées|strong="G1402"\w* \w au|strong="G1722"\w* \w vin|strong="G3631"\w*; qu’elles \w doivent|strong="G4247"\w* \w donner|strong="G1325"\w* \w de|strong="G1722"\w* \w bonnes|strong="G2570"\w* \w instructions|strong="G2567"\w*, -\v 4 \w dans|strong="G3588"\w* \w le|strong="G1510"\w* \w but|strong="G2443"\w* d’apprendre \w aux|strong="G2532"\w* \w jeunes|strong="G3501"\w* \w femmes|strong="G3501"\w* \w à|strong="G3588"\w* aimer \w leurs|strong="G1438"\w* \w maris|strong="G5362"\w* \w et|strong="G2443"\w* \w leurs|strong="G1438"\w* \w enfants|strong="G5388"\w*, -\v 5 \w à|strong="G2316"\w* \w être|strong="G2443"\w* \w retenues|strong="G4998"\w*, chastes, occupées \w aux|strong="G5293"\w* \w soins|strong="G4183"\w* \w domestiques|strong="G3626"\w*, \w bonnes|strong="G2570"\w*, \x c \xo 2.5 \xt Ge 3:16. 1 Co 14:34. Ép 5:22. Col 3:18. 1 Pi 3:1.\x*\w soumises|strong="G5293"\w* \w à|strong="G2316"\w* \w leurs|strong="G2398"\w* \w maris|strong="G5362"\w*, \w afin|strong="G2443"\w* \w que|strong="G2443"\w* \w la|strong="G3361"\w* \w parole|strong="G3056"\w* \w de|strong="G2443"\w* \w Dieu|strong="G2316"\w* \w ne|strong="G3361"\w* \w soit|strong="G2316"\w* \w pas|strong="G3361"\w* blasphémée. -\v 6 \w Exhorte|strong="G3870"\w* \w de|strong="G3588"\w* \w même|strong="G5615"\w* \w les|strong="G3870"\w* \w jeunes|strong="G3501"\w* \w gens|strong="G3501"\w* \w à|strong="G3588"\w* \w être|strong="G2443"\w* \w modérés|strong="G4993"\w*, -\v 7 \x d \xo 2.7 \xt 1 Ti 4:12. 1 Pi 5:3.\x*\w te|strong="G4572"\w* \w montrant|strong="G3930"\w* toi-même \w à|strong="G1722"\w* \w tous|strong="G3956"\w* \w égards|strong="G3956"\w* \w un|strong="G1722"\w* \w modèle|strong="G5179"\w* \w de|strong="G1722"\w* \w bonnes|strong="G2570"\w* \w œuvres|strong="G2041"\w*, \w et|strong="G1722"\w* \w donnant|strong="G1722"\w* \w un|strong="G1722"\w* \w enseignement|strong="G1319"\w* \w pur|strong="G2511"\w*, \w digne|strong="G4587"\w*, -\v 8 \w une|strong="G2192"\w* \w parole|strong="G3056"\w* \w saine|strong="G5199"\w*, irréprochable, \x e \xo 2.8 \xt 1 Pi 2:12, 15; 3:16.\x*\w afin|strong="G2443"\w* \w que|strong="G2443"\w* l’adversaire \w soit|strong="G3367"\w* \w confus|strong="G1788"\w*, n’ayant \w aucun|strong="G3367"\w* \w mal|strong="G5337"\w* \w à|strong="G2192"\w* \w dire|strong="G3004"\w* \w de|strong="G1537"\w* \w nous|strong="G2249"\w*. -\v 9 \x f \xo 2.9 \xt Ép 6:5. Col 3:22. 1 Ti 6:1, 2. 1 Pi 2:18.\x*\w Exhorte|strong="G3870"\w* \w les|strong="G3956"\w* \w serviteurs|strong="G1401"\w* \w à|strong="G1722"\w* \w être|strong="G3361"\w* \w soumis|strong="G5293"\w* \w à|strong="G1722"\w* \w leurs|strong="G2398"\w* \w maîtres|strong="G1203"\w*, \w à|strong="G1722"\w* \w leur|strong="G1722"\w* \w plaire|strong="G2101"\w* \w en|strong="G1722"\w* \w toutes|strong="G3956"\w* \w choses|strong="G3956"\w*, \w à|strong="G1722"\w* n’être \w point|strong="G3361"\w* contredisants, -\v 10 \w à|strong="G1722"\w* \w ne|strong="G3361"\w* \w rien|strong="G3361"\w* \w dérober|strong="G3557"\w*, \w mais|strong="G1161"\w* \w à|strong="G1722"\w* \w montrer|strong="G1731"\w* \w toujours|strong="G3956"\w* \w une|strong="G1722"\w* \w parfaite|strong="G3956"\w* \w fidélité|strong="G4102"\w*, \w afin|strong="G2443"\w* \w de|strong="G1722"\w* \w faire|strong="G2443"\w* \w honorer|strong="G2885"\w* \w en|strong="G1722"\w* \w tout|strong="G3956"\w* \w la|strong="G1722"\w* \w doctrine|strong="G1319"\w* \w de|strong="G1722"\w* \w Dieu|strong="G2316"\w* \w notre|strong="G2249"\w* \w Sauveur|strong="G4990"\w*. -\s1 La grâce de Dieu manifestée: ce qu’elle enseigne, et ce que Tite doit enseigner -\r V. 11-15: cf. 1 Pi 1:10-21. (Ro 12:1, 2. 1 Co 6:20. 1 Jn 3:1-3.) -\p -\v 11 \w Car|strong="G1063"\w* \w la|strong="G3956"\w* \w grâce|strong="G5485"\w* \w de|strong="G5485"\w* \w Dieu|strong="G2316"\w*, source \w de|strong="G5485"\w* \w salut|strong="G4992"\w* \w pour|strong="G1063"\w* \w tous|strong="G3956"\w* \w les|strong="G3956"\w* \w hommes|strong="G3778"\w*, \w a|strong="G2316"\w* \w été|strong="G3956"\w* \w manifestée|strong="G2014"\w*. -\v 12 \w Elle|strong="G2532"\w* \w nous|strong="G2249"\w* \w enseigne|strong="G1722"\w* \w à|strong="G1722"\w* renoncer \w à|strong="G1722"\w* \x g \xo 2.12 \xt Ép 1:4. Col 1:22. 2 Ti 1:9.\x*l’impiété \w et|strong="G2532"\w* \w aux|strong="G2532"\w* \x h \xo 2.12 \xt 1 Jn 2:16.\x*\w convoitises|strong="G1939"\w* \w mondaines|strong="G2886"\w*, \w et|strong="G2532"\w* \w à|strong="G1722"\w* \w vivre|strong="G2198"\w* \w dans|strong="G1722"\w* \w le|strong="G1722"\w* siècle \w présent|strong="G3568"\w* \w selon|strong="G1722"\w* \w la|strong="G1722"\w* \w sagesse|strong="G4996"\w*, \w la|strong="G1722"\w* \w justice|strong="G1346"\w* \w et|strong="G2532"\w* \w la|strong="G1722"\w* \w piété|strong="G2153"\w*, -\v 13 \x i \xo 2.13 \xt 1 Co 1:7. Ph 3:20.\x*\w en|strong="G2532"\w* \w attendant|strong="G4327"\w* \w la|strong="G2532"\w* \w bienheureuse|strong="G3107"\w* \w espérance|strong="G1680"\w*, \w et|strong="G2532"\w* \w la|strong="G2532"\w* \w manifestation|strong="G2015"\w* \w de|strong="G2532"\w* \w la|strong="G2532"\w* \w gloire|strong="G1391"\w* \w du|strong="G2532"\w* \w grand|strong="G3173"\w* \w Dieu|strong="G2316"\w* \w et|strong="G2532"\w* \w de|strong="G2532"\w* \w notre|strong="G2532"\w* \w Sauveur|strong="G4990"\w* Jésus-Christ, -\v 14 \x j \xo 2.14 \xt Ga 1:4; 2:20. Ép 5:2. Hé 9:14.\x*\w qui|strong="G3739"\w* s’est \w donné|strong="G1325"\w* \w lui-même|strong="G3739"\w* \w pour|strong="G5228"\w* \w nous|strong="G2249"\w*, \w afin|strong="G2443"\w* \w de|strong="G2532"\w* \w nous|strong="G2249"\w* \w racheter|strong="G3084"\w* \w de|strong="G2532"\w* \w toute|strong="G3956"\w* iniquité, \w et|strong="G2532"\w* \w de|strong="G2532"\w* \w se|strong="G2532"\w* \w faire|strong="G2443"\w* \w un|strong="G2532"\w* \w peuple|strong="G2992"\w* \w qui|strong="G3739"\w* \w lui|strong="G3739"\w* \w appartienne|strong="G4041"\w*, \w purifié|strong="G2511"\w* \w par|strong="G2532"\w* \w lui|strong="G3739"\w* \w et|strong="G2532"\w* \x k \xo 2.14 \xt Ép 2:10.\x*\w zélé|strong="G2207"\w* \w pour|strong="G5228"\w* \w les|strong="G2532"\w* \w bonnes|strong="G2570"\w* \w œuvres|strong="G2041"\w*. -\v 15 \w Dis|strong="G2980"\w* \w ces|strong="G3956"\w* \w choses|strong="G3956"\w*, \w exhorte|strong="G3870"\w*, \w et|strong="G2532"\w* \w reprends|strong="G1651"\w*, \w avec|strong="G3326"\w* \w une|strong="G2532"\w* \w pleine|strong="G3956"\w* \w autorité|strong="G2003"\w*. \x l \xo 2.15 \xt 1 Ti 4:12.\x*\w Que|strong="G2532"\w* \w personne|strong="G3367"\w* \w ne|strong="G2532"\w* \w te|strong="G4771"\w* \w méprise|strong="G4065"\w*. -\c 3 -\r V. 1-11: cf. 1 Pi 2:13-16. (Ép 2:1-10. Tit 2:11-15.) Ro 16:17, 18. -\p -\v 1 \x a \xo 3.1 \xt Ro 13:1, etc. 1 Pi 2:13.\x*Rappelle-leur d’être \w soumis|strong="G5293"\w* \w aux|strong="G4314"\w* magistrats \w et|strong="G4314"\w* \w aux|strong="G4314"\w* \w autorités|strong="G1849"\w*, d’obéir, d’être \w prêts|strong="G2092"\w* \w à|strong="G4314"\w* \w toute|strong="G3956"\w* \w bonne|strong="G2570"\w* \w œuvre|strong="G2041"\w*, -\v 2 \w de|strong="G4314"\w* \w ne|strong="G3367"\w* médire \w de|strong="G4314"\w* \w personne|strong="G3367"\w*, d’être pacifiques, \x b \xo 3.2 \xt Ph 4:5.\x*\w modérés|strong="G1933"\w*, \x c \xo 3.2 \xt 2 Ti 2:24, 25.\x*\w pleins|strong="G1731"\w* \w de|strong="G4314"\w* \w douceur|strong="G4240"\w* \w envers|strong="G4314"\w* \w tous|strong="G3956"\w* \w les|strong="G3956"\w* \w hommes|strong="G3778"\w*. -\v 3 \x d \xo 3.3 \xt 1 Co 6:11. Ép 2:1. Col 3:7. 1 Pi 4:3.\x*\w Car|strong="G1063"\w* \w nous|strong="G2249"\w* \w aussi|strong="G2532"\w*, \w nous|strong="G2249"\w* étions \w autrefois|strong="G4218"\w* \w insensés|strong="G3474"\w*, désobéissants, \w égarés|strong="G4105"\w*, \w asservis|strong="G1398"\w* \w à|strong="G1722"\w* \w toute|strong="G2532"\w* \w espèce|strong="G4164"\w* \w de|strong="G2532"\w* \w convoitises|strong="G1939"\w* \w et|strong="G2532"\w* \w de|strong="G2532"\w* \w voluptés|strong="G2237"\w*, \w vivant|strong="G1236"\w* \w dans|strong="G1722"\w* \w la|strong="G1722"\w* \w méchanceté|strong="G2549"\w* \w et|strong="G2532"\w* \w dans|strong="G1722"\w* l’envie, dignes d’être \w haïs|strong="G3404"\w*, \w et|strong="G2532"\w* \w nous|strong="G2249"\w* \w haïssant|strong="G3404"\w* \w les|strong="G2532"\w* \w uns|strong="G1537"\w* \w les|strong="G2532"\w* \w autres|strong="G1722"\w*. -\v 4 \w Mais|strong="G1161"\w*, \w lorsque|strong="G3753"\w* \w la|strong="G2532"\w* \w bonté|strong="G5544"\w* \w de|strong="G2532"\w* \w Dieu|strong="G2316"\w* \w notre|strong="G2532"\w* \w Sauveur|strong="G4990"\w* \w et|strong="G2532"\w* \w son|strong="G2532"\w* \w amour|strong="G5363"\w* \w pour|strong="G1161"\w* \w les|strong="G2532"\w* \w hommes|strong="G3778"\w* \w ont|strong="G2532"\w* \w été|strong="G1161"\w* \w manifestés|strong="G2014"\w*, -\v 5 \x e \xo 3.5 \xt Ép 1:4. 2 Ti 1:9.\x*\w il|strong="G2532"\w* \w nous|strong="G2249"\w* \w a|strong="G1722"\w* \w sauvés|strong="G4982"\w*, \x f \xo 3.5 \xt Ro 3:20, 28; 4:2, 6; 9:11; 11:6. Ga 2:16. Ép 2:9.\x*\w non|strong="G3756"\w* \w à|strong="G1722"\w* \w cause|strong="G1223"\w* \w des|strong="G2532"\w* \w œuvres|strong="G2041"\w* \w de|strong="G1537"\w* \w justice|strong="G1343"\w* \w que|strong="G3739"\w* \w nous|strong="G2249"\w* \w aurions|strong="G2249"\w* \w faites|strong="G4160"\w*, \x g \xo 3.5 \xt Ac 15:11. Ép 2:4.\x*\w mais|strong="G2532"\w* \w selon|strong="G2596"\w* \w sa|strong="G2532"\w* \w miséricorde|strong="G1656"\w*, \w par|strong="G1223"\w* \w le|strong="G1722"\w* \w baptême|strong="G3067"\w* \w de|strong="G1537"\w* \w la|strong="G1722"\w* \w régénération|strong="G3824"\w* \w et|strong="G2532"\w* \w le|strong="G1722"\w* \w renouvellement|strong="G3824"\w* \w du|strong="G1537"\w* Saint-Esprit, -\v 6 \x h \xo 3.6 \xt Éz 36:25.\x*qu’il \w a|strong="G1909"\w* \w répandu|strong="G1632"\w* \w sur|strong="G1909"\w* \w nous|strong="G2249"\w* \w avec|strong="G1223"\w* \w abondance|strong="G4146"\w* \w par|strong="G1223"\w* Jésus-Christ \w notre|strong="G1909"\w* \w Sauveur|strong="G4990"\w*, -\v 7 \w afin|strong="G2443"\w* \w que|strong="G2443"\w*, \w justifiés|strong="G1344"\w* \w par|strong="G2596"\w* \w sa|strong="G1565"\w* \w grâce|strong="G5485"\w*, \w nous|strong="G2443"\w* \w devenions|strong="G1096"\w*, \w en|strong="G2596"\w* \w espérance|strong="G1680"\w*, \w héritiers|strong="G2818"\w* \w de|strong="G2443"\w* \w la|strong="G2596"\w* \w vie|strong="G2222"\w* éternelle. -\v 8 \w Cette|strong="G3778"\w* \w parole|strong="G3056"\w* \w est|strong="G3588"\w* \w certaine|strong="G4103"\w*, \w et|strong="G2532"\w* \w je|strong="G2532"\w* \w veux|strong="G1014"\w* \w que|strong="G2443"\w* \w tu|strong="G4771"\w* \w affirmes|strong="G1226"\w* \w ces|strong="G3778"\w* \w choses|strong="G3588"\w*, \w afin|strong="G2443"\w* \w que|strong="G2443"\w* \w ceux|strong="G3588"\w* \w qui|strong="G3588"\w* \w ont|strong="G3778"\w* \w cru|strong="G4100"\w* \w en|strong="G2532"\w* \w Dieu|strong="G2316"\w* s’appliquent \w à|strong="G2532"\w* \w pratiquer|strong="G4291"\w* \w de|strong="G2532"\w* \w bonnes|strong="G2570"\w* \w œuvres|strong="G2041"\w*. \x i \xo 3.8 \xt 1 Ti 1:4; 4:7; 6:20. Tit 1:14.\x*Voilà \w ce|strong="G3778"\w* \w qui|strong="G3588"\w* \w est|strong="G3588"\w* \w bon|strong="G2570"\w* \w et|strong="G2532"\w* utile \w aux|strong="G2532"\w* \w hommes|strong="G3778"\w*. -\v 9 \w Mais|strong="G1161"\w* \w évite|strong="G4026"\w* \w les|strong="G2532"\w* \w discussions|strong="G2214"\w* \w folles|strong="G3474"\w*, \w les|strong="G2532"\w* \w généalogies|strong="G1076"\w*, \w les|strong="G2532"\w* \w querelles|strong="G2054"\w*, \w les|strong="G2532"\w* \w disputes|strong="G2054"\w* relatives \w à|strong="G2532"\w* \w la|strong="G2532"\w* \w loi|strong="G3544"\w*; \w car|strong="G1063"\w* \w elles|strong="G2532"\w* \w sont|strong="G2532"\w* inutiles \w et|strong="G2532"\w* \w vaines|strong="G3152"\w*. -\v 10 \x j \xo 3.10 \xt Mt 18:17. Ro 16:17. 2 Th 3:6. 2 Ti 3:5. 2 Jn v. 10.\x*\w Éloigne|strong="G3868"\w* \w de|strong="G2532"\w* \w toi|strong="G4771"\w*, \w après|strong="G3326"\w* \w un|strong="G1520"\w* \w premier|strong="G2596"\w* \w et|strong="G2532"\w* \w un|strong="G1520"\w* \w second|strong="G1208"\w* \w avertissement|strong="G3559"\w*, \w celui|strong="G2532"\w* \w qui|strong="G2532"\w* provoque \w des|strong="G2532"\w* divisions, -\v 11 \w sachant|strong="G1492"\w* qu’un \w homme|strong="G5108"\w* \w de|strong="G2532"\w* \w cette|strong="G2532"\w* \w espèce|strong="G5108"\w* \w est|strong="G3588"\w* \w perverti|strong="G1612"\w*, \w et|strong="G2532"\w* qu’il \w pèche|strong="G4160"\w*, \w en|strong="G2532"\w* \w se|strong="G2532"\w* \w condamnant|strong="G2919"\w* \w lui-même|strong="G3739"\w*. -\s1 Recommandations particulières. Salutations -\r V. 12-15: cf. Jn 15:8. 1 Co 16:20-24. -\p -\v 12 \w Lorsque|strong="G3752"\w* \w je|strong="G1473"\w* t’enverrai Artémas \w ou|strong="G2228"\w* \x k \xo 3.12 \xt Ac 20:4. Ép 6:21. 2 Ti 4:12.\x*\w Tychique|strong="G5190"\w*, hâte-toi \w de|strong="G1519"\w* \w venir|strong="G2064"\w* \w me|strong="G1473"\w* \w rejoindre|strong="G4314"\w* \w à|strong="G1519"\w* \w Nicopolis|strong="G3533"\w*; \w car|strong="G1063"\w* c’est \w là|strong="G1519"\w* \w que|strong="G2228"\w* j’ai \w résolu|strong="G2919"\w* \w de|strong="G1519"\w* \w passer|strong="G2443"\w* l’hiver. -\v 13 \w Aie|strong="G2424"\w* \w soin|strong="G2532"\w* \w de|strong="G2532"\w* \w pourvoir|strong="G4709"\w* \w au|strong="G2532"\w* \w voyage|strong="G4311"\w* \w de|strong="G2532"\w* \w Zénas|strong="G2211"\w*, \w le|strong="G2532"\w* docteur \w de|strong="G2532"\w* \w la|strong="G2532"\w* \w loi|strong="G3544"\w*, \w et|strong="G2532"\w* \x l \xo 3.13 \xt Ac 18:24. 1 Co 1:12.\x*d’Apollos, \w en|strong="G2532"\w* \w sorte|strong="G2443"\w* \w que|strong="G2443"\w* \w rien|strong="G3367"\w* \w ne|strong="G2532"\w* \w leur|strong="G2532"\w* \w manque|strong="G3007"\w*. -\v 14 \w Il|strong="G2532"\w* faut \w que|strong="G2443"\w* \w les|strong="G2532"\w* \w nôtres|strong="G2251"\w* \w aussi|strong="G2532"\w* \w apprennent|strong="G3129"\w* \w à|strong="G1519"\w* \w pratiquer|strong="G4291"\w* \w de|strong="G2532"\w* \w bonnes|strong="G2570"\w* \w œuvres|strong="G2041"\w* \w pour|strong="G1519"\w* subvenir \w aux|strong="G1519"\w* \w besoins|strong="G5532"\w* pressants, \w afin|strong="G2443"\w* qu’ils \w ne|strong="G2532"\w* \w soient|strong="G2041"\w* \w pas|strong="G3361"\w* \w sans|strong="G3361"\w* \w produire|strong="G4160"\w* \w des|strong="G2532"\w* fruits. -\v 15 \w Tous|strong="G3956"\w* \w ceux|strong="G3588"\w* \w qui|strong="G3588"\w* \w sont|strong="G3588"\w* \w avec|strong="G3326"\w* \w moi|strong="G1473"\w* \w te|strong="G4771"\w* saluent. Salue \w ceux|strong="G3588"\w* \w qui|strong="G3588"\w* \w nous|strong="G2249"\w* \w aiment|strong="G5368"\w* \w dans|strong="G1722"\w* \w la|strong="G1722"\w* \w foi|strong="G4102"\w*. \w Que|strong="G3588"\w* \w la|strong="G1722"\w* \w grâce|strong="G5485"\w* \w soit|strong="G5485"\w* \w avec|strong="G3326"\w* \w vous|strong="G5210"\w* \w tous|strong="G3956"\w*!`; diff --git a/packages/scribe/src/editor/nodes/emptyCrossRefence.ts b/packages/scribe/src/editor/nodes/emptyCrossRefence.ts deleted file mode 100644 index 3d6f179d..00000000 --- a/packages/scribe/src/editor/nodes/emptyCrossRefence.ts +++ /dev/null @@ -1,81 +0,0 @@ -export const emptyFootnote = { - children: [ - { - direction: null, - format: "", - indent: 0, - type: "para", - version: 1, - textFormat: 0, - marker: "p", - children: [ - { - type: "note", - marker: "f", - caller: "+", - direction: null, - format: "", - indent: 0, - version: 1, - children: [ - { - type: "immutable-note-caller", - caller: "b", - previewText: - "The Hebrew text can be read either as established praise established strength", - version: 1, - }, - { - type: "char", - marker: "ft", - text: "The Hebrew text can be read either as", - detail: 0, - format: 0, - mode: "normal", - style: "display: none", - version: 1, - }, - { - type: "char", - marker: "fqa", - text: "established praise", - detail: 0, - format: 0, - mode: "normal", - style: "display: none", - version: 1, - }, - { - type: "text", - text: "or", - detail: 0, - format: 0, - mode: "normal", - style: "display: none", - version: 1, - }, - { - type: "char", - marker: "fqa", - text: "established strength IIIII", - detail: 0, - format: 0, - mode: "normal", - style: "display: none", - version: 1, - }, - { - type: "text", - text: ".", - detail: 0, - format: 0, - mode: "normal", - style: "display: none", - version: 1, - }, - ], - }, - ], - }, - ], -}; diff --git a/packages/scribe/src/editor/nodes/emptyFootNote.ts b/packages/scribe/src/editor/nodes/emptyFootNote.ts deleted file mode 100644 index 49a9d527..00000000 --- a/packages/scribe/src/editor/nodes/emptyFootNote.ts +++ /dev/null @@ -1,85 +0,0 @@ -export interface Note { - type: "note"; - marker: string; - caller: string; - direction: string | null; - format: string; - indent: number; - version: number; - children: (ImmutableNoteCaller | Char | Text)[]; -} - -export interface ImmutableNoteCaller { - type: "immutable-note-caller"; - caller: string; - previewText: string; - version: number; -} - -export interface Char { - type: "char"; - marker: string; - text: string; - detail: number; - format: number; - mode: string; - style: string; - version: number; -} - -interface Text { - type: "text"; - text: string; - detail: number; - format: number; - mode: string; - style: string; - version: number; -} -export interface Para { - direction: null; - format: string; - indent: number; - type: "para"; - version: number; - textFormat: number; - marker: string; - children: Note[]; -} - -export interface Footnote { - children: Para[]; -} - -export const emptyFootnote: Footnote = { - children: [ - { - direction: null, - format: "", - indent: 0, - type: "para", - version: 1, - textFormat: 0, - marker: "p", - children: [ - { - type: "note", - marker: "f", - caller: "+", - direction: null, - format: "", - indent: 0, - version: 1, - children: [ - { - type: "immutable-note-caller", - caller: "b", - previewText: "", - version: 1, - }, - ], - }, - ], - }, - ], -}; diff --git a/packages/scribe/src/editor/plugins/KeyboardShortcutPlugin.tsx b/packages/scribe/src/editor/plugins/KeyboardShortcutPlugin.tsx deleted file mode 100644 index 1228b99a..00000000 --- a/packages/scribe/src/editor/plugins/KeyboardShortcutPlugin.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useEffect } from "react"; -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { IS_APPLE } from "@lexical/utils"; -import { UNDO_COMMAND, REDO_COMMAND } from "lexical"; - -export default function KeyboardShortcutPlugin(): null { - const [editor] = useLexicalComposerContext(); - - useEffect(() => { - const onKeyDown = (event: KeyboardEvent) => { - const { key, shiftKey, metaKey, ctrlKey, altKey } = event; - if (!(IS_APPLE ? metaKey : ctrlKey) || altKey) return; - if (key.toLowerCase() === "z" && shiftKey) { - event.preventDefault(); - editor.dispatchCommand(REDO_COMMAND, undefined); - } else if (key.toLowerCase() === "z") { - console.log("undo"); - event.preventDefault(); - editor.dispatchCommand(UNDO_COMMAND, undefined); - } - }; - - return editor.registerRootListener( - (rootElement: HTMLElement | null, prevRootElement: HTMLElement | null) => { - if (prevRootElement !== null) { - prevRootElement.removeEventListener("keydown", onKeyDown); - } - if (rootElement !== null) { - rootElement.addEventListener("keydown", onKeyDown); - } - }, - ); - }, [editor]); - - return null; -} diff --git a/packages/scribe/src/editor/plugins/ScriptureReferencePlugin.tsx b/packages/scribe/src/editor/plugins/ScriptureReferencePlugin.tsx deleted file mode 100644 index 607d921d..00000000 --- a/packages/scribe/src/editor/plugins/ScriptureReferencePlugin.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { - $getNodeByKey, - $getRoot, - $getSelection, - $isTextNode, - COMMAND_PRIORITY_LOW, - EditorState, - SELECTION_CHANGE_COMMAND, -} from "lexical"; -import { MutableRefObject, useEffect, useRef } from "react"; -import { - $findChapter, - $findNextChapter, - $findThisChapter, - $isBookNode, - $isParaNode, - BookNode, - CURSOR_CHANGE_TAG, - getSelectionStartNode, - isVerseInRange, - isVerseRange, - removeNodeAndAfter, - removeNodesBeforeNode, - ScriptureReference, -} from "shared"; -import { $findThisVerse, $findVerseOrPara } from "shared-react"; - -/** - * A component (plugin) that keeps the Scripture reference updated. - * @param scrRef - Scripture reference. - * @param onScrRefChange - Callback function when the Scripture reference has changed. - * @returns null, i.e. no DOM elements. - */ -export function ScriptureReferencePlugin({ - scrRef, - onScrRefChange, -}: { - scrRef: ScriptureReference; - onScrRefChange: (scrRef: ScriptureReference) => void; -}): null { - const [editor] = useLexicalComposerContext(); - /** Prevents the cursor being moved again after a selection has changed. */ - const hasSelectionChangedRef = useRef(false); - /** Prevents the `scrRef` updating again after the cursor has moved. */ - const hasCursorMovedRef = useRef(false); - const { book, chapterNum, verseNum } = scrRef; - - // Book loaded or changed - useEffect( - () => - editor.registerMutationListener( - BookNode, - (nodeMutations) => { - editor.update( - () => { - for (const [nodeKey, mutation] of nodeMutations) { - const bookNode = $getNodeByKey(nodeKey); - if (bookNode && $isBookNode(bookNode) && mutation === "created") { - $moveCursorToVerseStart(chapterNum, verseNum, hasCursorMovedRef); - } - } - }, - { tag: CURSOR_CHANGE_TAG }, - ); - }, - { skipInitialization: true }, - ), - [editor, chapterNum, verseNum], - ); - - // Scripture Reference changed - useEffect(() => { - if (hasSelectionChangedRef.current) hasSelectionChangedRef.current = false; - else { - editor.update(() => $moveCursorToVerseStart(chapterNum, verseNum, hasCursorMovedRef), { - tag: CURSOR_CHANGE_TAG, - }); - } - }, [editor, chapterNum, verseNum]); - - // Selection changed - useEffect( - () => - editor.registerCommand( - SELECTION_CHANGE_COMMAND, - () => { - if (hasCursorMovedRef.current) hasCursorMovedRef.current = false; - else { - $findAndSetChapterAndVerse( - book, - chapterNum, - verseNum, - onScrRefChange, - hasSelectionChangedRef, - ); - } - return false; - }, - COMMAND_PRIORITY_LOW, - ), - [editor, book, chapterNum, verseNum, onScrRefChange], - ); - - editor.registerUpdateListener(({ editorState }) => { - $getBookCode(editorState, onScrRefChange, scrRef); - }); - - return null; -} - -function $moveCursorToVerseStart( - chapterNum: number, - verseNum: number, - hasCursorMovedRef: MutableRefObject, -) { - const startNode = getSelectionStartNode($getSelection()); - const selectedVerse = $findThisVerse(startNode)?.getNumber(); - if (isVerseRange(selectedVerse) && isVerseInRange(verseNum, selectedVerse)) return; - - const children = $getRoot().getChildren(); - const chapterNode = $findChapter(children, chapterNum); - const nodesInChapter = removeNodesBeforeNode(children, chapterNode); - const nextChapterNode = $findNextChapter(nodesInChapter, !!chapterNode); - if ((nextChapterNode && !chapterNode) || !chapterNode) return; - - removeNodeAndAfter(nodesInChapter, nextChapterNode); - const verseOrParaNode = $findVerseOrPara(nodesInChapter, verseNum); - if (!verseOrParaNode) return; - - if ($isParaNode(verseOrParaNode)) { - const firstChild = verseOrParaNode.getFirstChild(); - if ($isTextNode(firstChild)) firstChild.select(0, 0); - else verseOrParaNode.select(0, 0); - } else verseOrParaNode.selectNext(0, 0); - hasCursorMovedRef.current = true; -} - -function $findAndSetChapterAndVerse( - book: string, - chapterNum: number, - verseNum: number, - onScrRefChange: (scrRef: ScriptureReference) => void, - hasSelectionChangedRef: MutableRefObject, -) { - const startNode = getSelectionStartNode($getSelection()); - if (!startNode) return; - - const chapterNode = $findThisChapter(startNode); - const selectedChapterNum = parseInt(chapterNode?.getNumber() ?? "1", 10); - const verseNode = $findThisVerse(startNode); - const verse = verseNode?.getNumber(); - // For verse ranges this returns the first number. - const selectedVerseNum = parseInt(verse ?? "0", 10); - // Check if the requested verse is within the current verse range - const isVerseInCurrentRange = verse - ? isVerseInRange(verseNum, verse) - : verseNum === selectedVerseNum; - hasSelectionChangedRef.current = !!( - (chapterNode && selectedChapterNum !== chapterNum) || - !isVerseInCurrentRange - ); - if (hasSelectionChangedRef.current) { - const scrRef: ScriptureReference = { - book, - chapterNum: selectedChapterNum, - verseNum: selectedVerseNum, - }; - if (verse != null && selectedVerseNum.toString() !== verse) scrRef.verse = verse; - onScrRefChange(scrRef); - } -} - -const $getBookCode = ( - editorState: EditorState, - onScrRefChange: (scrRef: ScriptureReference) => void, - currentScrRef: ScriptureReference, -) => { - editorState.read(() => { - const root = $getRoot(); - let node = root.getFirstChild(); - - while (node !== null) { - if ($isBookNode(node)) { - const bookNode = node; - onScrRefChange({ - ...currentScrRef, - book: bookNode.__code, - }); - break; - } - node = node.getNextSibling(); - } - }); -}; diff --git a/packages/scribe/src/editor/plugins/UsfmNodesMenuPlugin.tsx b/packages/scribe/src/editor/plugins/UsfmNodesMenuPlugin.tsx deleted file mode 100644 index 4cc230d9..00000000 --- a/packages/scribe/src/editor/plugins/UsfmNodesMenuPlugin.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { GetMarkerAction, ScriptureReference } from "shared"; -import useUsfmMarkersForMenu from "@/hooks/useUsfmMarkersForMenu"; -import NodesMenu from "@/components/NodesMenu/NodesMenu"; - -export default function UsfmNodesMenuPlugin({ - trigger, - scriptureReference, - contextMarker, - getMarkerAction, - autoNumbering, -}: { - trigger: string; - scriptureReference: ScriptureReference; - contextMarker: string | undefined; - getMarkerAction: GetMarkerAction; - autoNumbering?: boolean; -}) { - const { markersMenuItems } = useUsfmMarkersForMenu({ - scriptureReference, - contextMarker, - getMarkerAction, - autoNumbering, - }); - - return ; -} diff --git a/packages/scribe/src/editor/plugins/UsjNodesMenuPlugin.tsx b/packages/scribe/src/editor/plugins/UsjNodesMenuPlugin.tsx deleted file mode 100644 index 3c6fec38..00000000 --- a/packages/scribe/src/editor/plugins/UsjNodesMenuPlugin.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { $dfs, DFSNode, mergeRegister } from "@lexical/utils"; -import { - $getNodeByKey, - $getRoot, - $getSelection, - $isRangeSelection, - COMMAND_PRIORITY_LOW, - LexicalEditor, - SELECTION_CHANGE_COMMAND, -} from "lexical"; -import { useEffect, useMemo, useState } from "react"; -import { - $findNextChapter, - $findThisChapter, - $getCommonAncestorCompatible, - getNextVerse, - removeNodesBeforeNode, -} from "shared"; -import { $isVerseNode, VerseNode } from "shared"; -import { GetMarkerAction, ScriptureReference } from "shared"; -import { $isImmutableVerseNode, $isReactNodeWithMarker, ImmutableVerseNode } from "shared-react"; -import UsfmNodesMenuPlugin from "./UsfmNodesMenuPlugin"; - -type DfsVerseNode = Omit & { node: VerseNode | ImmutableVerseNode }; - -export default function UsjNodesMenuPlugin({ - trigger, - scrRef, - getMarkerAction, - autoNumbering = true, -}: { - trigger: string; - scrRef: ScriptureReference; - getMarkerAction: GetMarkerAction; - autoNumbering: boolean; -}) { - const { book, chapterNum, verseNum, verse } = scrRef; - // This is intentionally missing the `scrRef` dependency to ensure property changes are updated. - // eslint-disable-next-line react-hooks/exhaustive-deps - const scriptureReference = useMemo(() => scrRef, [book, chapterNum, verseNum, verse]); - - const [editor] = useLexicalComposerContext(); - const [contextMarker] = useContextMarker(editor); - - // Only register verse renumbering if autoNumbering is enabled - useVerseCreated(editor, autoNumbering); - - return ( - - ); -} - -function useContextMarker(editor: LexicalEditor) { - const [contextMarker, setContextMarker] = useState(); - useEffect( - () => - editor.registerCommand( - SELECTION_CHANGE_COMMAND, - () => { - editor.read(() => { - const selection = $getSelection(); - if (!$isRangeSelection(selection)) { - setContextMarker(undefined); - return; - } - - const startNode = $getNodeByKey(selection.anchor.key); - const endNode = $getNodeByKey(selection.focus.key); - if (!startNode || !endNode) { - setContextMarker(undefined); - return; - } - - const contextNode = $getCommonAncestorCompatible(startNode, endNode); - if (!contextNode || !$isReactNodeWithMarker(contextNode)) { - setContextMarker(undefined); - return; - } - - setContextMarker(contextNode.getMarker()); - }); - return false; - }, - COMMAND_PRIORITY_LOW, - ), - [editor], - ); - return [contextMarker]; -} - -/** - * Extracts the verse range and/or segments from a given verse string. - * - * The verse string can be in the format: - * - "1" (single verse) - * - "1a" (single verse segment) - * - "1-2" (verse range) - * - "1a-2b" (verse range with segments) - * - * @param verse - The verse string to extract the range from. - * @returns An array containing the matched groups or null if no match is found. - */ -function getVerseRangeSegment(verse: string) { - return RegExp(/(\d+)([a-zA-Z]+)?(-(\d+)([a-zA-Z]+)?)?/).exec(verse); -} - -/** - * Renumber all the verse numbers after the inserted verse to keep them sequential. - * @param insertedNode - Inserted verse node. - */ -function $renumberAllVerses(insertedNode: VerseNode | ImmutableVerseNode) { - const children = $getRoot().getChildren(); - const chapterNode = $findThisChapter(insertedNode); - const nodesInChapter = removeNodesBeforeNode(children, chapterNode); - const nextChapterNode = $findNextChapter(nodesInChapter, !!chapterNode); - const allVerseNodes = $dfs(chapterNode, nextChapterNode).filter( - (dfsNode): dfsNode is DfsVerseNode => - $isImmutableVerseNode(dfsNode.node) || $isVerseNode(dfsNode.node), - ); - // find the index of the inserted node in the DFS result - const insertedNodeKey = insertedNode.getKey(); - const nodeIndex = allVerseNodes.findIndex(({ node }) => node.getKey() === insertedNodeKey); - // all verse nodes that require renumbering - const verseNodes = allVerseNodes.slice(nodeIndex + 1); - - // renumber for each verse - let verseNum = parseInt(insertedNode.getNumber()); - verseNodes.forEach(({ node }) => { - const nodeVerse = node.getNumber(); - const nodeVerseNum = parseInt(nodeVerse); - if (nodeVerseNum > verseNum) return; - - const startVerse = getNextVerse(nodeVerseNum, undefined); - const nodeVerseSegment = getVerseRangeSegment(nodeVerse); - const isRange = !!nodeVerseSegment?.[3]; - const startVerseSegmentChar = nodeVerseSegment?.[2] ?? ""; - const endVerseSegmentChar = nodeVerseSegment?.[5] ?? ""; - const endVerse = isRange ? getNextVerse(parseInt(nodeVerseSegment[4]), undefined) : ""; - let tail = `${startVerseSegmentChar}`; - tail += isRange ? `-${endVerse}${endVerseSegmentChar}` : ""; - node.setNumber(`${startVerse}${tail}`); - verseNum = parseInt(isRange ? endVerse : startVerse); - }); -} - -function useVerseCreated(editor: LexicalEditor, autoNumbering: boolean) { - useEffect(() => { - if (!editor.hasNodes([VerseNode, ImmutableVerseNode])) { - throw new Error( - "UsjNodesMenuPlugin: VerseNode or ImmutableVerseNode not registered on editor!", - ); - } - - // Only register renumbering if autoNumbering is enabled - if (!autoNumbering) { - return; - } - - // Re-generate all verse numbers when a verse is added. - return mergeRegister( - editor.registerMutationListener(ImmutableVerseNode, (nodeMutations) => { - editor.update( - () => { - for (const [nodeKey, mutation] of nodeMutations) { - const node = $getNodeByKey(nodeKey); - if (mutation === "created" && $isImmutableVerseNode(node)) $renumberAllVerses(node); - } - }, - { tag: "history-merge" }, - ); - }), - editor.registerMutationListener(VerseNode, (nodeMutations) => { - editor.update( - () => { - for (const [nodeKey, mutation] of nodeMutations) { - const node = $getNodeByKey(nodeKey); - if (mutation === "created" && $isVerseNode(node)) $renumberAllVerses(node); - } - }, - { tag: "history-merge" }, - ); - }), - ); - }, [editor, autoNumbering]); -} diff --git a/packages/scribe/src/editor/plugins/useModifiedMarkersForMenu.ts b/packages/scribe/src/editor/plugins/useModifiedMarkersForMenu.ts deleted file mode 100644 index 9a287246..00000000 --- a/packages/scribe/src/editor/plugins/useModifiedMarkersForMenu.ts +++ /dev/null @@ -1,65 +0,0 @@ -// import { LexicalEditor } from "lexical"; -// import { useMemo } from "react"; -// import { GetMarkerAction, ScriptureReference, getMarker } from "shared"; - -// export default function useModifiedMarkersForMenu({ -// editor, -// scriptureReference, -// contextMarker, -// getMarkerAction, -// autoNumbering, -// }: { -// editor: LexicalEditor; -// scriptureReference: ScriptureReference; -// contextMarker: string | undefined; -// getMarkerAction: GetMarkerAction; -// autoNumbering: boolean; -// }) { -// const markersMenuItems = useMemo(() => { -// if (!contextMarker || !scriptureReference) return; -// const marker = getMarker(contextMarker); -// if (!marker?.children) return; - -// return Object.values(marker.children).flatMap((markers) => -// markers.map((marker) => { -// const markerData = getMarker(marker); -// const { action } = getMarkerAction(marker, markerData); - -// // Special handling for chapter and verse markers when autoNumbering is disabled -// const requiresInput = !autoNumbering && (marker === "c" || marker === "v"); - -// // Base menu item -// const menuItem = { -// name: marker, -// label: marker, -// description: markerData?.description ?? "", -// action: (editor: LexicalEditor, value: number) => { -// action({ editor, reference: scriptureReference }); -// }, -// // Add a custom action method to handle user input when manual numbering is used -// customActionWithValue: (editor: LexicalEditor, value: number) => { -// // Create a modified reference with the user-provided value -// const modifiedReference = { ...scriptureReference }; - -// if (marker === "c") { -// modifiedReference.chapterNum = value; -// } else if (marker === "v") { -// modifiedReference.verseNum = value; -// // modifiedReference.verse = value; -// } - -// // Call the action with the modified reference -// action({ -// editor, -// reference: modifiedReference, -// }); -// }, -// }; - -// return menuItem; -// }), -// ); -// }, [editor, contextMarker, scriptureReference, autoNumbering]); - -// return { markersMenuItems }; -// } diff --git a/packages/scribe/src/editor/plugins/view-mode.model.ts b/packages/scribe/src/editor/plugins/view-mode.model.ts deleted file mode 100644 index d04949fa..00000000 --- a/packages/scribe/src/editor/plugins/view-mode.model.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type ViewNameKey = keyof typeof viewModeToViewNames; -export type ViewMode = ViewNameKey; - -export const formattedViewMode = "formatted"; -export const unformattedViewMode = "unformatted"; -export const viewModeToViewNames = { - [formattedViewMode]: "Formatted", - [unformattedViewMode]: "Unformatted", -}; diff --git a/packages/scribe/src/editor/themes/editor-theme.ts b/packages/scribe/src/editor/themes/editor-theme.ts deleted file mode 100644 index 9485818f..00000000 --- a/packages/scribe/src/editor/themes/editor-theme.ts +++ /dev/null @@ -1,37 +0,0 @@ -const editorTheme = { - chapter: "chapter", - verse: "verse", - char: "char", - para: "para", - placeholder: "editor-placeholder", - paragraph: "editor-paragraph", - quote: "editor-quote", - heading: { - h1: "editor-heading-h1", - h2: "editor-heading-h2", - h3: "editor-heading-h3", - h4: "editor-heading-h4", - h5: "editor-heading-h5", - }, - list: { - nested: { - listitem: "editor-nested-listitem", - }, - ol: "editor-list-ol", - ul: "editor-list-ul", - listitem: "editor-listitem", - }, - image: "editor-image", - link: "editor-link", - text: { - bold: "editor-text-bold", - italic: "editor-text-italic", - overflowed: "editor-text-overflowed", - hashtag: "editor-text-hashtag", - underline: "editor-text-underline", - strikethrough: "editor-text-strikethrough", - underlineStrikethrough: "editor-text-underlineStrikethrough", - }, -}; - -export default editorTheme; diff --git a/packages/scribe/src/editor/utils/insertFunctions.ts b/packages/scribe/src/editor/utils/insertFunctions.ts deleted file mode 100644 index 7355568f..00000000 --- a/packages/scribe/src/editor/utils/insertFunctions.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - LexicalEditor, - $getSelection, - $isRangeSelection, - $createTextNode, - RangeSelection, - TextNode, - $insertNodes, - $applyNodeReplacement, - $parseSerializedNode, - SerializedLexicalNode, - $isRootOrShadowRoot, - $createParagraphNode, -} from "lexical"; -import { recurseNodes as recurseSerializedNodes } from "../adaptors/editor-usj.adaptor"; -import { recurseNodes as recurseEditorNodes } from "../adaptors/usj-editor.adaptor"; -import { emptyFootnote, Footnote, Char } from "../nodes/emptyFootNote"; -import { $wrapNodeInElement } from "@lexical/utils"; -import { $createChapterNode, $createVerseNode } from "shared"; - -function moveToEndOfNode(selection: RangeSelection, node: TextNode) { - const anchorOffset = node.getTextContentSize(); - selection.anchor.set(node.getKey(), anchorOffset, "text"); - selection.focus.set(node.getKey(), anchorOffset, "text"); -} -export const $createNodeFromSerializedNode = (serializedNode: SerializedLexicalNode) => { - return $applyNodeReplacement($parseSerializedNode(serializedNode)); -}; -export const $insertUsfmNode = (serializedNode: SerializedLexicalNode) => { - const newNode = $createNodeFromSerializedNode(serializedNode); - console.log("===>", { newNode }); - $insertNodes([newNode]); - - if ($isRootOrShadowRoot(newNode.getParentOrThrow())) { - $wrapNodeInElement(newNode, $createParagraphNode).selectEnd(); - } - - return newNode; -}; -export function insertChapterNode({ - editor, - inputValues: { chapter }, -}: { - editor: LexicalEditor; - inputValues: { chapter: string }; -}): void { - console.log({ chapter }); - const sid = "xx", - altnumber = "xx", - pubnumber = "xx", - text: string = chapter; - editor.update(() => { - const selection = $getSelection(); - if ($isRangeSelection(selection)) { - const chapterNode = $createChapterNode(chapter, sid, altnumber, pubnumber); - const textNode = $createTextNode(text); - chapterNode.append(textNode); - const spaceNode = $createTextNode(" "); - selection.insertNodes([chapterNode, spaceNode]); - moveToEndOfNode(selection, spaceNode); - } - }); -} - -export function insertVerseNode({ - editor, - inputValues: { verse }, -}: { - editor: LexicalEditor; - inputValues: { verse: string }; -}): void { - const sid = "xx", - altnumber = "xx", - pubnumber = "xx", - text: string = verse; - editor.update(() => { - const selection = $getSelection(); - if ($isRangeSelection(selection)) { - const verseNode = $createVerseNode(verse, text, sid, altnumber, pubnumber); - - const spaceNode = $createTextNode(" "); - selection.insertNodes([verseNode, spaceNode]); - moveToEndOfNode(selection, spaceNode); - } - }); -} -export interface InsertNoteNodeParams { - editor: LexicalEditor; - inputValues: { - fr?: string; - fqa?: string; - fq?: string; - ft?: string; - }; -} -type InputValues = InsertNoteNodeParams["inputValues"]; - -function deepCopy(obj: T): T { - return JSON.parse(JSON.stringify(obj)); -} -function updateOrAddNodes(footnote: Footnote, values: InputValues): Footnote { - const modifiedFootnote = deepCopy(footnote); - const newNodes: Char[] = []; - const note = modifiedFootnote.children[0].children.find((child) => child.type === "note"); - if (!note) return modifiedFootnote; - - Object.entries(values).forEach(([marker, text]) => { - const existingNode = note.children.find((child) => (child as Char).marker === marker); - if (existingNode) { - (existingNode as Char).text = text; - } else { - const newNode: Char = { - type: "char", - marker: marker, - text: text, - detail: 0, - format: 0, - mode: "normal", - style: "display: none", - version: 1, - }; - note.children.push(newNode); - newNodes.push(newNode); - } - }); - - return modifiedFootnote; -} - -export function insertFootNoteNode({ editor, inputValues }: InsertNoteNodeParams): void { - editor.update(() => { - const newFootnoteNode = updateOrAddNodes(emptyFootnote, inputValues); - console.log({ newFootnoteNode, emptyFootnote }); - const notecontent = recurseSerializedNodes(newFootnoteNode.children); - // const notecontent = recurseSerializedNodes(emptyFootnote.children); - console.log({ notecontent }); - const noteNode = recurseEditorNodes(notecontent); - console.log({ noteNode }); - $insertUsfmNode(noteNode[0]); - }); -} diff --git a/packages/scribe/src/hooks/use-deferred-state.hook.tsx b/packages/scribe/src/hooks/use-deferred-state.hook.tsx deleted file mode 100644 index 7e60fa49..00000000 --- a/packages/scribe/src/hooks/use-deferred-state.hook.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { deepEqual } from "fast-equals"; -import { Dispatch, SetStateAction, useEffect, useState } from "react"; - -type UseDeferredStateReturn = [T, T, Dispatch>]; - -/** - * `useDeferredState` is a custom hook that lets you defer updating part of the UI. - * The `deferred` value is only updated if the changed `incoming` value is different than the - * `modified` value. - * @param incoming - Incoming value to watch for changes. - * @returns Returns the `deferred` value, the `modified` value, and a function to update the - * modified value. - */ -export default function useDeferredState(incoming: T): UseDeferredStateReturn { - const [deferred, setDeferred] = useState(incoming); - const [modified, setModified] = useState(incoming); - - useEffect(() => { - if (!deepEqual(incoming, modified)) setDeferred(incoming); - // Intentionally ignoring changes to `modified`. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [incoming]); - - return [deferred, modified, setModified]; -} diff --git a/packages/scribe/src/hooks/useCursorCoords.tsx b/packages/scribe/src/hooks/useCursorCoords.tsx deleted file mode 100644 index 9aa40da0..00000000 --- a/packages/scribe/src/hooks/useCursorCoords.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { RefObject, useEffect } from "react"; -import { useFloatingPosition } from "./useFloatingPosition"; - -export default function useCursorCoords({ - isOpen, - floatingBoxRef, -}: { - isOpen: boolean; - floatingBoxRef: RefObject; -}) { - const { coords, updatePosition, cleanup, placement } = useFloatingPosition(); - - useEffect(() => { - if (!isOpen || !floatingBoxRef.current) { - cleanup(); - return undefined; - } - - const domRange = window.getSelection()?.getRangeAt(0); - if (domRange) { - updatePosition(domRange, floatingBoxRef.current); - return cleanup; - } - return undefined; - }, [cleanup, isOpen, floatingBoxRef, updatePosition]); - - return { coords, placement }; -} diff --git a/packages/scribe/src/hooks/useFloatingPosition.tsx b/packages/scribe/src/hooks/useFloatingPosition.tsx deleted file mode 100644 index c2574a31..00000000 --- a/packages/scribe/src/hooks/useFloatingPosition.tsx +++ /dev/null @@ -1,50 +0,0 @@ -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(); - 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 }; -} diff --git a/packages/scribe/src/hooks/useLexicalMenuNavigation.ts b/packages/scribe/src/hooks/useLexicalMenuNavigation.ts deleted file mode 100644 index 12a6db7c..00000000 --- a/packages/scribe/src/hooks/useLexicalMenuNavigation.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect } from "react"; -import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; -import { useMenuActions } from "@/components/NodesMenu/Menu/useMenuActions"; -import { COMMAND_PRIORITY_HIGH, KEY_DOWN_COMMAND } from "lexical"; - -export const useLexicalMenuNavigation = () => { - const menu = useMenuActions(); - const [editor] = useLexicalComposerContext(); - - useEffect(() => { - const handleEvent = (event: KeyboardEvent) => { - const actions: { [key: string]: () => void } = { - ArrowDown: () => menu?.moveDown(), - ArrowUp: () => menu?.moveUp(), - Enter: () => menu?.select(), - Tab: () => menu?.select(), - }; - - const action = actions[event.key]; - if (action) { - action(); - event.preventDefault(); - event.stopPropagation(); - return true; - } - - return false; - }; - - return editor.registerCommand(KEY_DOWN_COMMAND, handleEvent, COMMAND_PRIORITY_HIGH); - }, [editor, menu]); -}; diff --git a/packages/scribe/src/hooks/useModal.tsx b/packages/scribe/src/hooks/useModal.tsx deleted file mode 100644 index 46ac538e..00000000 --- a/packages/scribe/src/hooks/useModal.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useCallback, useMemo, useState, ReactElement } from "react"; - -import Modal from "../components/Modal/Modal"; - -export default function useModal(): [ - ReactElement | null, - (title: string, showModal: (onClose: () => void) => ReactElement) => void, -] { - const [modalContent, setModalContent] = useState(null); - - const onClose = useCallback(() => { - setModalContent(null); - }, []); - - const modal = useMemo(() => { - if (modalContent === null) { - return null; - } - const { title, content, closeOnClickOutside } = modalContent; - return ( - - {content} - - ); - }, [modalContent, onClose]); - - const showModal = useCallback( - ( - title: string, - getContent: (onClose: () => void) => ReactElement, - closeOnClickOutside = false, - ) => { - setModalContent({ - closeOnClickOutside, - content: getContent(onClose), - title, - }); - }, - [onClose], - ); - - return [modal, showModal]; -} diff --git a/packages/scribe/src/hooks/usePointerInteractions.tsx b/packages/scribe/src/hooks/usePointerInteractions.tsx deleted file mode 100644 index 70901a0e..00000000 --- a/packages/scribe/src/hooks/usePointerInteractions.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect, useState } from "react"; - -export const usePointerInteractions = () => { - const [isPointerDown, setIsPointerDown] = useState(false); - const [isPointerReleased, setIsPointerReleased] = useState(false); - - const handlePointerDown = () => { - setIsPointerDown(true); - setIsPointerReleased(false); - }; - - const handlePointerUp = () => { - setIsPointerDown(false); - setIsPointerReleased(true); - }; - - useEffect(() => { - document.addEventListener("pointerdown", handlePointerDown); - document.addEventListener("pointerup", handlePointerUp); - - return () => { - document.removeEventListener("pointerdown", handlePointerDown); - document.removeEventListener("pointerup", handlePointerUp); - }; - }, []); - - return { isPointerDown, isPointerReleased }; -}; diff --git a/packages/scribe/src/hooks/useUsfm2Usj.ts b/packages/scribe/src/hooks/useUsfm2Usj.ts deleted file mode 100644 index 288832a1..00000000 --- a/packages/scribe/src/hooks/useUsfm2Usj.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Usj } from "@eten-tech-foundation/scripture-utilities"; -import { useEffect, useState } from "react"; -import USFMParser from "sj-usfm-grammar"; - -export const useUsfm2Usj = () => { - const [usfm, setUsfm] = useState(); - const [usj, setUsj] = useState(); - - const parseUSFM = async (usfm: string) => { - await USFMParser.init(); - const usfmParser = new USFMParser(); - const usj = usfmParser.usfmToUsj(usfm); - if (usj) setUsj(usj); - }; - - useEffect(() => { - import("../editor/data/tit.usfm").then((data) => { - setUsfm(data.default); - }); - }, []); - - useEffect(() => { - (async () => usfm && parseUSFM(usfm))(); - }, [usfm]); - - return { usj }; -}; diff --git a/packages/scribe/src/hooks/useUsfmMarkersForMenu.ts b/packages/scribe/src/hooks/useUsfmMarkersForMenu.ts deleted file mode 100644 index 557bbb05..00000000 --- a/packages/scribe/src/hooks/useUsfmMarkersForMenu.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { LexicalEditor } from "lexical"; -import { useMemo } from "react"; -import { getMarker, GetMarkerAction, ScriptureReference } from "shared"; - -// getMarker() takes a marker string and gets its data from a usfm markers map object that is merged with overwrites that fit the PERF editor context. -// getMarkerAction() returns a function to generate a LexicalNode and insert it in the editor, this lexical node is a custom node made for the PERF editor -// NOTE: You can create your own typeahead plugin by creating your own getMarker() and getMarkerAction() functions adapted to your editor needs. -export default function useUsfmMarkersForMenu({ - scriptureReference, - contextMarker, - getMarkerAction, - autoNumbering, -}: { - scriptureReference: ScriptureReference; - contextMarker: string | undefined; - getMarkerAction: GetMarkerAction; - autoNumbering?: boolean; -}) { - const markersMenuItems = useMemo(() => { - if (!contextMarker || !scriptureReference) return; - const marker = getMarker(contextMarker); - if (!marker?.children) return; - - return Object.values(marker.children).flatMap((markers) => - markers.map((marker) => { - const markerData = getMarker(marker); - const { action } = getMarkerAction(marker, markerData); - return { - name: marker, - label: marker, - description: markerData?.description ?? "", - action: ({ - editor, - newVerseRChapterNum, - noteText, - }: { - editor: LexicalEditor; - newVerseRChapterNum?: number; - noteText?: string; - }) => { - action({ - editor, - reference: scriptureReference, - autoNumbering, - newVerseRChapterNum, - noteText, - }); - }, - }; - }), - ); - }, [autoNumbering, contextMarker, getMarkerAction, scriptureReference]); - - return { markersMenuItems }; -} diff --git a/packages/scribe/src/hooks/usj2Usfm.ts b/packages/scribe/src/hooks/usj2Usfm.ts deleted file mode 100644 index 7a316725..00000000 --- a/packages/scribe/src/hooks/usj2Usfm.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Usj } from "@eten-tech-foundation/scripture-utilities"; -import USFMParser from "sj-usfm-grammar"; - -export const Usj2Usfm = async (usj: Usj): Promise => { - // let usfm: string; - const parseUsj = async (usj: Usj): Promise => { - await USFMParser.init(); - const usfmParser = new USFMParser(); - return usfmParser.usjToUsfm(usj); - }; - const usfm: string = usj ? await parseUsj(usj) : ""; - return usfm; -}; - -export const usfm2Usj = async (usfm: string): Promise => { - // let usfm: string; - const parseUsfm = async (usfm: string): Promise => { - await USFMParser.init(); - const usfmParser = new USFMParser(); - return usfmParser.usfmToUsj(usfm); - }; - const usj: Usj = usfm ? await parseUsfm(usfm) : ({} as Usj); - return usj; -}; diff --git a/packages/scribe/src/index.ts b/packages/scribe/src/index.ts deleted file mode 100644 index b1ef582f..00000000 --- a/packages/scribe/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { default as Editor, type EditorRef, type EditorOptions } from "./editor/Editor"; -export type { UsjNodeOptions, ViewMode, ViewOptions } from "shared-react"; -export { - getDefaultViewMode, - getDefaultViewOptions, - getViewMode, - getViewOptions, -} from "shared-react"; diff --git a/packages/scribe/src/main.tsx b/packages/scribe/src/main.tsx deleted file mode 100644 index e1be1c88..00000000 --- a/packages/scribe/src/main.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { StrictMode } from "react"; -import * as ReactDOM from "react-dom/client"; -import "./styles/index.css"; -import App from "./App"; - -const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); - -root.render( - - - , -); diff --git a/packages/scribe/src/styles/App.css b/packages/scribe/src/styles/App.css deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/scribe/src/styles/index.css b/packages/scribe/src/styles/index.css deleted file mode 100644 index 0ec07bbc..00000000 --- a/packages/scribe/src/styles/index.css +++ /dev/null @@ -1,2567 +0,0 @@ -@import "tailwindcss"; - -@theme { - --color-primary: #ff5500; - --color-primary-50: #e4f2ff; - --color-secondary: #151515; - --color-success: #05c715; - --color-error: #ff1500; - --color-validation: #ffe5e5; - --color-light: #e4f1ff; - --color-dark: #333333; - --height-editor: 98vh; - --height-reference: calc((-9.5rem + 100vh) / 2); -} - -/* Editor */ - -.editor-input > p { - direction: inherit; - margin-top: 0; - margin-bottom: 0; - line-height: 1.5; -} - -/* USJ Nodes */ - -.leadingFloat { - float: start; -} -.clearFloat { - clear: both; -} -.align_start { - text-align: start; -} -.align_center { - text-align: center; -} -.align_end { - text-align: end; -} -@font-face { - font-family: "Charis SIL"; - src: - local("Charis SIL"), local("Charis SIL Bold"), local("Charis SIL Bold Italic"), - local("Charis SIL Italic"); - font-weight: normal; -} -@font-face { - font-family: "Charis SIL"; - src: local("Charis SIL Bold"); - font-weight: bold; -} -@font-face { - font-family: "Charis SIL"; - src: local("Charis SIL Italic"); - font-style: italic; -} -@font-face { - font-family: "Charis SIL"; - src: local("Charis SIL Bold Italic"); - font-weight: bold; - font-style: italic; -} -.usfm.formatted-font { - font-family: "Charis SIL", "Times New Roman", "Liberation Serif", "DejaVu Serif", Georgia, serif; - font-size: 12pt; -} - -/* UnknownNode.createDOM no longer sets an inline `display: none`; hiding now comes from these - classes (mirrors the base rule in platform's usj-nodes.css). Without this rule the scribe - editor — which also registers UnknownNode via usjReactNodes — would render unknown markers - (e.g. \fig, tables, \ref) as raw visible content. */ -.unknown-block, -.unknown-inline { - display: none; -} - -/* BookNode */ - -.usfm_id { - display: none; -} -.formatted-font .usfm_id { - font-size: 100%; -} - -/* ChapterNode, ImmutableChapterNode */ - -.formatted-font .usfm_c { - @apply m-2 block break-before-column font-medium tracking-wider uppercase; -} -.text-spacing[dir="ltr"] .usfm_c { - @apply pl-4 text-left; -} -.text-spacing[dir="rtl"] .usfm_c { - @apply pr-4 text-right; -} - -.formatted-font .usfm_ca { - color: #007306; - font-size: 133%; - font-style: italic; -} - -.formatted-font .usfm_cp { - font-weight: bold; - color: #003380; - font-size: 150%; -} -.text-spacing .usfm_cp { - margin-bottom: 4pt; - margin-top: 8pt; -} - -/* VerseNode, ImmutableVerseNode */ - -.formatted-font .usfm_v { - @apply align-top text-xs tracking-wider text-cyan-500 uppercase; -} -.text-spacing[dir="ltr"] .usfm_v { - @apply text-left; -} -.text-spacing[dir="rtl"] .usfm_v { - @apply text-right; -} - -.formatted-font .usfm_va { - color: #007306; - font-size: 66%; - vertical-align: text-top; -} - -.formatted-font .usfm_vp { - color: #003380; - font-size: 66%; - vertical-align: text-top; -} - -/* ParaNode */ - -.formatted-font .usfm_usfm { - font-size: 100%; -} - -.formatted-font .usfm_ide { - font-size: 100%; - display: none; -} - -.formatted-font .usfm_h { - font-size: 100%; - display: none; -} - -.formatted-font .usfm_h1 { - font-size: 100%; -} - -.formatted-font .usfm_h2 { - font-size: 100%; -} - -.formatted-font .usfm_h3 { - font-size: 100%; -} - -.usfm_toc1 { - @apply hidden; -} -.formatted-font .usfm_toc1 { - @apply font-bold text-green-900 italic; -} -.text-spacing[dir="ltr"] .usfm_toc1 { - @apply ml-2 text-left; -} -.text-spacing[dir="rtl"] .usfm_toc1 { - @apply mr-2 text-right; -} - -.usfm_toc2 { - @apply hidden; -} -.formatted-font .usfm_toc2 { - @apply text-green-900 italic; -} -.text-spacing[dir="ltr"] .usfm_toc2 { - @apply ml-2 text-left; -} -.text-spacing[dir="rtl"] .usfm_toc2 { - @apply mr-2 text-right; -} - -.usfm_toc3 { - @apply hidden; -} -.formatted-font .usfm_toc3 { - @apply font-bold text-red-500 italic; -} -.text-spacing[dir="ltr"] .usfm_toc3 { - @apply ml-2 text-left; -} -.text-spacing[dir="rtl"] .usfm_toc3 { - @apply mr-2 text-right; -} - -.formatted-font .usfm_toca1 { - color: #8c8c8c; - font-size: 83%; - font-style: italic; -} - -.formatted-font .usfm_toca2 { - color: #8c8c8c; - font-size: 83%; - font-style: italic; -} - -.formatted-font .usfm_toca3 { - color: #8c8c8c; - font-size: 83%; - font-style: italic; -} - -.formatted-font .usfm_rem { - color: #003380; - font-size: 100%; -} - -.formatted-font .usfm_sts { - color: #003380; - font-size: 100%; -} - -.formatted-font .usfm_restore { - color: #003380; - font-size: 100%; -} - -.formatted-font .usfm_imt { - font-weight: bold; - font-size: 116%; -} -.text-spacing .usfm_imt { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_imt1 { - font-weight: bold; - font-size: 116%; -} -.text-spacing .usfm_imt1 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_imt2 { - font-size: 108%; - font-style: italic; -} -.text-spacing .usfm_imt2 { - text-align: center; - margin-bottom: 3pt; - margin-top: 6pt; -} - -.formatted-font .usfm_imt3 { - font-weight: bold; - font-size: 100%; -} -.text-spacing .usfm_imt3 { - text-align: center; - margin-bottom: 2pt; - margin-top: 2pt; -} - -.formatted-font .usfm_imt4 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_imt4 { - text-align: center; - margin-bottom: 2pt; - margin-top: 2pt; -} - -.formatted-font .usfm_imte { - font-weight: bold; - font-size: 166%; -} -.text-spacing .usfm_imte { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_imte1 { - font-weight: bold; - font-size: 166%; -} -.text-spacing .usfm_imte1 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_imte2 { - font-size: 133%; - font-style: italic; -} -.text-spacing .usfm_imte2 { - text-align: center; - margin-bottom: 2pt; -} - -.formatted-font .usfm_is { - font-weight: bold; - font-size: 116%; -} -.text-spacing .usfm_is { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_is1 { - font-weight: bold; - font-size: 116%; -} -.text-spacing .usfm_is1 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_is2 { - font-weight: bold; - font-size: 100%; -} -.text-spacing .usfm_is2 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_iot { - font-weight: bold; - font-size: 100%; -} -.text-spacing .usfm_iot { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_io { - font-size: 100%; -} -.text-spacing[dir="ltr"] .usfm_io { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_io { - margin-right: 10vw; -} - -.formatted-font .usfm_io1 { - font-size: 100%; -} -.text-spacing[dir="ltr"] .usfm_io1 { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_io1 { - margin-right: 10vw; -} - -.formatted-font .usfm_io2 { - font-size: 100%; -} -.text-spacing[dir="ltr"] .usfm_io2 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_io2 { - margin-right: 15vw; -} - -.formatted-font .usfm_io3 { - font-size: 100%; -} -.text-spacing[dir="ltr"] .usfm_io3 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_io3 { - margin-right: 20vw; -} - -.formatted-font .usfm_io4 { - font-size: 100%; -} -.text-spacing[dir="ltr"] .usfm_io4 { - margin-left: 25vw; -} -.text-spacing[dir="rtl"] .usfm_io4 { - margin-right: 25vw; -} - -.formatted-font .usfm_ior { - font-size: 100%; -} - -.formatted-font .usfm_ip { - font-size: 100%; -} -.text-spacing .usfm_ip { - text-indent: 2.5vw; -} - -.formatted-font .usfm_im { - font-size: 100%; -} - -.formatted-font .usfm_ipi { - font-size: 100%; -} -.text-spacing .usfm_ipi { - text-indent: 2.5vw; - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_imi { - font-size: 100%; -} -.text-spacing .usfm_imi { - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_ili { - font-size: 100%; -} -.text-spacing .usfm_ili { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_ili { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_ili { - margin-right: 10vw; -} - -.formatted-font .usfm_ili1 { - font-size: 100%; -} -.text-spacing .usfm_ili1 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_ili1 { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_ili1 { - margin-right: 10vw; -} - -.formatted-font .usfm_ili2 { - font-size: 100%; -} -.text-spacing .usfm_ili2 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_ili2 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_ili2 { - margin-right: 15vw; -} - -.formatted-font .usfm_ipq { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_ipq { - text-indent: 2.5vw; - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_imq { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_imq { - margin-left: 5vw; - margin-right: 5vw; -} - -.usfm_ipr { - text-align: end; -} -.formatted-font .usfm_ipr { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_ipr { - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_ib { - font-size: 83%; -} - -.formatted-font .usfm_iq { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_iq { - text-indent: -15vw; -} -.text-spacing[dir="ltr"] .usfm_iq { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_iq { - margin-right: 20vw; -} - -.formatted-font .usfm_iq1 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_iq1 { - text-indent: -15vw; -} -.text-spacing[dir="ltr"] .usfm_iq1 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_iq1 { - margin-right: 20vw; -} - -.formatted-font .usfm_iq2 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_iq2 { - text-indent: -10vw; -} -.text-spacing[dir="ltr"] .usfm_iq2 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_iq2 { - margin-right: 20vw; -} - -.formatted-font .usfm_iq3 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_iq3 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_iq3 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_iq3 { - margin-right: 20vw; -} - -.formatted-font .usfm_iex { - font-size: 100%; -} -.text-spacing .usfm_iex { - text-indent: 2.5vw; - margin-bottom: 4pt; - margin-top: 4pt; -} - -.formatted-font .usfm_iqt { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_ie { - font-size: 83%; -} - -.formatted-font .usfm_cl { - font-weight: bold; - font-size: 150%; -} -.text-spacing .usfm_cl { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_cd { - font-size: 91%; -} -.text-spacing .usfm_cd { - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_p { - font-size: 100%; -} -.text-spacing .usfm_p { - text-indent: 2.5vw; -} - -.formatted-font .usfm_m { - font-size: 100%; -} - -.formatted-font .usfm_po { - font-size: 100%; -} -.text-spacing .usfm_po { - text-indent: 2.5vw; - margin-bottom: 4pt; - margin-top: 4pt; -} - -.usfm_pr { - text-align: end; -} -.formatted-font .usfm_pr { - font-size: 100%; -} - -.usfm_cls { - text-align: end; -} -.formatted-font .usfm_cls { - font-size: 100%; -} - -.formatted-font .usfm_pmo { - font-size: 100%; -} -.text-spacing .usfm_pmo { - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_pm { - font-size: 100%; -} -.text-spacing .usfm_pm { - text-indent: 2.5vw; - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_pmc { - font-size: 100%; -} -.text-spacing .usfm_pmc { - margin-left: 5vw; - margin-right: 5vw; -} - -.usfm_pmr { - text-align: end; -} -.formatted-font .usfm_pmr { - font-size: 100%; -} -.text-spacing .usfm_pmr { - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_pi { - font-size: 100%; -} -.text-spacing .usfm_pi { - text-indent: 2.5vw; - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_pi1 { - font-size: 100%; -} -.text-spacing .usfm_pi1 { - text-indent: 2.5vw; - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_pi2 { - font-size: 100%; -} -.text-spacing .usfm_pi2 { - text-indent: 2.5vw; -} -.text-spacing[dir="ltr"] .usfm_pi2 { - margin-left: 10vw; - margin-right: 5vw; -} -.text-spacing[dir="rtl"] .usfm_pi2 { - margin-left: 5vw; - margin-right: 10vw; -} - -.formatted-font .usfm_pi3 { - font-size: 100%; -} -.text-spacing .usfm_pi3 { - text-indent: 2.5vw; -} -.text-spacing[dir="ltr"] .usfm_pi3 { - margin-left: 15vw; - margin-right: 5vw; -} -.text-spacing[dir="rtl"] .usfm_pi3 { - margin-left: 5vw; - margin-right: 15vw; -} - -.formatted-font .usfm_pc { - font-size: 100%; -} -.text-spacing .usfm_pc { - text-align: center; -} - -.formatted-font .usfm_mi { - font-size: 100%; -} -.text-spacing .usfm_mi { - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_nb { - font-size: 100%; -} - -.formatted-font .usfm_q { - font-size: 100%; -} -.text-spacing .usfm_q { - text-indent: -10vw; -} -.text-spacing[dir="ltr"] .usfm_q { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_q { - margin-right: 15vw; -} - -.formatted-font .usfm_q1 { - font-size: 100%; -} -.text-spacing .usfm_q1 { - text-indent: -10vw; -} -.text-spacing[dir="ltr"] .usfm_q1 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_q1 { - margin-right: 15vw; -} - -.formatted-font .usfm_q2 { - font-size: 100%; -} -.text-spacing .usfm_q2 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_q2 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_q2 { - margin-right: 15vw; -} - -.formatted-font .usfm_q3 { - font-size: 100%; -} -.text-spacing .usfm_q3 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_q3 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_q3 { - margin-right: 15vw; -} - -.formatted-font .usfm_q4 { - font-size: 100%; -} -.text-spacing .usfm_q4 { - text-indent: -2.5vw; -} -.text-spacing[dir="ltr"] .usfm_q4 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_q4 { - margin-right: 15vw; -} - -.formatted-font .usfm_qc { - font-size: 100%; -} -.text-spacing .usfm_qc { - text-align: center; -} - -.usfm_qr { - text-align: end; -} -.formatted-font .usfm_qr { - font-size: 100%; -} - -.formatted-font .usfm_qa { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_qm { - font-size: 100%; -} -.text-spacing .usfm_qm { - text-indent: -15vw; -} -.text-spacing[dir="ltr"] .usfm_qm { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_qm { - margin-right: 20vw; -} - -.formatted-font .usfm_qm1 { - font-size: 100%; -} -.text-spacing .usfm_qm1 { - text-indent: -15vw; -} -.text-spacing[dir="ltr"] .usfm_qm1 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_qm1 { - margin-right: 20vw; -} - -.formatted-font .usfm_qm2 { - font-size: 100%; -} -.text-spacing .usfm_qm2 { - text-indent: -10vw; -} -.text-spacing[dir="ltr"] .usfm_qm2 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_qm2 { - margin-right: 20vw; -} - -.formatted-font .usfm_qm3 { - font-size: 100%; -} -.text-spacing .usfm_qm3 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_qm3 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_qm3 { - margin-right: 20vw; -} - -.formatted-font .usfm_qd { - font-size: 100%; - font-style: italic; -} -.text-spacing[dir="ltr"] .usfm_qd { - margin-left: 5vw; -} -.text-spacing[dir="rtl"] .usfm_qd { - margin-right: 5vw; -} - -.formatted-font .usfm_b { - font-size: 83%; -} - -.formatted-font .usfm_mt { - font-weight: bold; - font-size: 166%; -} -.text-spacing .usfm_mt { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_mt1 { - @apply text-2xl font-bold; -} -.text-spacing .usfm_mt1 { - @apply my-2 text-center; -} - -.formatted-font .usfm_mt2 { - font-size: 133%; - font-style: italic; -} -.text-spacing .usfm_mt2 { - text-align: center; - margin-bottom: 2pt; -} - -.formatted-font .usfm_mt3 { - font-weight: bold; - font-size: 133%; -} -.text-spacing .usfm_mt3 { - text-align: center; - margin-bottom: 2pt; - margin-top: 2pt; -} - -.formatted-font .usfm_mt4 { - font-size: 100%; -} -.text-spacing .usfm_mt4 { - text-align: center; - margin-bottom: 2pt; - margin-top: 2pt; -} - -.formatted-font .usfm_mte { - font-weight: bold; - font-size: 166%; -} -.text-spacing .usfm_mte { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_mte1 { - font-weight: bold; - font-size: 166%; -} -.text-spacing .usfm_mte1 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_mte2 { - font-size: 133%; - font-style: italic; -} -.text-spacing .usfm_mte2 { - text-align: center; - margin-bottom: 2pt; -} - -.formatted-font .usfm_ms { - font-weight: bold; - font-size: 116%; -} -.text-spacing .usfm_ms { - text-align: center; - margin-bottom: 4pt; - margin-top: 16pt; -} - -.formatted-font .usfm_ms1 { - @apply text-lg; -} -.text-spacing .usfm_ms1 { - @apply m-2 text-center; -} - -.formatted-font .usfm_ms2 { - font-weight: bold; - font-size: 116%; -} -.text-spacing .usfm_ms2 { - text-align: center; - margin-bottom: 4pt; - margin-top: 16pt; -} - -.formatted-font .usfm_ms3 { - font-size: 116%; - font-style: italic; -} -.text-spacing .usfm_ms3 { - text-align: center; - margin-bottom: 4pt; - margin-top: 16pt; -} - -.formatted-font .usfm_mr { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_mr { - text-align: center; - margin-bottom: 4pt; -} - -.formatted-font .usfm_s { - font-weight: bold; - font-size: 100%; -} -.text-spacing .usfm_s { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_s1 { - font-weight: bold; - font-size: 100%; -} -.text-spacing .usfm_s1 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_s2 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_s2 { - text-align: center; - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_s3 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_s3 { - margin-bottom: 3pt; - margin-top: 6pt; -} - -.formatted-font .usfm_s4 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_s4 { - margin-bottom: 3pt; - margin-top: 6pt; -} - -.formatted-font .usfm_sr { - font-weight: bold; - font-size: 100%; -} -.text-spacing .usfm_sr { - text-align: center; - margin-bottom: 4pt; -} - -.formatted-font .usfm_r { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_r { - text-align: center; - margin-bottom: 4pt; -} - -.formatted-font .usfm_sp { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_sp { - margin-bottom: 4pt; - margin-top: 8pt; -} - -.formatted-font .usfm_d { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_d { - text-align: center; - margin-bottom: 4pt; - margin-top: 4pt; -} - -.text-spacing .usfm_sd { - margin-bottom: 24pt; - margin-top: 24pt; -} - -.text-spacing .usfm_sd1 { - margin-bottom: 24pt; - margin-top: 24pt; -} - -.text-spacing .usfm_sd2 { - margin-bottom: 18pt; - margin-top: 18pt; -} - -.text-spacing .usfm_sd3 { - margin-bottom: 12pt; - margin-top: 12pt; -} - -.text-spacing .usfm_sd4 { - margin-bottom: 8pt; - margin-top: 8pt; -} - -.formatted-font .usfm_lh { - font-size: 100%; -} -.text-spacing .usfm_lh { - text-indent: 2.5vw; -} - -.formatted-font .usfm_li { - font-size: 100%; -} -.text-spacing .usfm_li { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_li { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_li { - margin-right: 10vw; -} - -.formatted-font .usfm_li1 { - font-size: 100%; -} -.text-spacing .usfm_li1 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_li1 { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_li1 { - margin-right: 10vw; -} - -.formatted-font .usfm_li2 { - font-size: 100%; -} -.text-spacing .usfm_li2 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_li2 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_li2 { - margin-right: 15vw; -} - -.formatted-font .usfm_li3 { - font-size: 100%; -} -.text-spacing .usfm_li3 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_li3 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_li3 { - margin-right: 20vw; -} - -.formatted-font .usfm_li4 { - font-size: 100%; -} -.text-spacing .usfm_li4 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_li4 { - margin-left: 25vw; -} -.text-spacing[dir="rtl"] .usfm_li4 { - margin-right: 25vw; -} - -.formatted-font .usfm_lf { - font-size: 100%; -} - -.formatted-font .usfm_lim { - font-size: 100%; -} -.text-spacing .usfm_lim { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_lim { - margin-left: 15vw; - margin-right: 5vw; -} -.text-spacing[dir="rtl"] .usfm_lim { - margin-left: 5vw; - margin-right: 15vw; -} - -.formatted-font .usfm_lim1 { - font-size: 100%; -} -.text-spacing .usfm_lim1 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_lim1 { - margin-left: 15vw; - margin-right: 5vw; -} -.text-spacing[dir="rtl"] .usfm_lim1 { - margin-left: 5vw; - margin-right: 15vw; -} - -.formatted-font .usfm_lim2 { - font-size: 100%; -} -.text-spacing .usfm_lim2 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_lim2 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_lim2 { - margin-right: 20vw; -} - -.formatted-font .usfm_lim3 { - font-size: 100%; -} -.text-spacing .usfm_lim3 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_lim3 { - margin-left: 25vw; -} -.text-spacing[dir="rtl"] .usfm_lim3 { - margin-right: 25vw; -} - -.formatted-font .usfm_lim4 { - font-size: 100%; -} -.text-spacing .usfm_lim4 { - text-indent: -7.5vw; -} -.text-spacing[dir="ltr"] .usfm_lim4 { - margin-left: 30vw; -} -.text-spacing[dir="rtl"] .usfm_lim4 { - margin-right: 30vw; -} - -.usfm_lit { - text-align: end; -} -.formatted-font .usfm_lit { - font-weight: bold; - font-size: 100%; -} - -.formatted-font .usfm_ph { - font-size: 100%; -} -.text-spacing .usfm_ph { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_ph { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_ph { - margin-right: 10vw; -} - -.formatted-font .usfm_ph1 { - font-size: 100%; -} -.text-spacing .usfm_ph1 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_ph1 { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_ph1 { - margin-right: 10vw; -} - -.formatted-font .usfm_ph2 { - font-size: 100%; -} -.text-spacing .usfm_ph2 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_ph2 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_ph2 { - margin-right: 15vw; -} - -.formatted-font .usfm_ph3 { - font-size: 100%; -} -.text-spacing .usfm_ph3 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_ph3 { - margin-left: 20vw; -} -.text-spacing[dir="rtl"] .usfm_ph3 { - margin-right: 20vw; -} - -/* CharNode */ - -.formatted-font .usfm_qs { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_qac { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_litl { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_lik { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_liv { - font-size: 100%; -} - -.formatted-font .usfm_liv1 { - font-size: 100%; -} - -.formatted-font .usfm_liv2 { - font-size: 100%; -} - -.formatted-font .usfm_liv3 { - font-size: 100%; -} - -.formatted-font .usfm_liv4 { - font-size: 100%; -} - -.formatted-font .usfm_liv5 { - font-size: 100%; -} - -.formatted-font .usfm_rq { - font-size: 83%; - font-style: italic; -} - -.formatted-font .usfm_qt { - font-weight: bold; - font-size: 100%; -} - -.formatted-font .usfm_nd { - font-size: 100%; - text-decoration: underline; -} - -.formatted-font .usfm_tl { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_dc { - font-style: italic; -} - -.formatted-font .usfm_bk { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_sig { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_pn { - font-weight: bold; - font-size: 100%; - text-decoration: underline; -} - -.formatted-font .usfm_png { - font-size: 100%; - text-decoration: underline; -} - -.formatted-font .usfm_addpn { - font-weight: bold; - font-size: 100%; - font-style: italic; - text-decoration: underline; -} - -.formatted-font .usfm_wj { - color: #d43128; - font-size: 100%; -} - -.formatted-font .usfm_k { - font-weight: bold; - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_sls { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_ord { - vertical-align: text-top; - font-size: 66%; -} - -.formatted-font .usfm_add { - font-weight: bold; - font-style: italic; -} - -.formatted-font .usfm_no { - font-size: 100%; -} - -.formatted-font .usfm_it { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_bd { - font-weight: bold; - font-size: 100%; -} - -.formatted-font .usfm_bdit { - font-weight: bold; - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_em { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_sc { - font-size: 100%; - font-variant: small-caps; -} - -.formatted-font .usfm_sup { - vertical-align: text-top; - font-size: 66%; -} - -.formatted-font .usfm_pb { - font-size: 100%; -} - -.formatted-font .usfm_fig { - font-size: 100%; -} - -.formatted-font .usfm_jmp { - color: #003380; - text-decoration: underline; -} - -.formatted-font .usfm_pro { - font-size: 83%; -} - -.formatted-font .usfm_rb { - font-size: 100%; -} - -.formatted-font .usfm_w { - font-size: 100%; -} - -.formatted-font .usfm_wh { - font-size: 100%; -} - -.formatted-font .usfm_wg { - font-size: 100%; -} - -.formatted-font .usfm_wa { - font-size: 100%; -} - -.formatted-font .usfm_ndx { - font-size: 100%; -} - -/* Footnote NoteNode */ - -.formatted-font .usfm_f { - font-size: 80%; - position: relative; - top: -0.5em; -} - -.formatted-font .usfm_fe { - font-size: 100%; -} - -/* Footnote CharNode */ - -.formatted-font .usfm_fr { - font-weight: bold; - font-size: 100%; -} - -.formatted-font .usfm_ft { - font-size: 100%; -} - -.formatted-font .usfm_fk { - font-weight: bold; - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_fq { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_fqa { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_fl { - font-weight: bold; - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_fw { - font-size: 100%; -} - -.formatted-font .usfm_fp { - font-size: 100%; -} - -.formatted-font .usfm_fv { - vertical-align: text-top; - font-size: 66%; -} - -.formatted-font .usfm_fdc { - font-size: 100%; -} - -.formatted-font .usfm_fm { - vertical-align: text-top; - font-size: 66%; -} - -/* Cross-reference NoteNode */ - -.formatted-font .usfm_x { - font-size: 100%; -} - -/* Cross-reference CharNode */ - -.formatted-font .usfm_xo { - font-weight: bold; - font-size: 100%; -} - -.formatted-font .usfm_xop { - font-size: 100%; -} - -.formatted-font .usfm_xt { - font-size: 100%; - unicode-bidi: embed; -} - -.formatted-font .usfm_xta { - font-size: 100%; -} - -.formatted-font .usfm_xk { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_xq { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_xot { - font-size: 100%; -} - -.formatted-font .usfm_xnt { - font-size: 100%; -} - -.formatted-font .usfm_xdc { - font-size: 100%; -} - -/* TextNode in NoteNode */ - -.marker-hidden .usfm_f span[data-lexical-text="true"], -.marker-hidden .usfm_fe span[data-lexical-text="true"], -.marker-hidden .usfm_x span[data-lexical-text="true"] { - display: none; -} - -/* periph */ - -.formatted-font .usfm_periph { - font-weight: bold; - color: #e87217; - font-size: 116%; -} -.text-spacing .usfm_periph { - margin-bottom: 4pt; - margin-top: 16pt; -} - -.formatted-font .usfm_p1 { - font-size: 100%; -} -.text-spacing .usfm_p1 { - text-indent: 2.5vw; -} - -.formatted-font .usfm_p2 { - font-size: 100%; -} -.text-spacing .usfm_p2 { - text-indent: 2.5vw; -} -.text-spacing[dir="ltr"] .usfm_p2 { - margin-left: 2.5vw; -} -.text-spacing[dir="rtl"] .usfm_p2 { - margin-right: 2.5vw; -} - -.formatted-font .usfm_k1 { - font-size: 100%; -} - -.formatted-font .usfm_k2 { - font-size: 100%; -} - -.formatted-font .usfm_xtSee { - color: #003380; - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_xtSeeAlso { - color: #003380; - font-size: 100%; - font-style: italic; -} - -/* MilestoneNode */ - -.formatted-font .usfm_qt-s { - font-size: 100%; -} - -.formatted-font .usfm_qt-e { - font-size: 100%; -} - -.formatted-font .usfm_qt1-s { - font-size: 100%; -} - -.formatted-font .usfm_qt1-e { - font-size: 100%; -} - -.formatted-font .usfm_qt2-s { - font-size: 100%; -} - -.formatted-font .usfm_qt2-e { - font-size: 100%; -} - -.formatted-font .usfm_qt3-s { - font-size: 100%; -} - -.formatted-font .usfm_qt3-e { - font-size: 100%; -} - -.formatted-font .usfm_qt4-s { - font-size: 100%; -} - -.formatted-font .usfm_qt4-e { - font-size: 100%; -} - -.formatted-font .usfm_qt5-s { - font-size: 100%; -} - -.formatted-font .usfm_qt5-e { - font-size: 100%; -} - -.formatted-font .usfm_ts-s { - font-size: 100%; -} - -.formatted-font .usfm_ts-e { - font-size: 100%; -} - -/* table */ - -.formatted-font .usfm_tr { - font-size: 100%; -} -.text-spacing .usfm_tr { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_tr { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_tr { - margin-right: 10vw; -} - -.formatted-font .usfm_th1 { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_th2 { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_th3 { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_th4 { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_th5 { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_tc1 { - font-size: 100%; -} - -.formatted-font .usfm_tc2 { - font-size: 100%; -} - -.formatted-font .usfm_tc3 { - font-size: 100%; -} - -.formatted-font .usfm_tc4 { - font-size: 100%; -} - -.formatted-font .usfm_tc5 { - font-size: 100%; -} - -.formatted-font .usfm_thc1 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thc1 { - text-align: center; -} - -.formatted-font .usfm_thc2 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thc2 { - text-align: center; -} - -.formatted-font .usfm_thc3 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thc3 { - text-align: center; -} - -.formatted-font .usfm_thc4 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thc4 { - text-align: center; -} - -.formatted-font .usfm_thc5 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thc5 { - text-align: center; -} - -.formatted-font .usfm_tcc1 { - font-size: 100%; -} -.text-spacing .usfm_tcc1 { - text-align: center; -} - -.formatted-font .usfm_tcc2 { - font-size: 100%; -} -.text-spacing .usfm_tcc2 { - text-align: center; -} - -.formatted-font .usfm_tcc3 { - font-size: 100%; -} -.text-spacing .usfm_tcc3 { - text-align: center; -} - -.formatted-font .usfm_tcc4 { - font-size: 100%; -} -.text-spacing .usfm_tcc4 { - text-align: center; -} - -.formatted-font .usfm_tcc5 { - font-size: 100%; -} -.text-spacing .usfm_tcc5 { - text-align: center; -} - -.formatted-font .usfm_thr1 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thr1 { - text-align: end; -} - -.formatted-font .usfm_thr2 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thr2 { - text-align: end; -} - -.formatted-font .usfm_thr3 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thr3 { - text-align: end; -} - -.formatted-font .usfm_thr4 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thr4 { - text-align: end; -} - -.formatted-font .usfm_thr5 { - font-size: 100%; - font-style: italic; -} -.text-spacing .usfm_thr5 { - text-align: end; -} - -.formatted-font .usfm_tcr1 { - font-size: 100%; -} -.text-spacing .usfm_tcr1 { - text-align: end; -} - -.formatted-font .usfm_tcr2 { - font-size: 100%; -} -.text-spacing .usfm_tcr2 { - text-align: end; -} - -.formatted-font .usfm_tcr3 { - font-size: 100%; -} -.text-spacing .usfm_tcr3 { - text-align: end; -} - -.formatted-font .usfm_tcr4 { - font-size: 100%; -} -.text-spacing .usfm_tcr4 { - text-align: end; -} - -.formatted-font .usfm_tcr5 { - font-size: 100%; -} -.text-spacing .usfm_tcr5 { - text-align: end; -} - -/* table/unknown */ - -.formatted-font .usfm_tr1 { - font-size: 100%; -} -.text-spacing .usfm_tr1 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_tr1 { - margin-left: 10vw; -} -.text-spacing[dir="rtl"] .usfm_tr1 { - margin-right: 10vw; -} - -.formatted-font .usfm_tr2 { - font-size: 100%; -} -.text-spacing .usfm_tr2 { - text-indent: -5vw; -} -.text-spacing[dir="ltr"] .usfm_tr2 { - margin-left: 15vw; -} -.text-spacing[dir="rtl"] .usfm_tr2 { - margin-right: 15vw; -} - -.formatted-font .usfm_ps { - font-size: 100%; -} -.text-spacing .usfm_ps { - text-indent: 2.5vw; -} - -.formatted-font .usfm_psi { - font-size: 100%; -} -.text-spacing .usfm_psi { - text-indent: 2.5vw; - margin-left: 5vw; - margin-right: 5vw; -} - -.formatted-font .usfm_fs { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_wr { - font-size: 100%; - font-style: italic; -} - -.formatted-font .usfm_pub { - font-size: 83%; -} - -.formatted-font .usfm_toc { - font-size: 83%; -} - -.formatted-font .usfm_pref { - font-size: 83%; -} - -.formatted-font .usfm_intro { - font-size: 83%; -} - -.formatted-font .usfm_conc { - font-size: 83%; -} - -.formatted-font .usfm_glo { - font-size: 83%; -} - -.formatted-font .usfm_idx { - font-size: 83%; -} - -.formatted-font .usfm_maps { - font-size: 83%; -} - -.formatted-font .usfm_cov { - font-size: 83%; -} - -.formatted-font .usfm_spine { - font-size: 83%; -} - -.formatted-font .usfm_pubinfo { - color: #003380; - font-size: 100%; -} - -.formatted-font .usfm_zpa-xb { - font-size: 100%; -} - -.formatted-font .usfm_zpa-xc { - font-weight: bold; - font-size: 100%; -} - -.formatted-font .usfm_zpa-xv { - font-size: 100%; -} - -.formatted-font .usfm_zpa-d { - font-size: 100%; -} - -body { - background-color: rgba(252, 252, 252, 1); - color: rgba(25, 25, 25, 1); -} - -.marker { - unicode-bidi: isolate; -} -.formatted-font .marker { - color: rgba(140, 140, 140, 1); - font-size: 0.7em; -} - -/* Used for unformatted displays */ -.markerplain { - unicode-bidi: isolate; -} - -.notetext { - unicode-bidi: embed; -} - -.attribute { - color: rgba(170, 170, 170, 1); -} - -.attribute:hover { - color: rgba(25, 25, 25, 1); -} - -.invalid { - color: rgba(204, 30, 20, 1); - font-weight: bold; -} - -/* NoteNode, ImmutableNoteCallerNode */ - -.immutable-note-caller > button, -/* Styles for Preview (and Ruby) views */ -.caller_preview, -.previewcallee { - color: rgba(18, 82, 179, 1); - font-weight: bold; - line-height: 1; - vertical-align: super; - font-size: 0.66em; -} - -.immutable-note-caller > button { - cursor: pointer; - text-decoration: none; - border: 0; - padding: 0; - background-color: inherit; -} - -@counter-style note-callers { - system: alphabetic; - /* These symbols are updated in TS by the `NoteNodePlugin`. */ - symbols: "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" - "w" "x" "y" "z"; -} - -/* Cross-reference (x, ex) auto-generated callers use their own sequence, so they don't also - increment the footnote counter above. */ -@counter-style cross-ref-callers { - system: cyclic; - /* These symbols are updated in TS by the `NoteNodePlugin`. */ - symbols: "\2020"; - suffix: ""; -} - -.editor-input { - counter-reset: caller crossref; -} - -.editor-input.reset-counters { - counter-reset: none; -} - -.note.usfm_f .immutable-note-caller[data-caller="+"], -.note.usfm_fe .immutable-note-caller[data-caller="+"], -.note.usfm_ef .immutable-note-caller[data-caller="+"], -.note.usfm_efe .immutable-note-caller[data-caller="+"] { - counter-increment: caller; -} - -.note.usfm_f .immutable-note-caller[data-caller="+"] > button::before, -.note.usfm_fe .immutable-note-caller[data-caller="+"] > button::before, -.note.usfm_ef .immutable-note-caller[data-caller="+"] > button::before, -.note.usfm_efe .immutable-note-caller[data-caller="+"] > button::before { - content: counter(caller, note-callers); -} - -.note.usfm_x .immutable-note-caller[data-caller="+"], -.note.usfm_ex .immutable-note-caller[data-caller="+"] { - counter-increment: crossref; -} - -.note.usfm_x .immutable-note-caller[data-caller="+"] > button::before, -.note.usfm_ex .immutable-note-caller[data-caller="+"] > button::before { - content: counter(crossref, cross-ref-callers); -} - -.formatted-font.text-spacing .immutable-note-caller[data-caller="-"] { - display: none; -} - -.formatted-font .immutable-note-caller { - @apply text-xs text-blue-500; -} - -.caller_big { - unicode-bidi: normal; - color: rgba(18, 82, 179, 1); - font-weight: bold; - text-indent: 0pt; - vertical-align: text-top; - font-size: 0.66em; -} - -.caller_small { - unicode-bidi: normal; - color: rgba(18, 82, 179, 1); - font-family: "Times New Roman", serif; - vertical-align: text-top; - text-indent: 0pt; - font-size: 0.66em; -} - -.caller_highlight { - background-color: #ffffb5; - border-top: solid 1px #0000ff; - border-bottom: solid 1px #0000ff; -} - -.opennote { - color: #7777ff; -} - -table { - border-collapse: collapse; -} - -td { - border: 1px solid #000000; - page-break-inside: avoid; - /* FB27281 adding padding based on font size*/ - padding-right: 0.28em; - padding-left: 0.28em; -} - -td.markercell { - border-style: none; -} - -rt { - cursor: pointer; -} - -/* Style statues */ -.status_unknown { - color: rgba(204, 30, 20, 1); - font-weight: bold; -} - -.status_invalid { - border-bottom: 1px solid rgba(204, 30, 20, 1); - color: rgba(204, 30, 20, 1); -} - -.caption { - text-align: center; - font-style: italic; - font-weight: bold; -} - -.figure { - text-align: center; -} - -.sidebar { - border: solid 1px rgba(18, 82, 179, 1); - margin-left: 10px; -} - -/* VerseNode, ImmutableVerseNode */ - -/* -.formatted-font .verse { - @apply text-cyan-500; - background-color: rgba(222, 222, 222, 1); - vertical-align: super; - font-size: 0.66em; -} -.text-spacing .verse { - margin: 0px 2px 0px 2px; - padding: 0px 1px 0px 1px; - text-indent: 0in; - white-space: nowrap; -} -*/ - -span.unread img { - background-color: #ffff99; - position: relative; - bottom: -1px; /* negative of border-width to align baseline */ - border-width: 1px; - border-style: solid; - border-color: #808080; -} -span.read img { - background-color: transparent; - position: relative; - bottom: 0px; - border-width: 0px; - border-style: none; -} - -.annot_comment_todo { - border-bottom: 1px dashed #888888; -} - -.annot_comment_done { - border-bottom: 1px dashed #888888; -} - -.annot_greencursor { - background-color: rgba(152, 235, 157, 1); -} -.annot_goldcursor { - background-color: rgba(255, 255, 163, 1); -} -.annot_bluecursor { - background-color: rgba(204, 224, 255, 1); -} -.annot_greycursor { - background-color: rgba(222, 222, 222, 1); -} -.annot_violetcursor { - background-color: rgba(233, 212, 255, 1); -} - -.annot_spellingerror { - background-repeat: repeat-x; - background-position: left bottom; - padding-bottom: 0px; - vertical-align: text-top; -} - -.annot_spellingunknown { - background-repeat: repeat-x; - background-position: left bottom; - padding-bottom: 0px; - vertical-align: text-top; -} - -.found_term { - background-color: rgba(222, 222, 222, 1); - text-indent: 0; - margin-left: 0; - display: inline-block; - border-bottom-style: solid; - border-bottom-width: medium medium thick medium; - border-bottom-color: rgba(252, 252, 252, 1); - text-decoration: inherit; -} -.guessed_term { - background-color: rgba(255, 191, 143, 1); - text-indent: 0; - margin-left: 0; - display: inline-block; - border-bottom-style: solid; - border-bottom-width: medium medium thick medium; - border-bottom-color: rgba(252, 252, 252, 1); - text-decoration: inherit; -} -.found_term.unselected_term { - background-color: rgba(222, 222, 222, 0.6); - text-indent: 0; - margin-left: 0; - display: inline-block; - border-bottom-style: solid; - border-bottom-width: medium medium thick medium; - border-bottom-color: rgba(252, 252, 252, 1); - text-decoration: inherit; -} -.guessed_term.unselected_term { - background-color: rgba(255, 191, 143, 0.3); - text-indent: 0; - margin-left: 0; - display: inline-block; - border-bottom-style: solid; - border-bottom-width: medium medium thick medium; - border-bottom-color: rgba(252, 252, 252, 1); - text-decoration: inherit; -} -.selected_term { - border-style: none none solid none; - text-indent: 0; - margin-left: 0; - display: inline-block; - border-bottom-style: solid; - border-bottom-width: medium medium thick medium; - border-bottom-color: rgba(252, 252, 252, 1); - text-decoration: inherit; -} -.annot_reference_link { - border-bottom: 1px solid #93c4ff; -} -.annot_invalid_reference { - border-bottom: 1px solid #ff8080; -} -.annot_checkError { - border-top: 1px solid #ff0000; - border-bottom: 1px solid #ff0000; - background-color: rgba(255, 204, 204, 0.5); -} - -/* ContextMenuPlugin */ - -.auto-embed-menu { - width: 150px; -} - -.typeahead-popover { - background: #fff; - box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3); - border-radius: 8px; -} - -.typeahead-popover ul { - padding: 0; - list-style: none; - margin: 0; - border-radius: 8px; - max-height: 200px; - overflow-y: scroll; - -ms-overflow-style: none; - scrollbar-width: none; -} - -.typeahead-popover ul::-webkit-scrollbar { - display: none; -} - -.typeahead-popover ul li { - margin: 0; - min-width: 180px; - font-size: 14px; - outline: none; - cursor: pointer; - border-radius: 8px; -} - -.typeahead-popover ul li.selected { - background: #eee; -} - -.typeahead-popover li { - margin: 0 8px 0 8px; - padding: 8px; - color: #050505; - cursor: pointer; - line-height: 16px; - font-size: 15px; - display: flex; - align-content: center; - flex-direction: row; - flex-shrink: 0; - background-color: #fff; - border-radius: 8px; - border: 0; -} - -.typeahead-popover li.active { - display: flex; - width: 20px; - height: 20px; - background-size: contain; -} - -.typeahead-popover li:first-child { - border-radius: 8px 8px 0px 0px; -} - -.typeahead-popover li:last-child { - border-radius: 0px 0px 8px 8px; -} - -.typeahead-popover li:hover { - background-color: #eee; -} - -.typeahead-popover li .text { - display: flex; - line-height: 20px; - flex-grow: 1; - min-width: 150px; -} - -.typeahead-popover li .icon { - display: flex; - width: 20px; - height: 20px; - user-select: none; - margin-right: 8px; - line-height: 16px; - background-size: contain; - 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); -} diff --git a/packages/scribe/src/types.d.ts b/packages/scribe/src/types.d.ts deleted file mode 100644 index fbc2a95c..00000000 --- a/packages/scribe/src/types.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "sj-usfm-grammar"; diff --git a/packages/scribe/src/vite-env.d.ts b/packages/scribe/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2..00000000 --- a/packages/scribe/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/scribe/tsconfig.json b/packages/scribe/tsconfig.json deleted file mode 100644 index 62ebbd94..00000000 --- a/packages/scribe/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.lib.json" - }, - { - "path": "./tsconfig.spec.json" - } - ] -} diff --git a/packages/scribe/tsconfig.lib.json b/packages/scribe/tsconfig.lib.json deleted file mode 100644 index 8e6821d7..00000000 --- a/packages/scribe/tsconfig.lib.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "types": ["node", "vite/client"], - "rootDir": "src", - "baseUrl": ".", - "paths": { - "@/*": ["src/*"] - }, - "jsx": "react-jsx", - "module": "esnext", - "moduleResolution": "bundler", - "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo" - }, - "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"], - "exclude": [ - "out-tsc", - "dist", - "**/*.spec.ts", - "**/*.test.ts", - "**/*.spec.tsx", - "**/*.test.tsx", - "**/*.spec.js", - "**/*.test.js", - "**/*.spec.jsx", - "**/*.test.jsx", - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx", - "eslint.config.js", - "eslint.config.cjs", - "eslint.config.mjs" - ], - "references": [ - { - "path": "../../libs/shared-react/tsconfig.lib.json" - }, - { - "path": "../../libs/shared/tsconfig.lib.json" - }, - { - "path": "../utilities/tsconfig.lib.json" - } - ] -} diff --git a/packages/scribe/tsconfig.spec.json b/packages/scribe/tsconfig.spec.json deleted file mode 100644 index 50987518..00000000 --- a/packages/scribe/tsconfig.spec.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out-tsc/vitest", - "types": [ - "vitest", - "vitest/globals", - "vitest/importMeta", - "vite/client", - "node" - ], - "forceConsistentCasingInFileNames": true, - "jsx": "react-jsx", - "module": "esnext", - "moduleResolution": "bundler" - }, - "include": [ - "vite.config.ts", - "vite.config.mts", - "vitest.config.ts", - "vitest.config.mts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "src/**/*.test.tsx", - "src/**/*.spec.tsx", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx", - "src/**/*.d.ts" - ], - "references": [ - { - "path": "../../libs/shared" - }, - { - "path": "../../libs/shared-react" - }, - { - "path": "../utilities" - }, - { - "path": "./tsconfig.lib.json" - } - ] -} diff --git a/packages/scribe/vite.config.mts b/packages/scribe/vite.config.mts deleted file mode 100644 index 33f0c70c..00000000 --- a/packages/scribe/vite.config.mts +++ /dev/null @@ -1,82 +0,0 @@ -/// -import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin"; -import react from "@vitejs/plugin-react-swc"; -import * as path from "path"; -// import { visualizer } from "rollup-plugin-visualizer"; -import { defineConfig } from "vitest/config"; -import dts from "vite-plugin-dts"; - -// https://vitejs.dev/config/ -export default defineConfig({ - root: __dirname, - cacheDir: "../../node_modules/.vite/packages/scribe-editor", - plugins: [ - react(), - nxViteTsPaths(), - dts({ - entryRoot: "src", - rollupTypes: true, - tsconfigPath: path.join(__dirname, "tsconfig.lib.json"), - exclude: ["src/App.tsx", "src/main.tsx", "src/**/*.test.ts", "src/**/*.test.tsx"], - aliasesExclude: ["@eten-tech-foundation/scripture-utilities"], - }), - ], - resolve: { - alias: { - "@": path.resolve(__dirname, "src"), - }, - }, - build: { - outDir: "./dist", - emptyOutDir: true, - sourcemap: true, - reportCompressedSize: true, - commonjsOptions: { - transformMixedEsModules: true, - }, - lib: { - // Could also be a dictionary or array of multiple entry points. - entry: "src/index.ts", - name: "@eten-tech-foundation/scribe-editor", - fileName: "index", - // Change this to the formats you want to support. - // Don't forget to update your package.json as well. - formats: ["es" as const], - }, - rollupOptions: { - external: [ - // peerDependencies - "react", - "react-dom", - "react/jsx-runtime", - // dependencies - "@eten-tech-foundation/scripture-utilities", - "@floating-ui/dom", - "fast-equals", - // Exclude all Lexical packages and their sub-modules - /^@lexical\/.*/, - /^lexical.*/, - ], - // open the HTML file manually or set `open` to true - // plugins: [visualizer({ filename: "dist/bundle-analysis.html", open: false })], - output: { - globals: { - react: "React", - "react-dom": "ReactDOM", - }, - }, - }, - }, - test: { - watch: false, - globals: true, - environment: "jsdom", - include: ["{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], - reporters: ["default"], - passWithNoTests: true, - coverage: { - reportsDirectory: "./test-output/vitest/coverage", - provider: "v8" as const, - }, - }, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf250d62..c8b7ef89 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -467,58 +467,6 @@ importers: specifier: ^13.6.30 version: 13.6.30 - packages/scribe: - dependencies: - '@eten-tech-foundation/scripture-utilities': - specifier: workspace:~ - version: link:../utilities - '@floating-ui/dom': - specifier: ^1.7.6 - version: 1.7.6 - '@lexical/mark': - specifier: ^0.43.0 - version: 0.43.0 - '@lexical/react': - specifier: ^0.43.0 - version: 0.43.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.30) - '@lexical/selection': - specifier: ^0.43.0 - version: 0.43.0 - '@lexical/utils': - specifier: ^0.43.0 - version: 0.43.0 - fast-equals: - specifier: ^5.4.0 - version: 5.4.0 - lexical: - specifier: ^0.43.0 - version: 0.43.0 - devDependencies: - '@tailwindcss/postcss': - specifier: ^4.2.2 - version: 4.2.2 - postcss: - specifier: ^8.5.25 - version: 8.5.26 - react: - specifier: ^18.3.1 - version: 18.3.1 - react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) - shared: - specifier: workspace:~ - version: link:../../libs/shared - shared-react: - specifier: workspace:~ - version: link:../../libs/shared-react - sj-usfm-grammar: - specifier: ^3.0.8 - version: 3.0.8 - tailwindcss: - specifier: ^4.2.2 - version: 4.2.2 - packages/utilities: dependencies: '@xmldom/xmldom': @@ -546,10 +494,6 @@ packages: '@acemir/cssom@0.9.31': resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - '@asamuzakjp/css-color@5.0.1': resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -3336,98 +3280,6 @@ packages: peerDependencies: tailwindcss: '>=3.2.0' - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} - - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.2.2': - resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} - '@tanstack/react-table@8.21.3': resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} engines: {node: '>=12'} @@ -7071,9 +6923,6 @@ packages: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} - sj-usfm-grammar@3.0.8: - resolution: {integrity: sha512-GThlgmxJKcs+abG/az0YVlTesGEAHkuRH5yy5Sj/Y5Xzq6SCL4EnZl2mdoemFEzvSjfPDE0tohKtHzCnmrohTQ==} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -7905,8 +7754,6 @@ snapshots: '@acemir/cssom@0.9.31': {} - '@alloc/quick-lru@5.2.0': {} - '@asamuzakjp/css-color@5.0.1': dependencies: '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -11337,75 +11184,6 @@ snapshots: dependencies: tailwindcss: 4.2.2 - '@tailwindcss/node@4.2.2': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.19.0 - jiti: 2.6.1 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.2 - - '@tailwindcss/oxide-android-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide@4.2.2': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - - '@tailwindcss/postcss@4.2.2': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - postcss: 8.5.26 - tailwindcss: 4.2.2 - '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/table-core': 8.21.3 @@ -12783,7 +12561,8 @@ snapshots: destroy@1.2.0: {} - detect-libc@2.0.4: {} + detect-libc@2.0.4: + optional: true detect-newline@3.1.0: {} @@ -14538,6 +14317,7 @@ snapshots: lightningcss-linux-x64-musl: 1.32.0 lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + optional: true lines-and-columns@1.2.4: {} @@ -15707,8 +15487,6 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 - sj-usfm-grammar@3.0.8: {} - slash@3.0.0: {} slice-ansi@7.1.0: diff --git a/tsconfig.json b/tsconfig.json index 23abf121..13919757 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,9 +15,6 @@ { "path": "./packages/platform" }, - { - "path": "./packages/scribe" - }, { "path": "./libs/shared-react" },