diff --git a/package-lock.json b/package-lock.json index 5c1e1a68..cf314f3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@mui/x-date-pickers": "^6.18.4", "@tiptap/extension-collaboration": "^2.0.3", "@tiptap/extension-collaboration-cursor": "^2.0.3", + "@tiptap/extension-link": "^2.2.4", "@tiptap/extension-placeholder": "^2.0.0-beta.218", "@tiptap/pm": "^2.0.0-beta.218", "@tiptap/react": "^2.0.0-beta.218", @@ -2722,6 +2723,22 @@ "@tiptap/core": "^2.0.0" } }, + "node_modules/@tiptap/extension-link": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.2.4.tgz", + "integrity": "sha512-Qsx0cFZm4dxbkToXs5TcXbSoUdicv8db1gV1DYIZdETqjBm4wFjlzCUP7hPHFlvNfeSy1BzAMRt+RpeuiwvxWQ==", + "dependencies": { + "linkifyjs": "^4.1.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, "node_modules/@tiptap/extension-list-item": { "version": "2.1.12", "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz", @@ -6107,6 +6124,11 @@ "uc.micro": "^1.0.1" } }, + "node_modules/linkifyjs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz", + "integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/package.json b/package.json index 02d7ab23..eadb9768 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "dependencies": { "@datasworn/core": "^0.0.6", "@datasworn/ironsworn-classic": "^0.0.6", - "@datasworn/starforged": "^0.0.6", "@datasworn/ironsworn-classic-delve": "^0.0.6", + "@datasworn/starforged": "^0.0.6", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@fontsource/bebas-neue": "^5.0.12", @@ -26,6 +26,7 @@ "@mui/x-date-pickers": "^6.18.4", "@tiptap/extension-collaboration": "^2.0.3", "@tiptap/extension-collaboration-cursor": "^2.0.3", + "@tiptap/extension-link": "^2.2.4", "@tiptap/extension-placeholder": "^2.0.0-beta.218", "@tiptap/pm": "^2.0.0-beta.218", "@tiptap/react": "^2.0.0-beta.218", diff --git a/src/components/shared/RichTextEditor/MarkdownEditor.tsx b/src/components/shared/RichTextEditor/MarkdownEditor.tsx index 55e6ba2c..7681dfb2 100644 --- a/src/components/shared/RichTextEditor/MarkdownEditor.tsx +++ b/src/components/shared/RichTextEditor/MarkdownEditor.tsx @@ -4,6 +4,8 @@ import { Markdown } from "tiptap-markdown"; import { Editor } from "./Editor"; import { Box, Typography } from "@mui/material"; import { MarkdownEditorToolbar } from "./MarkdownEditorToolbar"; +import Link from "@tiptap/extension-link"; + export interface MarkdownEditorProps { label: string; content: string; @@ -16,7 +18,11 @@ export function MarkdownEditor(props: MarkdownEditorProps) { const editor = useEditor( { - extensions: [StarterKit, Markdown], + extensions: [ + StarterKit, + Markdown, + Link.extend({ inclusive: false }).configure({ openOnClick: false }), + ], content, onBlur, onUpdate: ({ editor }) => { diff --git a/src/components/shared/RichTextEditor/MarkdownEditorToolbar.tsx b/src/components/shared/RichTextEditor/MarkdownEditorToolbar.tsx index a6e8ee91..3b4ac4c5 100644 --- a/src/components/shared/RichTextEditor/MarkdownEditorToolbar.tsx +++ b/src/components/shared/RichTextEditor/MarkdownEditorToolbar.tsx @@ -1,4 +1,13 @@ -import { Box, ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material"; +import { + Autocomplete, + Box, + ListItemText, + Popover, + TextField, + ToggleButton, + ToggleButtonGroup, + Tooltip, +} from "@mui/material"; import { Editor } from "@tiptap/react"; import BoldIcon from "@mui/icons-material/FormatBold"; import ItalicIcon from "@mui/icons-material/FormatItalic"; @@ -6,6 +15,10 @@ import StrikeThroughIcon from "@mui/icons-material/FormatStrikethrough"; import HorizontalRuleIcon from "@mui/icons-material/HorizontalRule"; import BulletListIcon from "@mui/icons-material/FormatListBulleted"; import NumberedListIcon from "@mui/icons-material/FormatListNumbered"; +import { useStore } from "stores/store"; +import MovesIcon from "@mui/icons-material/DirectionsRun"; +import { OracleIcon } from "assets/OracleIcon"; +import { useState } from "react"; export interface MarkdownEditorToolbarProps { editor: Editor | null; @@ -14,10 +27,59 @@ export interface MarkdownEditorToolbarProps { export function MarkdownEditorToolbar(props: MarkdownEditorToolbarProps) { const { editor } = props; + const moveMap = useStore((store) => store.rules.moveMaps.moveMap); + const oracleMap = useStore( + (store) => store.rules.oracleMaps.oracleRollableMap + ); + + const [moveLinkPopoverParent, setMoveLinkPopoverParent] = + useState(null); + const [oracleLinkPopoverParent, setOracleLinkPopoverParent] = + useState(null); + if (!editor) { return null; } + const autocompleteOptions: { + id: string; + type: "move" | "oracle"; + label: string; + }[] = []; + Object.values(moveMap).forEach((move) => { + autocompleteOptions.push({ + id: move.id, + type: "move", + label: move.name, + }); + }); + Object.values(oracleMap).forEach((oracle) => { + autocompleteOptions.push({ + id: oracle.id, + type: "oracle", + label: oracle.name, + }); + }); + + const addDataswornLink = (id: string, label: string) => { + setMoveLinkPopoverParent(null); + setOracleLinkPopoverParent(null); + + editor.chain().focus().insertContent(label).run(); + + const { from } = editor.state.selection; + const startPos = from - label.length; + const endPos = startPos + label.length; + + editor + .chain() + .setTextSelection({ from: startPos, to: endPos }) + .setLink({ href: `id:${id}` }) + .setTextSelection(endPos + 1) + .focus() + .run(); + }; + return ( + + + setMoveLinkPopoverParent(evt.currentTarget)} + selected={false} + > + + + + + setOracleLinkPopoverParent(evt.currentTarget)} + selected={false} + > + + + + + + + setMoveLinkPopoverParent(null)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "left", + }} + > + + option.id} + getOptionLabel={(option) => option.name} + renderInput={(params) => ( + + )} + renderOption={(props, option) => ( + + + + )} + onChange={(evt, value) => { + value && addDataswornLink(value.id, value.name); + }} + /> + + + setOracleLinkPopoverParent(null)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "left", + }} + > + + option.id} + getOptionLabel={(option) => option.name} + renderInput={(params) => ( + + )} + renderOption={(props, option) => ( + + + + )} + onChange={(evt, value) => { + value && addDataswornLink(value.id, value.name); + }} + /> + + ); } diff --git a/src/components/shared/SimpleTable/SimpleTable.tsx b/src/components/shared/SimpleTable/SimpleTable.tsx index 033bc50c..67180c3a 100644 --- a/src/components/shared/SimpleTable/SimpleTable.tsx +++ b/src/components/shared/SimpleTable/SimpleTable.tsx @@ -42,21 +42,21 @@ export function SimpleTable(props: SimpleTableProps) { backgroundColor: theme.palette.background.paperInlayDarker, }, - "& th:nth-child(1)": { + "& th:nth-of-type(1)": { borderTopLeftRadius: theme.shape.borderRadius, }, - "& th:nth-last-child(1)": { + "& th:nth-last-of-type(1)": { borderTopRightRadius: theme.shape.borderRadius, }, "& tbody tr:nth-of-type(even) td": { backgroundColor: theme.palette.background.paperInlay, }, - "& tbody tr:nth-last-child(1)": { - "& td:nth-child(1)": { + "& tbody tr:nth-last-of-type(1)": { + "& td:nth-of-type(1)": { borderBottomLeftRadius: theme.shape.borderRadius, }, - "& td:nth-last-child(1)": { + "& td:nth-last-of-type(1)": { borderBottomRightRadius: theme.shape.borderRadius, }, },