-
Notifications
You must be signed in to change notification settings - Fork 10
feat: simple editor tables #1190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
40e5dc8
9bb0a37
563eb64
2b7e8db
1e08ded
9176811
3e9aa0a
dd0047c
6ef665c
47cb231
7bcb93d
84c23e7
ce15539
2282864
a099800
d877004
16a01c4
2036d80
7dfdd95
a7aa50e
2264f50
0e9d6ed
26a882b
d73ab8d
3c19080
74dec8d
276b3ce
b29573b
4ba397a
8f87963
920d713
c0367d1
13f556e
98bcb8f
f924ea0
a55d1e6
22397ae
1517acc
78ed9bc
9965669
2475322
c0d7dbf
1a390e1
c233f6d
4d6f9b4
33ce227
53c76a3
5e47918
a5156ca
b610712
a414089
ef96d1f
627be67
274a956
992a054
c93c1c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,15 +11,16 @@ import { AttributePanel } from "./components/AttributePanel"; | |
| import { basePlugins } from "./plugins"; | ||
| import { baseSchema } from "./schemas"; | ||
|
|
||
| import "prosemirror-view/style/prosemirror.css"; | ||
| import "prosemirror-gapcursor/style/gapcursor.css"; | ||
| import "prosemirror-view/style/prosemirror.css"; | ||
| // For math | ||
| import "@benrbray/prosemirror-math/dist/prosemirror-math.css"; | ||
| import "katex/dist/katex.min.css"; | ||
|
|
||
| import { fixTables } from "prosemirror-tables"; | ||
|
|
||
| import { cn } from "utils"; | ||
|
|
||
| import { EditorContextProvider } from "./components/Context"; | ||
| import { MenuBar } from "./components/MenuBar"; | ||
| import SuggestPanel from "./components/SuggestPanel"; | ||
|
|
||
|
|
@@ -61,13 +62,18 @@ const initSuggestProps: SuggestProps = { | |
|
|
||
| export default function ContextEditor(props: ContextEditorProps) { | ||
| const [suggestData, setSuggestData] = useState<SuggestProps>(initSuggestProps); | ||
| const [editorState, setEditorState] = useState( | ||
| EditorState.create({ | ||
| const [editorState, setEditorState] = useState(() => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIL you can use a function to initialize |
||
| let state = EditorState.create({ | ||
| doc: props.initialDoc ? baseSchema.nodeFromJSON(props.initialDoc) : undefined, | ||
| schema: baseSchema, | ||
| plugins: [...basePlugins(baseSchema, props, suggestData, setSuggestData), reactKeys()], | ||
| }) | ||
| ); | ||
| }); | ||
| const fix = fixTables(state); | ||
| if (fix) { | ||
| state = state.apply(fix.setMeta("addToHistory", false)); | ||
| } | ||
| return state; | ||
| }); | ||
|
|
||
| const nodeViews = useMemo(() => { | ||
| return { contextAtom: props.atomRenderingComponent }; | ||
|
|
@@ -97,20 +103,18 @@ export default function ContextEditor(props: ContextEditorProps) { | |
| editable={() => !props.disabled} | ||
| className={cn("font-serif", props.className)} | ||
| > | ||
| <EditorContextProvider activeNode={null} position={0}> | ||
| {props.hideMenu ? null : ( | ||
| <div className="sticky top-0 z-10"> | ||
| <MenuBar upload={props.upload} /> | ||
| </div> | ||
| )} | ||
| <ProseMirrorDoc /> | ||
| <AttributePanel menuHidden={!!props.hideMenu} containerRef={containerRef} /> | ||
| <SuggestPanel | ||
| suggestData={suggestData} | ||
| setSuggestData={setSuggestData} | ||
| containerRef={containerRef} | ||
| /> | ||
| </EditorContextProvider> | ||
| {props.hideMenu ? null : ( | ||
| <div className="sticky top-0 z-10"> | ||
| <MenuBar upload={props.upload} /> | ||
| </div> | ||
| )} | ||
| <ProseMirrorDoc /> | ||
| <AttributePanel menuHidden={!!props.hideMenu} containerRef={containerRef} /> | ||
| <SuggestPanel | ||
| suggestData={suggestData} | ||
| setSuggestData={setSuggestData} | ||
| containerRef={containerRef} | ||
| /> | ||
| </ProseMirror> | ||
| </div> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import type { EditorState } from "prosemirror-state"; | ||
|
|
||
| import { Fragment, Node } from "prosemirror-model"; | ||
| import { NodeSelection } from "prosemirror-state"; | ||
|
|
||
| import { assert } from "utils"; | ||
|
|
||
| import type { Dispatch } from "./types"; | ||
|
|
||
| const nodeTypes = ["title", "figcaption", "credit", "license"] as const; | ||
| const nodeSlots = [ | ||
| "title", | ||
| // (child) | ||
| "table", | ||
| "image", | ||
| // (/child) | ||
| "figcaption", | ||
| "credit", | ||
| "license", | ||
| ] as const; | ||
| type ToggleableNodeType = (typeof nodeTypes)[number]; | ||
|
|
||
| const errNodeNotResolved = "Node not resolved at position"; | ||
| const errNodeNotFigure = "Node is not a figure node"; | ||
| const errNodeContentInvalid = "Node has invalid content"; | ||
|
|
||
| export const toggleFigureNode = | ||
| (state: EditorState, dispatch?: Dispatch) => | ||
| (position: number, nodeType: ToggleableNodeType) => { | ||
| const figurePosition = state.doc.resolve(position); | ||
| const figureNode = figurePosition.nodeAfter; | ||
| assert(figureNode !== null, errNodeNotResolved); | ||
| assert(figureNode.type.name === "figure", errNodeNotFigure); | ||
| const sparseContent: (Node | undefined)[] = []; | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I use a sparse array here to ensure each element is in the proper order. First I fill in the array like [title, image]
[, table, figcaption]
[, image, , , credit]
// etcThen remove all the holes in the array. This could be done by pushing each existing node into a new array, but if the figure's children ever got out of sync with the |
||
|
|
||
| let insert = true; | ||
| figureNode.content.forEach((node) => { | ||
| if (node.type.name !== nodeType) { | ||
| const slot = nodeSlots.findIndex((name) => name === node.type.name); | ||
| assert(slot > -1, errNodeContentInvalid); | ||
| sparseContent[slot] = node; | ||
| } else { | ||
| insert = false; | ||
| } | ||
| }); | ||
|
|
||
| if (insert) { | ||
| const slot = nodeSlots.findIndex((name) => name === nodeType); | ||
| sparseContent[slot] = figureNode.type.schema.nodes[nodeType].create(); | ||
| } | ||
|
|
||
| // remove holes in the array | ||
| const content = sparseContent.filter((node) => node !== undefined) as Node[]; | ||
|
|
||
| // replace the figure node with the new content | ||
| const newFigureNode = figureNode.type.create( | ||
| figureNode.attrs, | ||
| Fragment.fromArray(content), | ||
| figureNode.marks | ||
| ); | ||
|
|
||
| if (dispatch) { | ||
| const tr = state.tr.replaceWith( | ||
| figurePosition.pos, | ||
| figurePosition.pos + figureNode.nodeSize, | ||
| newFigureNode | ||
| ); | ||
| const selection = NodeSelection.create(tr.doc, tr.mapping.map(figurePosition.pos)); | ||
| tr.setSelection(selection); | ||
| dispatch(tr); | ||
| } | ||
|
|
||
| return true; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this version of prosemirror tables uses a different version of
prosemirror-modelthen we have been using. this causes errors when eg using math inside of a table.i fixed this here: #1242