mdx-editor / editor

A rich text editor React component for markdown
https://mdxeditor.dev
MIT License
2.09k stars 160 forks source link

MDXEditor has no styling #2

Closed ryendu closed 1 year ago

ryendu commented 1 year ago

Hi, first of all, thanks for much for this repo, it's amazing! I tried using MDXEditor in my React project but couldn't figure out a way to style it. This is what it looks like when imported.

Screenshot 2023-07-04 at 6 25 45 PM

I'm using

I'm using MDXEditor inside a client component in NextJS. This is how I'm setting it up:

<div>
  <MDXEditor
    markdown={markdown}
    onChange={(markdown) => {
      setMarkdown(markdown);
    }}
  />
</div>
petyosi commented 1 year ago

Oh, so exciting :). The first issue!

You need to import @mdxeditor/editor/style.css in there. This assumes that your bundling respects this package line. If not, the actual css file is here.

Please let me know how it goes, I am happy to help with your evaluation. It's a new project of mine, and I am happy to receive and act on feedback.

petyosi commented 1 year ago

Added this to the getting started docs.

ryendu commented 1 year ago

it worked, thanks!!

braincore commented 1 year ago

@petyosi Any suggestions for how to get styles included with a simple import rather than manually copying styles in?

Is it webpack that's unable to incorporate the exports key in your package.json?

I'm using:

petyosi commented 1 year ago

@braincore I'm not sure what you mean. What's the problem you have?

braincore commented 1 year ago

The toolbar had no styling before and after adding import '@mdxeditor/editor/style.css';. I assumed (but did not independently verify) that it's as you said: the bundler (webpack) does not respect the exports line in package.json.

If you don't think that's it, I'll dig in further.

petyosi commented 1 year ago

Thanks for the clarification.

As far as I can tell, exports should work with your webpack version. Do you see any kind of error? Does it work if you use '@mdxeditor/editor/dist/style.css' (the actual location of the file?).

If you have the whole thing reproducible somewhere, I should be able to help further and document it properly. Thank you!

braincore commented 1 year ago

Sorry for the delay.

Using '@mdxeditor/editor/dist/style.css' doesn't work: ./dist/style.css is not exported from package.

I'm trying to test this with a large existing project so I don't have anything small and reproducible unfortunately. I assume it works for a fresh project.

petyosi commented 1 year ago

@braincore in this case, it looks like your system does understand the exports field, but something else goes in the way. I'm not sure what goes on, but I would suggest that you poke around with the webdev tools and see where the style.css ends up. It could be many things, hard to say without being familiar with the project structure.

HWilliams64 commented 1 year ago

I am facing this same issue. The styling is missing from the mdx-editor components after running npm build in a standard create-react-app. I can confirm the browser receives the @mdxeditor/editor/style.css stylesheet after npm build, but the styles are not applied.

package.json ```json { "name": "my-app", "version": "0.1.0", "private": true, "dependencies": { "@auth0/auth0-react": "^2.2.0", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@fec/remark-a11y-emoji": "^3.1.0", "@fontsource/poppins": "^5.0.3", "@geoffcox/react-splitter": "^2.1.2", "@loadable/component": "^5.15.3", "@logtail/browser": "^0.4.9", "@mdxeditor/editor": "^0.22.5", "@monaco-editor/react": "^4.5.1", "@mui/icons-material": "^5.11.16", "@mui/lab": "^5.0.0-alpha.134", "@mui/material": "^5.13.5", "@mui/x-date-pickers": "^6.12.0", "@reduxjs/toolkit": "^1.9.5", "@tanstack/match-sorter-utils": "^8.8.4", "@tanstack/react-query": "^4.29.15", "@tanstack/react-table": "^8.9.2", "@tanstack/react-virtual": "^3.0.0-beta.54", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "ajv": "^8.12.0", "axios": "^1.4.0", "axios-cache-interceptor": "^1.2.0", "dayjs": "^1.11.9", "endent": "^2.1.0", "figlet": "^1.6.0", "flourite": "^1.2.4", "framer-motion": "^10.16.2", "highlight.js": "^11.8.0", "html-react-parser": "^4.0.0", "humanize-duration": "^3.28.0", "import-lazy": "^4.0.0", "lexical": "^0.12.2", "localstorage-slim": "^2.4.0", "match-sorter": "^6.3.1", "material-react-table": "^1.14.0", "object-hash": "^3.0.0", "package.json": "^2.0.1", "qs": "^6.11.2", "react": "^18.2.0", "react-content-loader": "^6.2.1", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.11", "react-helmet": "^6.1.0", "react-icons": "^4.9.0", "react-markdown": "^8.0.7", "react-redux": "^8.1.1", "react-router-dom": "^6.15.0", "react-scripts": "^5.0.1", "react-spinners": "^0.13.8", "react-syntax-highlighter": "^15.5.0", "react-use-websocket": "^4.3.1", "redux-persist": "^6.0.0", "redux-persist-transform-compress": "^4.2.0", "rehype-katex": "^6.0.3", "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", "sugar": "^2.0.6", "throttle-debounce": "^5.0.0", "tinyld": "^1.3.4", "web-vitals": "^2.1.4" }, "jest-w": "jest --watch", "jest": { "transform": { "^.+\\.[t|j]sx?$": "babel-jest" } }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ```
Component JS file ```js import { useEffect, useState } from "react"; import { Box, Divider, Stack } from "@mui/material"; import { truncateString } from "../../utils"; import { detect } from "tinyld/light"; import { BoldItalicUnderlineToggles, InsertCodeBlock, InsertImage, MDXEditor, UndoRedo, headingsPlugin, listsPlugin, quotePlugin, thematicBreakPlugin, toolbarPlugin, imagePlugin, codeBlockPlugin, codeMirrorPlugin, CodeToggle, linkPlugin, linkDialogPlugin, CreateLink, InsertTable, tablePlugin, ListsToggle, BlockTypeSelect, ConditionalContents, ChangeCodeMirrorLanguage, InsertThematicBreak, DiffSourceToggleWrapper, diffSourcePlugin, } from "@mdxeditor/editor"; import "@mdxeditor/editor/style.css"; // Seems to do nothing in production import { COLORS } from "../../styles/Styles"; const TEXT = require("../../text.json"); const TextEditor = (props) => { const [value, setValue] = useState(props.initialText || ""); const [focus, setFocus] = useState(false); useEffect(() => { // Sync local state with incoming prop setValue(props.initialText); }, [props.initialText]); const onChange = (editorValue) => { const shortenedValue = truncateString(editorValue, props.maxLength); setValue(shortenedValue); let lang = detect(value); if (!lang || !lang.length) { lang = "en"; } props.handleChange(lang, shortenedValue); }; useEffect(() => { const parentDiv = window.document.getElementById(props.uniqueId); if (!parentDiv) { console.error(`Parent element #${props.uniqueId} was not found.`); return; } parentDiv .querySelectorAll("._editorWrapper_npm11_133") .forEach((element) => { // @ts-ignore const css = element.style; css.setProperty("border-radius", "6px"); css.setProperty("margin", `${focus ? 1 : 2}px`); css.setProperty("border", `${focus ? 2 : 1}px solid #4178bc`); //css.setProperty("border-right", `${focus ? 2 : 1}px solid #4178bc`); //css.setProperty("border-left", `${focus ? 2 : 1}px solid #4178bc`); }); // Content container base parentDiv .querySelectorAll("._contentEditable_npm11_339") .forEach((element) => { // @ts-ignore const css = element.style; css.setProperty("min-height", `${props.minHeight || "20vh"}`); css.setProperty("max-height", `${props.maxHeight || "80vh"}`); css.setProperty("height", `${props.height || "20vh"}`); css.setProperty("resize", `vertical`); css.setProperty("overflow", `auto`); }); // Content container top layer parentDiv .querySelectorAll(`._rootContentEditableWrapper_npm11_1016`) .forEach((element) => { // @ts-ignore const css = element.style; css.setProperty("border-bottom-left-radius", "6px"); css.setProperty("border-bottom-right-radius", "6px"); css.setProperty("background", focus ? "#fff" : COLORS.primaryGrey); }); //Toolbar parentDiv.querySelectorAll(`._toolbarRoot_npm11_140`).forEach((element) => { // @ts-ignore const css = element.style; css.setProperty("border-radius", "0px"); css.setProperty("border-top-left-radius", "6px"); css.setProperty("border-top-right-radius", "6px"); css.setProperty("border-bottom", `1px solid #4178bc`); css.setProperty( "background-color", focus ? COLORS.primaryGrey : COLORS.altMidGrey ); }); //Toolbar redo/undo buttons parentDiv .querySelectorAll("._selectWithLabel_npm11_922 ._selectTrigger_npm11_267") .forEach((element) => { // @ts-ignore const css = element.style; css.setProperty("border", `1px solid ${COLORS.primaryLightBlue}`); css.setProperty("cursor", "pointer"); }); //Toolbar other buttons parentDiv .querySelectorAll( `._toolbarToggleItem_npm11_181, ._toolbarButton_npm11_210, ._toolbarToggleSingleGroup_npm11_196` ) .forEach((element) => { // @ts-ignore const css = element.style; css.setProperty("color", COLORS.primaryDarkBlue); css.setProperty("cursor", "pointer"); }); }); useEffect(() => { const parentDiv = window.document.getElementById(props.uniqueId); if (!parentDiv) { console.error(`Parent element #${props.uniqueId} was not found.`); return; } // Exit if the parent div doesn't exist. const divElement = parentDiv.querySelector("._contentEditable_npm11_339"); const handleFocusIn = (e) => { setFocus(true); }; const handleFocusOut = (e) => { setFocus(false); }; if (divElement) { divElement.addEventListener("focusin", handleFocusIn); divElement.addEventListener("focusout", handleFocusOut); //divElement.addEventListener("click", handleClick); } else { console.error( `Parent element #${props.uniqueId} > ._contentEditable_npm11_339 was not found.` ); return; } // Cleanup the event listeners on component unmount return () => { if (divElement) { divElement.removeEventListener("focusin", handleFocusIn); divElement.removeEventListener("focusout", handleFocusOut); //divElement.removeEventListener("click", handleClick); } }; // The effect will run once after the first render and won't run again unless // the component is unmounted and then remounted. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( ( editor?.editorType === "codeblock", contents: () => ( } > ), }, { when: (editor) => editor?.editorType === "codeblock", contents: () => ( } > ), }, { fallback: () => ( } sx={{ width: "100%" }} > ), }, ]} /> ), }), ]} /> ); }; TextEditor.defaultProps = { height: "200px", initialText: "", handleChange: () => {}, options: {}, placeholder: TEXT.components.markdown_text_field.default_help_text, uniqueId: "description-editor", defaultCodeBlockLanguage: "python", }; export default TextEditor; ```
Dockerfile ```dockerfile # Step 1: Build the React application FROM node:18 AS build ENV NODE_OPTIONS="--max-old-space-size=16384" # Set working directory WORKDIR /app # Copy package.json and package-lock.json COPY ./frontend/package*.json ./ # Install dependencies RUN npm install # Copy local files to container COPY ./frontend . # Build the React app RUN npm run build # Step 2: Serve the React application from Nginx FROM nginx:alpine # Install curl for health check RUN apk update \ && apk --no-cache \ add curl bash jq \ && mkdir -p "/data/nginx/cache" # Copy nginx configuration COPY docker/webapp/node/default.conf.template /etc/nginx/templates/nginx.conf.template COPY docker/webapp/node/nginx.conf /etc/nginx/nginx.conf COPY docker/webapp/node/entry.sh /entry.sh RUN chmod +x /entry.sh # Copy the build app to the nginx container COPY --from=build /app/build /usr/share/nginx/html # Expose ports EXPOSE 80 # Start Nginx CMD ["/entry.sh"] ```

You might say the useEffect() block with the CSS editing is the issue, but even when that's completely removed, the basic CSS is missing after the build.

When I run it via npm start it looks like this: Screen Shot 2023-09-17 at 3 01 00 PM

When I run it after building in a docker container, it looks like this: Screen Shot 2023-09-17 at 2 31 01 PM

HWilliams64 commented 1 year ago

I found a workaround for the issue by using react-helmet to inject the mdxeditor style into the style in the page's head. I have little experience with frontend dev, and I am not proud of this solution, yet it works. I am open to any suggestions.

More specifically, below you can see at the top of my js component mentioned earlier, I added the following snippet to inject the mdxeditor into the style component of the page's head:

import {mdEditorStyle} from "./MdEditorStyle";

...

<Box id={props.uniqueId} sx={{ p: "1px", width: 1 }}>
     <Helmet>
         <style type="text/css">{mdEditorStyle}</style>
    </Helmet>
    <MDXEditor
        markdown={value}
        onChange={onChange}
    ...

The ./MdEditorStyle file contains a string with the CSS from the @mdxeditor/editor/style.css:

export const mdEditorStyle = `
:root, .light-theme {
  --blue1: hsl(206, 100%, 99.2%);
  --blue2: hsl(210, 100%, 98.0%);
  --blue3: hsl(209, 100%, 96.5%);
...
`

I hope this helps

petyosi commented 1 year ago

When I run it after building in a docker container, it looks like this:

@HWilliams64 this sounds like a build step issue. Your workaround is dangerous; the package may update its CSS contents, breaking your app in subtle ways. I may suggest that you debug what happens in the production step using your web inspector.

petyosi commented 1 year ago

For anyone who finds this issue and experiences something similar: there's nothing special about the MDXEditor styling setup - it boils down to including a style file from an NPM package. The style file is declared as an export field. All modern bundlers understand that.

The mdx-editor organization includes several sample setups in Next.js (both routers), CRA, and Vite. If you have problems with the styling, compare your setup with the respective repo. If you still have troubles, isolate your problem down to a simple reproduction in a public repo and open a new issue. I should be able to help you if I can reproduce the problem on my side.

I am going to lock this issue as it turns into a catch-all problem of unrelated reports.