{"id":49249843,"url":"https://github.com/wingedrasengan927/medium-editor","last_synced_at":"2026-04-24T23:36:31.627Z","repository":{"id":289853040,"uuid":"944332646","full_name":"wingedrasengan927/medium-editor","owner":"wingedrasengan927","description":"A medium clone built with lexical","archived":false,"fork":false,"pushed_at":"2026-04-22T14:22:33.000Z","size":9904,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-22T15:31:25.978Z","etag":null,"topics":["editor","lexical","medium","medium-clone","text-editor"],"latest_commit_sha":null,"homepage":"https://medium-editor-lmr5y.ondigitalocean.app/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wingedrasengan927.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-07T06:57:11.000Z","updated_at":"2026-04-22T13:54:55.000Z","dependencies_parsed_at":"2025-04-25T12:23:17.075Z","dependency_job_id":"78549c8f-38f2-444b-b7c2-2976d7801fc8","html_url":"https://github.com/wingedrasengan927/medium-editor","commit_stats":null,"previous_names":["wingedrasengan927/medium-editor"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/wingedrasengan927/medium-editor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wingedrasengan927%2Fmedium-editor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wingedrasengan927%2Fmedium-editor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wingedrasengan927%2Fmedium-editor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wingedrasengan927%2Fmedium-editor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wingedrasengan927","download_url":"https://codeload.github.com/wingedrasengan927/medium-editor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wingedrasengan927%2Fmedium-editor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32245151,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["editor","lexical","medium","medium-clone","text-editor"],"created_at":"2026-04-24T23:36:31.164Z","updated_at":"2026-04-24T23:36:31.620Z","avatar_url":"https://github.com/wingedrasengan927.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# An Open Source Medium clone built with Lexical.\nPlease refer to the [guide](https://medium-editor-lmr5y.ondigitalocean.app/) for more details.\n\n## Installation\n\nThe first step is to install peer dependencies in your project. This package assumes you are working within a React environment, so `react` and `react-dom` should ideally already be part of your project setup. The primary peer dependencies you need to install are:\n\n* `@lexical/react`: \"\u003e=0.39.0\"\n* `@tabler/icons-react`: \"\u003e=3.31.0\"\n* `lexical`: \"\u003e=0.39.0\"\n* `react-aria-components`: \"\u003e=1.8.0\"\n\nYou can install them using npm. Adjust the versions if needed to match your project's requirements, ensuring they meet the minimum versions specified above. The command to add to install the peer dependencies using npm is:\n\n```bash\nnpm install @lexical/react @tabler/icons-react lexical react-aria-components\n```\n\nThere are other dependencies as well which are listed in the `package.json` file. However, they will be automatically installed by npm. You don't need to manually install them.\n\nOnce the peer dependencies are installed, you can proceed to install the main package. To install using npm, the command is:\n\n```bash\nnpm install lexical-medium-editor@latest\n```\n\n## Usage\n\nOnce the setup is done, you can create and use an `Editor` component that closely resembles this page as follows:\n\nFirst create an `Editor` component and the associated css as follows:\n\n- `src`\n  - `components`\n    - `Editor.jsx`\n    - `editor_styles.css`\n  - `App.jsx`\n\nThe `Editor.jsx` file should look like this:\n\n```jsx\nimport { useRef } from \"react\";\nimport { Button } from \"react-aria-components\";\nimport { $getRoot } from \"lexical\";\nimport { $generateHtmlFromNodes } from \"@lexical/html\";\nimport LexicalEditor from \"lexical-medium-editor\";\nimport { initialConfig } from \"lexical-medium-editor/config\";\nimport \"lexical-medium-editor/styles.css\";\nimport \"./editor_styles.css\";\n\nfunction Navbar({ onCopyHTML, onCopyJSON, onCopyText }) {\n  return (\n    \u003cnav className=\"navbar\"\u003e\n      \u003cdiv className=\"navbar-links\" /\u003e\n      \u003cdiv className=\"action-grp\"\u003e\n        \u003cButton className=\"navbar-btn\" onPress={onCopyHTML}\u003e\n          Copy HTML\n        \u003c/Button\u003e\n        \u003cButton className=\"navbar-btn\" onPress={onCopyJSON}\u003e\n          Copy JSON\n        \u003c/Button\u003e\n        \u003cButton className=\"navbar-btn\" onPress={onCopyText}\u003e\n          Copy Text\n        \u003c/Button\u003e\n      \u003c/div\u003e\n    \u003c/nav\u003e\n  );\n}\n\nexport default function Editor() {\n  const editorStateRef = useRef(null);\n  const editorRef = useRef(null);\n\n  const handleOnChange = (editorState) =\u003e {\n    editorStateRef.current = editorState;\n  };\n\n  const copyToClipboard = async (text) =\u003e {\n    await navigator.clipboard.writeText(text);\n  };\n\n  const handleCopyHTML = () =\u003e {\n    const editor = editorRef.current;\n    if (editor) {\n      editor.read(() =\u003e {\n        const htmlString = $generateHtmlFromNodes(editor, null);\n        copyToClipboard(htmlString);\n      });\n    }\n  };\n\n  const handleCopyJSON = () =\u003e {\n    if (editorStateRef.current) {\n      const jsonString = JSON.stringify(editorStateRef.current.toJSON(), null, 2);\n      copyToClipboard(jsonString);\n    }\n  };\n\n  const handleCopyText = () =\u003e {\n    if (editorStateRef.current) {\n      editorStateRef.current.read(() =\u003e {\n        const textContent = $getRoot().getTextContent();\n        copyToClipboard(textContent);\n      });\n    }\n  };\n\n  return (\n    \u003c\u003e\n      \u003cNavbar\n        onCopyHTML={handleCopyHTML}\n        onCopyJSON={handleCopyJSON}\n        onCopyText={handleCopyText}\n      /\u003e\n      \u003cdiv className=\"editor-wrapper\"\u003e\n        \u003cLexicalEditor\n          initialConfig={initialConfig}\n          onChange={handleOnChange}\n          editorRef={editorRef}\n          blockToolbarGap={32}\n          isHeadingOneFirst={false}\n          spellCheck={false}\n        /\u003e\n      \u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n```\n\nThe `editor_styles.css` file should look like this:\n\n```css\n.editor-wrapper {\n  margin: 0 auto;\n  width: 50%;\n}\n\n@media (max-width: 1200px) {\n  .editor-wrapper {\n    width: 65%;\n  }\n}\n\n@media (max-width: 992px) {\n  .editor-wrapper {\n    width: 75%;\n  }\n}\n\n@media (max-width: 768px) {\n  .editor-wrapper {\n    width: 85%;\n  }\n}\n\n.navbar {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 8px 16px;\n  background-color: white;\n  border-bottom: 4px solid #fff34e;\n  margin-bottom: 32px;\n  height: 64px;\n  box-sizing: border-box;\n}\n\n.action-grp {\n  gap: 8px;\n  display: flex;\n}\n\n.navbar-btn {\n  color: var(--text-color-dark);\n  background: var(--button-background-dark);\n  border: 1px solid var(--border-color-dark);\n  border-radius: 4px;\n  appearance: none;\n  vertical-align: middle;\n  font-size: 1rem;\n  text-align: center;\n  margin: 0;\n  outline: none;\n  padding: 6px 10px;\n  text-decoration: none;\n  font-family: \"firacode\", monospace;\n  cursor: pointer;\n\n  \u0026[data-pressed] {\n    box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);\n    background: var(--button-background-pressed-dark);\n    border-color: var(--border-color-pressed-dark);\n  }\n\n  \u0026[data-focus-visible] {\n    outline: 2px solid var(--focus-ring-color-dark);\n    outline-offset: -1px;\n  }\n}\n\n@media (max-width: 576px) {\n  .navbar {\n    padding: 8px;\n    margin-bottom: 16px;\n  }\n\n  .action-grp {\n    gap: 4px;\n  }\n\n  .navbar-btn {\n    padding: 4px 8px;\n    font-size: 0.875rem;\n  }\n\n\n  .editor-wrapper {\n    width: 95%;\n  }\n}\n\n@media (max-width: 400px) {\n  .navbar {\n    flex-direction: column;\n    height: auto;\n    padding: 8px;\n  }\n\n  .action-grp {\n    margin-top: 8px;\n    width: 100%;\n    justify-content: space-between;\n  }\n\n  .editor-wrapper {\n    width: 100%;\n  }\n}\n```\n\nNow you can import and use this component into your main `App.jsx` component and use it like so:\n\n```jsx\nimport Editor from \"./components/Editor\";\nimport \"./App.css\";\n\nfunction App() {\n  return (\n    \u003cdiv className=\"app-container\"\u003e\n      \u003cEditor /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n\nOne final change. To use LaTeX, you need to load MathJax v4 into your `index.html` file. This can be done by adding the following tags:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003clink rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n    \u003ctitle\u003emedium-blog-editor\u003c/title\u003e\n    \u003cscript\u003e\n      window.MathJax = {\n        tex: {\n          inlineMath: [\n            [\"$\", \"$\"],\n            [\"\\\\(\", \"\\\\)\"],\n          ],\n          displayMath: [\n            [\"$$\", \"$$\"],\n            [\"\\\\[\", \"\\\\]\"],\n          ],\n        },\n        chtml: {\n          displayOverflow: \"scroll\",\n          linebreaks: {\n            inline: true,\n          },\n        },\n        startup: {\n          typeset: false,\n        },\n      };\n    \u003c/script\u003e\n    \u003cscript\n      type=\"text/javascript\"\n      id=\"MathJax-script\"\n      async\n      src=\"https://cdn.jsdelivr.net/npm/mathjax@4.1.0/tex-mml-chtml.js\"\n    \u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n    \u003cscript type=\"module\" src=\"/src/main.jsx\"\u003e\u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThat's it. You're ready to go!\n\nNote that this was tested on vite build. It was not thoroughly tested for each scenario. So there's a good chance that you'll encounter errors. If you do, please email them to me at ms.neerajkrishna@gmail.com or you can also raise an issue in the GitHub repository.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwingedrasengan927%2Fmedium-editor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwingedrasengan927%2Fmedium-editor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwingedrasengan927%2Fmedium-editor/lists"}