{"id":13393430,"url":"https://github.com/bustle/mobiledoc-kit","last_synced_at":"2025-05-15T04:00:25.158Z","repository":{"id":18498889,"uuid":"21694966","full_name":"bustle/mobiledoc-kit","owner":"bustle","description":"A toolkit for building WYSIWYG editors with Mobiledoc","archived":false,"fork":false,"pushed_at":"2024-11-14T16:39:06.000Z","size":10591,"stargazers_count":1562,"open_issues_count":49,"forks_count":152,"subscribers_count":58,"default_branch":"master","last_synced_at":"2025-05-08T03:50:41.557Z","etag":null,"topics":["javascript","mobiledoc","mobiledoc-editor","mobiledoc-kit","wysiwyg-editor"],"latest_commit_sha":null,"homepage":"https://bustle.github.io/mobiledoc-kit/demo/","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/bustle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-07-10T13:22:19.000Z","updated_at":"2025-04-30T06:13:35.000Z","dependencies_parsed_at":"2024-01-07T05:59:00.101Z","dependency_job_id":"fa882fc7-0a63-4500-8f02-dfebead62c73","html_url":"https://github.com/bustle/mobiledoc-kit","commit_stats":{"total_commits":992,"total_committers":54,"mean_commits":18.37037037037037,"dds":0.564516129032258,"last_synced_commit":"942bc726bfe9acb132437adf7f177945903724e5"},"previous_names":["bustlelabs/content-kit-editor","bustlelabs/mobiledoc-kit"],"tags_count":118,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bustle%2Fmobiledoc-kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bustle%2Fmobiledoc-kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bustle%2Fmobiledoc-kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bustle%2Fmobiledoc-kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bustle","download_url":"https://codeload.github.com/bustle/mobiledoc-kit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254227222,"owners_count":22035659,"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","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":["javascript","mobiledoc","mobiledoc-editor","mobiledoc-kit","wysiwyg-editor"],"created_at":"2024-07-30T17:00:52.605Z","updated_at":"2025-05-15T04:00:25.086Z","avatar_url":"https://github.com/bustle.png","language":"JavaScript","readme":"# Mobiledoc Kit [![CI Build Status](https://github.com/bustle/mobiledoc-kit/workflows/CI/badge.svg)](https://github.com/bustle/mobiledoc-kit/actions?query=workflow%3ACI)\n\n\u003cimg width=\"300\" src=\"https://bustle.github.io/mobiledoc-kit/demo/mobiledoc-logo-color-small.png\" alt=\"Mobiledoc Logo\"/\u003e\n\nMobiledoc Kit is a framework-agnostic library for building WYSIWYG editors\nsupporting rich content via cards.\n\n## Libraries\n\nThis repository hosts the core Mobiledoc Kit library. If you want to use Mobiledoc Kit to _create a WYSIWYG editor_ you have the following options:\n\n| Environment      | Library                                                                      |\n| ---------------- | ---------------------------------------------------------------------------- |\n| Plain JavaScript | [mobiledoc-kit](https://github.com/bustle/mobiledoc-kit) (this repo)         |\n| Ember            | [ember-mobiledoc-editor](https://github.com/bustle/ember-mobiledoc-editor)   |\n| React            | [react-mobiledoc-editor](https://github.com/upworthy/react-mobiledoc-editor) |\n\nIf you only want to use the Mobiledoc-Kit runtime, for _rendering mobiledoc posts only_ (not editing or creating them), you can use:\n\n| Output Format/Environment                                                           | Library                                                                                                                |\n| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| Plain JavaScript In-Browser (DOM)                                                   | [mobiledoc-dom-renderer](https://github.com/bustle/mobiledoc-dom-renderer)                                             |\n| Server-Side Rendering (HTML)                                                        | see [mobiledoc-dom-renderer's Rendering HTML section](https://github.com/bustle/mobiledoc-dom-renderer#rendering-html) |\n| Server-Side Rendering (Text-only, e.g. SEO)                                         | [mobiledoc-text-renderer](https://github.com/bustle/mobiledoc-text-renderer)                                           |\n| In-Browser (DOM) Rendering, with Ember                                              | [ember-mobiledoc-dom-renderer](https://github.com/bustle/ember-mobiledoc-dom-renderer)                                 |\n| React Server and Browser Renderer                                                   | [mobiledoc-react-renderer](https://github.com/dailybeast/mobiledoc-react-renderer)                                     |\n| 🔮 Render Mobiledoc as VDOM by passing React or React-like `createElement` function | [mobiledoc-vdom-renderer](https://github.com/bustle/mobiledoc-vdom-renderer)                                           |\n\nMobiledoc is a deliberately simple and terse format, and you are encouraged to write your own renderer if you have other target output formats (e.g., a PDF renderer, an iOS Native Views Renderer, etc.).\n\nOther 3rd Party Libraries:\n\n| Environment      | Library                                                                      |\n| ---------------- | ---------------------------------------------------------------------------- |\n| Python           | [mobiledoc-py](https://github.com/SuhJae/mobiledoc-py)         |\n\n\n## Demo\n\nTry a demo at [bustle.github.io/mobiledoc-kit/demo](https://bustle.github.io/mobiledoc-kit/demo/).\n\n## API Documentation\n\nAPI Documentation is [available online](http://bustle.github.io/mobiledoc-kit/demo/docs/).\n\n## Intro to Mobiledoc Kit\n\n- Posts are serialized to a JSON format called **Mobiledoc** instead of to\n  HTML. Mobiledoc can be rendered for the web, mobile web, or in theory on any\n  platform. Mobiledoc is portable and fast.\n- The editor makes limited use of Content Editable, the siren-song of doomed\n  web editor technologies.\n- Mobiledoc is designed for _rich_ content. We call rich sections of an\n  article \"cards\" and rich inline elements \"atoms\" and implementing a new one doesn't require an understanding\n  of Mobiledoc editor internals. Adding a new atom or card takes an afternoon, not several\n  days. To learn more, see the docs for\n  **[Atoms](https://github.com/bustle/mobiledoc-kit/blob/master/ATOMS.md)**,\n  **[Cards](https://github.com/bustle/mobiledoc-kit/blob/master/CARDS.md)**\n  and\n  **[Mobiledoc Renderers](https://github.com/bustle/mobiledoc-kit/blob/master/RENDERERS.md)**\n\nTo learn more about the ideas behind Mobiledoc and the editor (note that the\neditor used to be named Content-Kit), see these blog posts:\n\n- [The Content-Kit announcement post](http://madhatted.com/2015/7/31/announcing-content-kit-and-mobiledoc).\n- [Building the Content-Kit Editor on Content Editable](https://medium.com/@bantic/building-content-kit-editor-on-contenteditable-99a94871c951)\n- [Content-Kit: Programmatic Editing](http://madhatted.com/2015/8/25/content-kit-programmatic-editing)\n\nThe Mobiledoc kit saves posts in\n**[Mobiledoc format](https://github.com/bustle/mobiledoc-kit/blob/master/MOBILEDOC.md)**.\n\n### Usage\n\nThe `Mobiledoc.Editor` class is invoked with an element to render into and\noptionally a Mobiledoc to load. For example:\n\n```js\nconst simpleMobiledoc = {\n  version: '0.3.2',\n  markups: [],\n  atoms: [],\n  cards: [],\n  sections: [[1, 'p', [[0, [], 0, 'Welcome to Mobiledoc']]]],\n}\nconst element = document.querySelector('#editor')\nconst options = { mobiledoc: simpleMobiledoc }\nconst editor = new Mobiledoc.Editor(options)\neditor.render(element)\n```\n\n`options` is an object which may include the following properties:\n\n- `mobiledoc` - [object] A mobiledoc object to load and edit.\n- `placeholder` - [string] default text to show before a user starts typing.\n- `spellcheck` - [boolean] whether to enable spellcheck. Defaults to true.\n- `autofocus` - [boolean] When true, focuses on the editor when it is rendered.\n- `undoDepth` - [number] How many undo levels should be available. Default\n  value is five. Set this to zero to disable undo/redo.\n- `cards` - [array] The list of cards that the editor may render\n- `atoms` - [array] The list of atoms that the editor may render\n- `cardOptions` - [object] Options passed to cards and atoms\n- `unknownCardHandler` - [function] This will be invoked by the editor-renderer\n  whenever it encounters an unknown card\n- `unknownAtomHandler` - [function] This will be invoked by the editor-renderer\n  whenever it encounters an unknown atom\n- `parserPlugins` - [array] See [DOM Parsing Hooks](https://github.com/bustle/mobiledoc-kit#dom-parsing-hooks)\n- `tooltipPlugin` - [object] Optional plugin for customizing tooltip appearance\n\nThe editor leverages unicode characters, so HTML documents must opt in to\nUTF8. For example this can be done by adding the following to an HTML\ndocument's `\u003chead\u003e`:\n\n```html\n\u003cmeta charset=\"utf-8\" /\u003e\n```\n\n### Editor API\n\n- `editor.serialize(version=\"0.3.2\")` - serialize the current post for persistence. Returns\n  Mobiledoc.\n- `editor.destroy()` - teardown the editor event listeners, free memory etc.\n- `editor.disableEditing()` - stop the user from being able to edit the\n  current post with their cursor. Programmatic edits are still allowed.\n- `editor.enableEditing()` - allow the user to make edits directly\n  to a post's text.\n- `editor.editCard(cardSection)` - change the card to its edit mode (will change\n  immediately if the card is already rendered, or will ensure that when the card\n  does get rendered it will be rendered in the \"edit\" state initially)\n- `editor.displayCard(cardSection)` - same as `editCard` except in display mode.\n- `editor.range` - Read the current Range object for the cursor.\n\n### Position API\n\nA `Position` object represents a location in a document. For example your\ncursor may be at a position, text may be inserted at a position, and a range\nhas a starting position and an ending position.\n\nPosition objects are returned by several APIs, for example `deleteRange` returns\na position. Some methods, like `splitSection` accept a position as an argument.\n\nA position can be created for any point in a document with\n`section#toPosition(offset)`.\n\nPosition API includes:\n\n- `position.section` - The section of this position\n- `position.offset` - The character offset of this position in the section.\n- `position.marker` - Based on the section and offset, the marker this position\n  is on. A position may not always have a marker (for example a cursor before\n  or after a card).\n- `position.toRange(endPosition)` - Create a range based on two positions. Accepts\n  the direction of the range as a second optional argument.\n- `position.isEqual(otherPosition)` - Is this position the same as another\n- `position.move(characterCount)` - Move a number of characters to the right\n  (positive number) or left (negative number)\n- `position.moveWord(direction)` - Move a single word in a given direction.\n\n### Range API\n\n`Range` represents a range of a document. A range has a starting position\n(`head`), ending position (`tail`), and a direction (for example highlighting\ntext left-to-right is a forward direction, highlighting right-to-left is a\nbackward direction).\n\nRanges are returned by several APIs, but most often you will be interested in\nthe current range selected by the user (be it their cursor or an actual\nselection). This can be accessed at `editor#range`. Several post editor APIs\nexpect a range as an argument, for example `setRange` or `deleteRange`.\n\nRanges sport several public APIs for manipulation, each of which returns a new,\nunique range instance:\n\n- `range.head` - The position on the range closer to the start of the document.\n- `range.tail` - The position on the range closer to the end of the document.\n- `range.isCollapsed` - A range is collapsed when its head and tail are the same\n  position.\n- `range.focusedPosition` - If a range has a forward direction, then tail. If\n  it has a backward direction, then head.\n- `range.extend(characterCount)` - Grow a range one character in whatever its\n  direction is.\n- `range.move(direction)` - If the range is collapsed, move the range forward\n  one character. If it is not, collapse it in the direction passed.\n- `range.expandByMarker(callback)` - In both directions attempt grow the\n  range as long as `callback` returns true. `callback` is passed each marker\n  as the range is grown.\n\n### Editor Lifecycle Hooks\n\nAPI consumers may want to react to given interaction by the user (or by\na programmatic edit of the post). Lifecycle hooks provide notification\nof change and opportunity to edit the post where appropriate.\n\nRegister a lifecycle hook by calling the hook name on the editor with a\ncallback function. For example:\n\n```js\neditor.didUpdatePost(postEditor =\u003e {\n  let { range } = editor\n  let cursorSection = range.head.section\n\n  if (cursorSection.text === 'add-section-when-i-type-this') {\n    let section = editor.builder.createMarkupSection('p')\n    postEditor.insertSectionBefore(section, cursorSection.next)\n    postEditor.setRange(new Mobiledoc.Range(section.headPosition))\n  }\n})\n```\n\nThe available lifecycle hooks are:\n\n- `editor.didUpdatePost(postEditor =\u003e {})` - An opportunity to use the\n  `postEditor` and possibly change the post before rendering begins.\n- `editor.willRender()` - After all post mutation has finished, but before\n  the DOM is updated.\n- `editor.didRender()` - After the DOM has been updated to match the\n  edited post.\n- `editor.willDelete((range, direction, unit))` - Provides `range`, `direction` and `unit` to identify the coming deletion.\n- `editor.didDelete((range, direction, unit))` - Provides `range`, `direction` and `unit` to identify the completed deletion.\n- `editor.cursorDidChange()` - When the cursor (or selection) changes as a result of arrow-key\n  movement or clicking in the document.\n- `editor.onTextInput()` - When the user adds text to the document (see [example](https://github.com/bustlelabs/mobiledoc-kit#responding-to-text-input))\n- `editor.inputModeDidChange()` - The active section(s) or markup(s) at the current cursor position or selection have changed. This hook can be used with `Editor#activeMarkups`, `Editor#activeSections`, and `Editor#activeSectionAttributes` to implement a custom toolbar.\n- `editor.beforeToggleMarkup(({markup, range, willAdd}) =\u003e {...})` - Register a\n  callback that will be called before `editor#toggleMarkup` is applied. If any\n  callback returns literal `false`, the toggling of markup will be canceled.\n  (Toggling markup done via the postEditor, e.g. `editor.run(postEditor =\u003e postEditor.toggleMarkup(...))` will skip this callback.\n- `editor.willCopy(({html, text, mobiledoc}) =\u003e {...})` - Called before the\n  serialized versions of the selected markup is copied to the system\n  pasteboard.\n  `editor.willPaste(({html, text, mobiledoc}) =\u003e {...})` - Called before the\n  serialized versions of the system pasteboard is pasted into the mobiledoc.\n\nFor more details on the lifecycle hooks, see the [Editor documentation](https://bustle.github.io/mobiledoc-kit/demo/docs/Editor.html).\n\n### Programmatic Post Editing\n\nA major goal of the Mobiledoc kit is to allow complete customization of user\ninterfaces using the editing surface. The programmatic editing API allows\nthe creation of completely custom interfaces for buttons, hot-keys, and\nother interactions.\n\nTo change the post in code, use the `editor.run` API. For example, the\nfollowing usage would mark currently selected text as \"strong\":\n\n```js\neditor.run(postEditor =\u003e {\n  postEditor.toggleMarkup('strong')\n})\n```\n\nIt is important that you make changes to posts, sections, and markers through\nthe `run` and `postEditor` API. This API allows the Mobiledoc editor to conserve\nand better understand changes being made to the post.\n\n```js\neditor.run(postEditor =\u003e {\n  const mention = postEditor.builder.createAtom('mention', 'Jane Doe', { id: 42 })\n  // insert at current cursor position:\n  // or should the user have to grab the current position from the editor first?\n  postEditor.insertMarkers(editor.range.head, [mention])\n})\n```\n\nFor more details on the API of `postEditor`, see the [API documentation](https://github.com/bustle/mobiledoc-kit/blob/master/src/js/editor/post.ts).\n\nFor more details on the API for the builder, required to create new sections\natoms, and markers, see the [builder API](https://github.com/bustle/mobiledoc-kit/blob/master/src/js/models/post-node-builder.ts).\n\n### Configuring hot keys\n\nThe Mobiledoc editor allows the configuration of hot keys and text expansions.\nFor instance, the hot-key command-B to make selected text bold, is registered\ninternally as:\n\n```js\nconst boldKeyCommand = {\n  str: 'META+B',\n  run(editor) {\n    editor.run(postEditor =\u003e postEditor.toggleMarkup('strong'))\n  },\n}\neditor.registerKeyCommand(boldKeyCommand)\n```\n\nAll key commands must have `str` and `run` properties as shown above.\n\n`str` describes the key combination to use and may be a single key, or modifier(s) and a key separated by `+`, e.g.: `META+K` (cmd-K), `META+SHIFT+K` (cmd-shift-K)\n\nModifiers can be any of `CTRL`, `META`, `SHIFT`, or `ALT`.\n\nThe key can be any of the alphanumeric characters on the keyboard, or one of the following special keys:\n\n`BACKSPACE`, `TAB`, `ENTER`, `ESC`, `SPACE`, `PAGEUP`, `PAGEDOWN`, `END`, `HOME`, `LEFT`, `UP`, `RIGHT`, `DOWN`, `INS`, `DEL`\n\n#### Overriding built-in keys\n\nYou can override built-in behavior by simply registering a hot key with the same name.\nFor example, to submit a form instead of entering a new line when `enter` is pressed you could do the following:\n\n```js\nconst enterKeyCommand = {\n  str: 'enter',\n  run(editor) {\n    // submit the form\n  },\n}\neditor.registerKeyCommand(enterKeyCommand)\n```\n\nTo fall-back to the default behavior, return `false` from `run`.\n\n### Responding to text input\n\nThe editor exposes a hook `onTextInput` that can be used to programmatically react\nto text that the user enters. Specify a handler object with `text` or `match`\nproperties and a `run` callback function, and the editor will invoke the callback\nwhen the text before the cursor ends with `text` or matches `match`.\nThe callback is called after the matching text has been inserted. It is passed\nthe `editor` instance and an array of matches (either the result of `match.exec`\non the matching user-entered text, or an array containing only the `text`).\n\n```js\neditor.onTextInput({\n  text: 'X',\n  run(editor) {\n    // This callback is called after user types 'X'\n  },\n})\n\neditor.onTextInput({\n  match: /\\d\\dX$/, // Note the \"$\" end anchor\n  run(editor) {\n    // This callback is called after user types number-number-X\n  },\n})\n```\n\nThe editor has several default text input handlers that are defined in\n`src/js/editor/text-input-handlers.js`.\n\nTo remove default text input handlers call the unregister function.\n\n```js\neditor.unregisterAllTextInputHandlers()\n```\n\n#### `\\n` special-case match character\n\nWhen writing a matching string it is common to use `\\s` at the end of a match\nregex, thus triggering the handler for a given string when the users presses\nthe space or tab key.\n\nWhen the enter key is pressed no actual characters are added to a document.\nInstead a new section is created following the current section. Despite this,\nyou may use `\\n` in a match regex to capture moments when the enter key is\npressed. For example if you wanted to process a URL for auto-linking you\nmight want to process the string on both the space key and when the user hits\nenter.\n\nSince `\\s` is a superset of `\\n`, that makes the following regex a valid match\nfor a hand-typed URL after the user presses space or enter:\n\n```regex\n/\\b(https?:\\/\\/[^\\s]+)\\s$/\n```\n\n### DOM Parsing hooks\n\nA developer can override the default parsing behavior for leaf DOM nodes in\npasted HTML.\n\nFor example, when an `img` tag is pasted it may be appropriate to\nfetch that image, upload it to an authoritative source, and create a specific\nkind of image card with the new URL in its payload.\n\nA demonstration of this:\n\n```js\nfunction imageToCardParser(node, builder, { addSection, addMarkerable, nodeFinished }) {\n  if (node.nodeType !== 1 || node.tagName !== 'IMG') {\n    return\n  }\n  const payload = { src: node.src }\n  const cardSection = builder.createCardSection('my-image', payload)\n  addSection(cardSection)\n  nodeFinished()\n}\nconst options = {\n  parserPlugins: [imageToCardParser],\n}\nconst editor = new Mobiledoc.Editor(options)\nconst element = document.querySelector('#editor')\neditor.render(element)\n```\n\nParser hooks are called with three arguments:\n\n- `node` - The node of DOM being parsed. This may be a text node or an element.\n- `builder` - The abstract model builder.\n- `env` - An object containing three callbacks to modify the abstract\n  - `addSection` - Close the current section and add a new one\n  - `addMarkerable` - Add a markerable (marker or atom) to the current section\n  - `nodeFinished` - Bypass all remaining parse steps for this node\n\nNote that you _must_ call `nodeFinished` to stop a DOM node from being\nparsed by the next plugin or the default parser.\n\n### Tooltip Plugins\n\nDevelopers can customize the appearance of tooltips (e.g. those shown when a user hovers over a link element) by specificying a tooltip plugin. A tooltip plugin is an object that implements the `renderLink` method.\n\nThe `renderLink` method is called with three arguments:\n\n- `tooltip` - The DOM element of the tooltip UI.\n- `link` - The DOM element (HTMLAnchorElement) of the link to display a tooltip for.\n- `actions` - An object containing functions that can be called in response to user interaction.\n  - `editLink` - A function that, when called, prompts the user to edit the `href` of the link element in question.\n\nThe `renderLink` method is responsible for populating the passed `tooltip` element with the correct content to display to the user based on the link in question. This allows Mobiledoc users to, for example, provide localized tooltip text via their system of choice.\n\n```js\nconst MyTooltip = {\n  renderLink(tooltip, link, { editLink }) {\n    tooltip.innerHTML = `${i18n.translate('URL: ')} ${link.href}`\n    const button = document.createElement('button')\n    button.innerText = i18n.translate('Edit')\n    button.addEventListener('click', editLink)\n    tooltip.append(button)\n  },\n}\nconst editor = new Mobiledoc.Editor({\n  tooltipPlugin: MyTooltip,\n})\nconst element = document.querySelector('#editor')\neditor.render(element)\n```\n\n## Contributing\n\nFork the repo, write a test, make a change, open a PR.\n\n### Tests, Linting, Formatting\n\nInstall dependencies via yarn:\n\n- [Node.js](http://nodejs.org/) is required\n- Install [yarn](https://yarnpkg.com/en/docs/install) globally: `npm install -g yarn` or `brew install yarn`\n- Install dependencies with yarn: `yarn install`\n\nRun tests via the built-in broccoli server:\n\n- `yarn start`\n- `open http://localhost:4200/tests`\n\nOr run headless tests via testem:\n\n- `yarn test`\n\nTests in CI are run at Github Actions via Saucelabs (see the `test:ci` yarn script).\n\nRun linter\n\n- `yarn lint`\n\nRun formatting\n\n- `yarn format`\n\n### Demo\n\nTo run the demo site locally:\n\n- `yarn start`\n- `open http://localhost:4200/demo/`\n\nThe assets for the demo are in `/demo`.\n\n### Debugging\n\nA debugging environment that prints useful information about the active Mobiledoc editor\ncan be access by:\n\n- `yarn start`\n- `open http://localhost:4200/demo/debug.html`\n\n### Getting Help\n\nIf you notice a bug or have a feature request please [open an issue on github](https://github.com/bustle/mobiledoc-kit/issues).\nIf you have a question about usage you can post in the [slack channel](https://mobiledoc-kit.slack.com/) (automatic invites available from our [slackin app](https://mobiledoc-slack.herokuapp.com/)) or on StackOverflow using the [`mobiledoc-kit` tag](http://stackoverflow.com/questions/tagged/mobiledoc-kit).\n\n### Releasing (Implementer notes)\n\n- Use `np` (`yarn install -g np`)\n- `np \u003cversion\u003e` (e.g. `np 0.12.0`)\n- `git push \u003corigin\u003e --tags`\n\n### Deploy the website (demo \u0026 docs)\n\nThe demo website is hosted at\n[bustle.github.io/mobiledoc-kit/demo](https://bustle.github.io/mobiledoc-kit/demo).\n\nTo publish a new version:\n\n- `yarn run build:website` - This builds the website into `dist/website`\n- `yarn run deploy:website` - Pushes the website to the `gh-pages` branch of the `origin` at github\n\n_Development of Mobiledoc and the supporting libraries was generously funded by [BDG Media](https://www.bdg.com)._\n","funding_links":[],"categories":["JavaScript","独立的","Rich text editor","javascript","27. 富文本编辑器/Markdown编辑器/Markdown解析器"],"sub_categories":["24.3 Web Sockets"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbustle%2Fmobiledoc-kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbustle%2Fmobiledoc-kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbustle%2Fmobiledoc-kit/lists"}