Skip to content

Commit

Permalink
Add Vim binding for CodePair using CodeMirror 6
Browse files Browse the repository at this point in the history
  • Loading branch information
choidabom committed Sep 7, 2024
1 parent 94f2a63 commit 4553a6e
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 25 deletions.
13 changes: 13 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@mui/x-date-pickers": "^7.13.0",
"@react-hook/window-size": "^3.1.1",
"@reduxjs/toolkit": "^2.0.1",
"@replit/codemirror-vim": "^6.2.1",
"@sentry/react": "^7.99.0",
"@swc/helpers": "^0.5.3",
"@tanstack/react-query": "^5.17.15",
Expand Down
45 changes: 32 additions & 13 deletions frontend/src/components/editor/DocumentView.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Backdrop, Box, CircularProgress, Paper } from "@mui/material";
import { useWindowWidth } from "@react-hook/window-size";
import { useSelector } from "react-redux";
import { selectEditor } from "../../store/editorSlice";
import Resizable from "react-resizable-layout";
import { useWindowWidth } from "@react-hook/window-size";
import { ScrollSync, ScrollSyncPane } from "react-scroll-sync";
import { EditorModeType, selectEditor } from "../../store/editorSlice";
import Editor from "./Editor";
import { Backdrop, Box, CircularProgress, Paper } from "@mui/material";
import EditorBottomBar, { BOTTOM_BAR_HEIGHT } from "./EditorBottomBar";
import Preview from "./Preview";
import { ScrollSync, ScrollSyncPane } from "react-scroll-sync";

function DocumentView() {
const DocumentView = () => {
const editorStore = useSelector(selectEditor);
const windowWidth = useWindowWidth();

Expand All @@ -20,9 +21,7 @@ function DocumentView() {

return (
<>
{/* For Markdown Preview Theme */}
<div className="wmde-markdown-var" />
{editorStore.mode === "both" && (
{editorStore.mode === EditorModeType.both && (
<Resizable axis={"x"} initial={windowWidth / 2} min={400}>
{({ position: width, separatorProps }) => (
<ScrollSync>
Expand All @@ -32,10 +31,21 @@ function DocumentView() {
display: "flex",
height: "100%",
overflow: "hidden",
position: "relative",
}}
>
<div id="left-block" style={{ width }}>
<Editor />
<div
id="left-block"
style={{
width,
position: "relative",
height: "100%",
}}
>
<div style={{ height: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)` }}>
<Editor />
</div>
<EditorBottomBar width={width} />
</div>
<Paper
id="splitter"
Expand Down Expand Up @@ -66,14 +76,23 @@ function DocumentView() {
)}
</Resizable>
)}
{editorStore.mode === "read" && (

{editorStore.mode === EditorModeType.edit && (
<div style={{ position: "relative", height: "100%" }}>
<div style={{ height: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)` }}>
<Editor />
</div>
<EditorBottomBar width="100%" />
</div>
)}

{editorStore.mode === EditorModeType.read && (
<Box sx={{ p: 4, overflow: "auto" }} height="100%">
<Preview />
</Box>
)}
{editorStore.mode === "edit" && <Editor />}
</>
);
}
};

export default DocumentView;
6 changes: 5 additions & 1 deletion frontend/src/components/editor/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { markdown } from "@codemirror/lang-markdown";
import { EditorState } from "@codemirror/state";
import { keymap } from "@codemirror/view";
import { vim } from "@replit/codemirror-vim";
import { xcodeDark, xcodeLight } from "@uiw/codemirror-theme-xcode";
import { basicSetup, EditorView } from "codemirror";
import { useCallback, useEffect, useState } from "react";
Expand All @@ -10,7 +11,7 @@ import { useCreateUploadUrlMutation, useUploadFileMutation } from "../../hooks/a
import { useCurrentTheme } from "../../hooks/useCurrentTheme";
import { useFormatUtils } from "../../hooks/useFormatUtils";
import { useToolBar } from "../../hooks/useToolBar";
import { selectEditor, setCmView } from "../../store/editorSlice";
import { CodeKeyType, selectEditor, setCmView } from "../../store/editorSlice";
import { selectSetting } from "../../store/settingSlice";
import { selectWorkspace } from "../../store/workspaceSlice";
import { imageUploader } from "../../utils/imageUploader";
Expand Down Expand Up @@ -41,6 +42,7 @@ function Editor() {
!element ||
!editorStore.doc ||
!editorStore.client ||
!editorStore.codeKey ||
typeof settingStore.fileUpload?.enable !== "boolean"
) {
return;
Expand All @@ -66,6 +68,7 @@ function Editor() {
keymap.of(setKeymapConfig()),
basicSetup,
markdown(),
editorStore.codeKey === CodeKeyType.vim ? vim() : [],
themeMode == "light" ? xcodeLight : xcodeDark,
EditorView.theme({ "&": { width: "100%" } }),
EditorView.lineWrapping,
Expand Down Expand Up @@ -94,6 +97,7 @@ function Editor() {
element,
editorStore.client,
editorStore.doc,
editorStore.codeKey,
themeMode,
workspaceStore.data,
settingStore.fileUpload?.enable,
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/components/editor/EditorBottomBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Button, Menu, MenuItem } from "@mui/material";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { CodeKeyType, selectEditor, setCodeKeyType } from "../../store/editorSlice";

export const BOTTOM_BAR_HEIGHT = 25;

const EditorBottomBar = (props: { width: number | string }) => {
const { width } = props;
const dispatch = useDispatch();
const editorStore = useSelector(selectEditor);
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const open = Boolean(anchorEl);

const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

const handleChangeCodeKey = (newKeyCode: CodeKeyType) => {
dispatch(setCodeKeyType(newKeyCode));
handleClose();
};

return (
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
width,
background: "000000",
borderTop: "1px solid #ccc",
height: BOTTOM_BAR_HEIGHT,
display: "flex",
}}
>
<Button variant="text" onClick={handleOpen}>
{editorStore.codeKey}
</Button>
<Menu
id="codekey-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
anchorOrigin={{
vertical: "top",
horizontal: "left",
}}
transformOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
{Object.values(CodeKeyType).map((keyType) => (
<MenuItem key={keyType} onClick={() => handleChangeCodeKey(keyType)}>
{keyType}
</MenuItem>
))}
</Menu>
</div>
);
};

export default EditorBottomBar;
2 changes: 1 addition & 1 deletion frontend/src/components/editor/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import rehypeExternalLinks from "rehype-external-links";
import rehypeKatex from "rehype-katex";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import { getCodeString } from "rehype-rewrite";
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
import remarkMath from "remark-math";
import { useCurrentTheme } from "../../hooks/useCurrentTheme";
import { selectEditor } from "../../store/editorSlice";
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/headers/DocumentHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useNavigate } from "react-router-dom";
import { useUserPresence } from "../../hooks/useUserPresence";
import { EditorModeType, selectEditor, setMode } from "../../store/editorSlice";
import { selectWorkspace } from "../../store/workspaceSlice";
import { ShareRole } from "../../utils/share";
import DownloadMenu from "../common/DownloadMenu";
import ShareButton from "../common/ShareButton";
import ThemeButton from "../common/ThemeButton";
Expand All @@ -31,8 +32,8 @@ function DocumentHeader() {
const { presenceList } = useUserPresence(editorState.doc);

useEffect(() => {
if (editorState.shareRole === "READ") {
dispatch(setMode("read"));
if (editorState.shareRole === ShareRole.READ) {
dispatch(setMode(EditorModeType.read));
}
}, [dispatch, editorState.shareRole]);

Expand All @@ -58,7 +59,7 @@ function DocumentHeader() {
</Tooltip>
)}
<Paper>
{editorState.shareRole !== "READ" && (
{editorState.shareRole !== ShareRole.READ && (
<ToggleButtonGroup
value={editorState.mode}
exclusive
Expand Down
30 changes: 23 additions & 7 deletions frontend/src/store/editorSlice.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "./store";
import { createSlice } from "@reduxjs/toolkit";
import { EditorView } from "codemirror";
import * as yorkie from "yorkie-js-sdk";
import { YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType } from "../utils/yorkie/yorkieSync";
import { ShareRole } from "../utils/share";
import { EditorView } from "codemirror";
import { YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType } from "../utils/yorkie/yorkieSync";
import { RootState } from "./store";

export enum EditorModeType {
edit = "edit",
both = "both",
read = "read",
}

export enum CodeKeyType {
sublime = "sublime",
vim = "vim",
}

export type EditorModeType = "edit" | "both" | "read";
export type CodePairDocType = yorkie.Document<
YorkieCodeMirrorDocType,
YorkieCodeMirrorPresenceType
>;

export interface EditorState {
mode: EditorModeType;
codeKey: CodeKeyType;
shareRole: ShareRole | null;
doc: CodePairDocType | null;
client: yorkie.Client | null;
cmView: EditorView | null;
}

const initialState: EditorState = {
mode: "both",
mode: EditorModeType.both,
codeKey: CodeKeyType.sublime,
shareRole: null,
doc: null,
client: null,
Expand All @@ -35,6 +47,9 @@ export const editorSlice = createSlice({
setMode: (state, action: PayloadAction<EditorModeType>) => {
state.mode = action.payload;
},
setCodeKeyType: (state, action: PayloadAction<CodeKeyType>) => {
state.codeKey = action.payload;
},
setShareRole: (state, action: PayloadAction<ShareRole | null>) => {
state.shareRole = action.payload;
},
Expand All @@ -50,7 +65,8 @@ export const editorSlice = createSlice({
},
});

export const { setMode, setDoc, setClient, setShareRole, setCmView } = editorSlice.actions;
export const { setMode, setCodeKeyType, setShareRole, setDoc, setClient, setCmView } =
editorSlice.actions;

export const selectEditor = (state: RootState) => state.editor;

Expand Down

0 comments on commit 4553a6e

Please sign in to comment.