{"id":19335323,"url":"https://github.com/ericadamski/react-markdown-menu","last_synced_at":"2025-08-13T11:48:12.269Z","repository":{"id":57140745,"uuid":"121384253","full_name":"ericadamski/react-markdown-menu","owner":"ericadamski","description":"A small medium-esk react component to allow editing markdown files","archived":false,"fork":false,"pushed_at":"2018-05-03T18:27:21.000Z","size":500,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2025-07-26T21:26:58.677Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ericadamski.github.io/react-markdown-menu/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericadamski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-02-13T13:14:12.000Z","updated_at":"2018-05-03T18:27:22.000Z","dependencies_parsed_at":"2022-09-05T01:30:47.574Z","dependency_job_id":null,"html_url":"https://github.com/ericadamski/react-markdown-menu","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ericadamski/react-markdown-menu","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericadamski%2Freact-markdown-menu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericadamski%2Freact-markdown-menu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericadamski%2Freact-markdown-menu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericadamski%2Freact-markdown-menu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericadamski","download_url":"https://codeload.github.com/ericadamski/react-markdown-menu/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericadamski%2Freact-markdown-menu/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270235075,"owners_count":24550175,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-13T02:00:09.904Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2024-11-10T03:07:03.020Z","updated_at":"2025-08-13T11:48:12.216Z","avatar_url":"https://github.com/ericadamski.png","language":"JavaScript","readme":"[![Build Status](https://travis-ci.org/ericadamski/react-markdown-menu.svg?branch=master)](https://travis-ci.org/ericadamski/react-markdown-menu)\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n\n# react-markdown-menu\n\nA small medium-esk react component to allow editing markdown files\n\n# Install\n\n`npm install react-markdown-menu`\n\nor\n\n`yarn add react-markdown-menu`\n\n# Usage\n\nWhile the component is intended to be used as a compound component until it has been tested the main usage will be through the base `MarkdownMenu` component\n\n```javascript\nclass MarkdownMenu extends Component {\n  static propTypes = {\n    // The number of pixels from the left of the browser to place the menu\n    x: PropTypes.number,\n    // The number of pixels from the top of the browser to place the menu\n    y: PropTypes.number,\n    // A function that is called whenever a button on the menu is clicked.\n    // The function is of the type:\n    // function onChange(modifiedText: string, line: boolean): undefined\n    // When a button is clicked to modified text is sent to the onChange,\n    // and a boolean to say whether to replace the entire line or\n    // just the selected area\n    onChange: PropTypes.func.isRequired,\n    // The text selection of the entire line the cursor is currently on\n    lineSelection: PropTypes.string,\n    // The text selection of whatever is curretly highlighted\n    selection: PropTypes.string,\n  };\n\n  /* the rest of the class */\n}\n```\n\nBelow is an example using a textarea as the editor\n\n```javascript\nimport React from 'react';\nimport { render, findDOMNode } from 'react-dom';\nimport { MarkdownMenu } from 'react-markdown-menu';\n\nclass App extends React.Component {\n  state = {};\n\n  getLineRange(value, selectionEnd) {\n    let length = 0;\n\n    const lines = value.split('\\n').map(str =\u003e (length += str.length + 1));\n\n    const max = lines.filter(l =\u003e l \u003c selectionEnd);\n\n    const start = max[max.length - 1] || 0;\n    const end = lines[max.length] - 1;\n\n    return [start, end];\n  }\n\n  componentDidMount() {\n    // Setup event listeners\n    if (this.textareaRef) {\n      const textarea = (this.textarea = findDOMNode(this.textareaRef));\n      const menu = findDOMNode(this.menu);\n\n      console.log(menu);\n\n      const hideMenu = () =\u003e\n        this.setState({ selection: null, lineSelection: null });\n\n      // Hide then menu on scroll of the window.\n      document.addEventListener('scroll', hideMenu);\n\n      // Hide the menu on clicking outside of the menu\n      document.addEventListener(\n        'mousedown',\n        ({ target }) =\u003e !menu.contains(target) \u0026\u0026 hideMenu()\n      );\n\n      // Update the selected text and position of the menu\n      let clickEvent;\n      // Keep track of the click position to know where to place the menu\n      textarea.addEventListener('mousedown', event =\u003e (clickEvent = event));\n      // Handle text and line selection\n      textarea.addEventListener('select', event =\u003e {\n        const { value, selectionStart, selectionEnd } = textarea;\n        const { clientX, clientY } = clickEvent;\n        console.log(clickEvent);\n\n        const [lineStart, lineEnd] = this.getLineRange(value, selectionEnd);\n\n        console.log(this);\n\n        this.setState({\n          x: clientY,\n          y: clientX,\n          lineRange: [lineStart, lineEnd],\n          selectionRange: [selectionStart, selectionEnd],\n          lineSelection: value.substring(lineStart, lineEnd),\n          selection: value.substring(selectionStart, selectionEnd),\n        });\n      });\n    }\n  }\n\n  onChange(text, line) {\n    const { selectionRange, lineRange } = this.state;\n\n    const [start, end] = line ? lineRange : selectionRange;\n\n    this.textarea.value = `${this.textarea.value.slice(\n      0,\n      start\n    )}${text}${this.textarea.value.slice(end)}`;\n\n    this.textarea.setSelectionRange(start, end + (text.length - end - start));\n  }\n\n  render() {\n    const { x, y, selection, lineSelection } = this.state;\n\n    return (\n      \u003cdiv\u003e\n        \u003cMarkdownMenu\n          ref={node =\u003e (this.menu = node)}\n          x={x}\n          y={y}\n          onChange={this.onChange.bind(this)}\n          selection={selection}\n          lineSelection={lineSelection}\n        /\u003e\n        \u003ctextarea ref={node =\u003e (this.textareaRef = node)} /\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nrender(\u003cApp /\u003e, document.getElementById('root'));\n```\n\n[![Edit 4jv2612w4x](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/4jv2612w4x)\n\nThe **experimental** compound component example that hasn't been completely tested and the API not yet refined.\n\n```javascript\nimport React from 'react';\nimport { render } from 'react-dom';\nimport { Editor } from 'react-markdown-menu';\n\nclass App extends React.Component {\n  getLineRange(value, selectionEnd) {\n    let length = 0;\n\n    const lines = value.split('\\n').map(str =\u003e (length += str.length + 1));\n\n    const max = lines.filter(l =\u003e l \u003c selectionEnd);\n\n    const start = max[max.length - 1] || 0;\n    const end = lines[max.length] - 1;\n\n    return [start, end];\n  }\n\n  getSelectionRange(element) {\n    const { selectionStart, selectionEnd } = element;\n\n    return [selectionStart, selectionEnd];\n  }\n\n  updateText(element, text, [start, end]) {\n    element.value = `${element.value.slice(\n      0,\n      start\n    )}${text}${element.value.slice(end)}`;\n  }\n\n  updateSelection(element, text, [start, end]) {\n    element.setSelectionRange(start, end + (text.length - end - start));\n  }\n\n  render() {\n    return (\n      \u003cEditor\n        onChange={console.log}\n        updateText={this.updateText}\n        updateSelection={this.updateSelection}\n        getSelectionRange={this.getSelectionRange}\n        onChangeSelection={(element, update) =\u003e\n          element.addEventListener('select', update)\n        }\n        getLineRange={this.getLineRange}\n        render={() =\u003e \u003ctextarea /\u003e}\n      /\u003e\n    );\n  }\n}\n\nrender(\u003cApp /\u003e, document.getElementById('root'));\n```\n\n[![Edit 52nnq4pxml](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/52nnq4pxml)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericadamski%2Freact-markdown-menu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericadamski%2Freact-markdown-menu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericadamski%2Freact-markdown-menu/lists"}