{"id":14007132,"url":"https://github.com/peterpeterparker/stylo","last_synced_at":"2025-04-04T11:14:16.210Z","repository":{"id":36998499,"uuid":"450188237","full_name":"peterpeterparker/stylo","owner":"peterpeterparker","description":"Another kind of rich text editor","archived":false,"fork":false,"pushed_at":"2023-04-10T07:09:05.000Z","size":1254,"stargazers_count":713,"open_issues_count":5,"forks_count":28,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-03-28T10:08:24.182Z","etag":null,"topics":["custom-elements","editor","rich-text-editor","stenciljs","web-components","wysiwyg","wysiwyg-editor"],"latest_commit_sha":null,"homepage":"https://stylojs.com","language":"TypeScript","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/peterpeterparker.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2022-01-20T17:12:14.000Z","updated_at":"2025-03-28T04:16:07.000Z","dependencies_parsed_at":"2024-02-09T03:26:12.207Z","dependency_job_id":null,"html_url":"https://github.com/peterpeterparker/stylo","commit_stats":{"total_commits":265,"total_committers":8,"mean_commits":33.125,"dds":"0.037735849056603765","last_synced_commit":"065944bb782a383d49ccb9dc7c81e1eafcc914fd"},"previous_names":["peterpeterparker/stylo","papyrs/stylo"],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterpeterparker%2Fstylo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterpeterparker%2Fstylo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterpeterparker%2Fstylo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peterpeterparker%2Fstylo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peterpeterparker","download_url":"https://codeload.github.com/peterpeterparker/stylo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246651539,"owners_count":20811993,"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":["custom-elements","editor","rich-text-editor","stenciljs","web-components","wysiwyg","wysiwyg-editor"],"created_at":"2024-08-10T10:01:50.828Z","updated_at":"2025-04-04T11:14:16.187Z","avatar_url":"https://github.com/peterpeterparker.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Stylo\n\nAnother kind of rich text editor.\n\n- Interactive design 🎯\n- Customizable 💪\n- Framework agnostic 😎\n- Lightweight 🪶\n- Future Proof 🚀\n- Open Source ⭐️\n\nA project from [Papyrs](https://papy.rs), a blogging platform on web3.\n\n[![GitHub release](https://img.shields.io/github/release/papyrs/stylo/all?logo=GitHub\u0026color=lightgrey)](https://github.com/papyrs/stylo/releases/latest)\n[![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fstylojs.com)](https://twitter.com/intent/tweet?url=https%3A%2F%2Fstylojs.com\u0026text=Another%20kind%20of%20rich%20text%20editor%20by%20%40PapyrsApp)\n\n## Table of contents\n\n- [Getting Started](#getting-started)\n- [Concept](#concept)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Config](#config)\n- [Plugins](#plugins)\n- [Toolbar](#toolbar)\n- [Menus](#menus)\n- [Events](#events)\n- [Listener](#listener)\n- [Contributing](#contributing)\n- [i18n](#i18n)\n- [License](#license)\n\n## Getting Started\n\nStylo is an open source WYSIWYG interactive editor for JavaScript. Its goal is to bring great user experience and interactivity to the web, for everyone, with no dependencies.\n\n## Concept\n\nThe library - a web component - needs as bare minimum property a reference to an editable HTML element (`contenteditable=\"true\"`).\n\nIt needs only one single top container set as editable and will maintain a list of children, paragraphs, that are themselves HTML elements.\n\n```html\n\u003carticle contenteditable=\"true\"\u003e\n  \u003cdiv\u003eLorem ipsum dolor sit amet.\u003c/div\u003e\n  \u003chr /\u003e\n  \u003cul\u003e\n    \u003cli\u003eHello\u003c/li\u003e\n    \u003cli\u003eWorld\u003c/li\u003e\n  \u003c/ul\u003e\n  \u003cdiv\u003eIn ac tortor suscipit.\u003c/div\u003e\n\u003c/article\u003e\n```\n\nTo keep track of the changes for a custom \"undo redo\" stack and to forward the information to your application, the component mainly uses the [MutationObserver API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).\n\nIt also uses some keyboard, mouse or touch events to present UI elements or apply styling changes.\n\n## Installation\n\nYou can use Stylo via CDN or by installing it locally.\n\n### CDN\n\nAdd the following code to your page to load the editor.\n\n```html\n\u003cscript type=\"module\" src=\"https://unpkg.com/@papyrs/stylo@latest/dist/stylo/stylo.esm.js\"\u003e\u003c/script\u003e\n```\n\nThat's it, the component is imported and loaded.\n\n### Local Installation\n\nInstall the editor in your project from [npm](https://www.npmjs.com/package/@papyrs/stylo):\n\n```bash\nnpm install @papyrs/stylo\n```\n\nAfterwards you will need to load - i.e. import - the component in your application. Use one of the following methods, the one that fits the best your needs or framework.\n\n#### Loader\n\nLazy load the components with the help of a loader. This is the recommended solution to load Stylo in [vite](https://vitejs.dev/) projects.\n\n```js\nimport {defineCustomElements} from '@papyrs/stylo/dist/loader';\ndefineCustomElements();\n```\n\n#### Import\n\nImport the library.\n\n```js\nimport '@papyrs/stylo';\n```\n\n#### Custom Elements\n\nIt is also possible to import only selected element, as for example the `\u003cstylo-color /\u003e` component.\n\n```js\nimport {StyloColor} from '@papyrs/stylo/dist/components/stylo-color';\ncustomElements.define('stylo-color', StyloColor);\n```\n\nNote: it will recursively define all children components for a component when it is registered.\n\n## Usage\n\nTo integrate the editor to your application, add the following tag next to your editable element:\n\n```\n\u003cstylo-editor\u003e\u003c/stylo-editor\u003e\n```\n\nThe component needs to find place at the same level because its UI elements are `absolute` positioned.\n\nOnce added, provide a reference to your container.\n\n```js\n// Your editable element\nconst article = document.querySelector('article[contenteditable=\"true\"]');\n\n// Stylo\nconst stylo = document.querySelector('stylo-editor');\n\n// Set the `containerRef` property\nstylo.containerRef = article;\n```\n\n## Config\n\nThe editor is provided with a default configuration. It can be customized by setting the property `config` of the `\u003cstylo-editor/\u003e` component.\n\nFor more information:\n\n- [i18.d.ts](src/types/i18n.ts) for the list of languages\n- [config.store.ts](src/stores/config.store.ts) for the default plugins and toolbar configuration\n\n## Plugins\n\nA plugin is a transform function that adds a new paragraph to the editable container.\n\nYou can contribute by adding new plugins to this repo or create custom plugins for your application only.\n\nThe list of plugins available at runtime by the editor is fully customizable.\n\n### Development\n\nStylo exposes interfaces and utilities to ease the development of new plugins. Basically, a plugin should provide:\n\n- `text`: the text, a `string`, displayed to the user in the UI popover\n- `icon`: an icon displayed to the user in the UI popover. it can be one of the built-in icons ([src/types/plugin.ts](src/types/plugin.ts)) or an inline SVG - i.e. an SVG provided as `string`\n- `createParagraphs`: the function that effectively create the new paragraph(s), add these elements to the DOM and can optionally give focus to the newly created first or last element\n\nFor example, a plugin that generates a new paragraph that is itself a Web Component name `\u003chello-world/\u003e` would look as following:\n\n```js\nimport {\n  createEmptyElement,\n  StyloPlugin,\n  StyloPluginCreateParagraphsParams,\n  transformParagraph\n} from '@papyrs/stylo';\n\nexport const hr: StyloPlugin = {\n  text: 'My Hello World',\n  icon: `\u003csvg width=\"32\" height=\"32\" viewBox=\"0 0 512 512\"\u003e\n        ...\n    \u003c/svg\u003e\n  `,\n  createParagraphs: async ({container, paragraph}: StyloPluginCreateParagraphsParams) =\u003e {\n    // Create your Web Component or HTML Element\n    const helloWorld = document.createElement('hello-world');\n\n    // Set properties, attributes or styles\n    helloWorld.setAttributes('yolo', 'true');\n\n    transformParagraph({\n      elements: [helloWorld, createEmptyElement({nodeName: 'div'})],\n      paragraph,\n      container,\n      focus: 'first'\n    });\n  }\n};\n```\n\nIn addition, it is worth to note that `createParagraphs` is a promise. This gives you the ability to hi-jack the user flow to trigger some functions in your application before the DOM is actually modified. As for example opening a modal after a plugin as been selected by the user.\n\nThings to pay attention to:\n\n- when users are using your plugins, they should not end up trapped not being able to continue editing and create new paragraphs. That's why we advise to generate an empty `div` (in above example `createEmptyElement`) at the same time as your element(s)\n- Stylo expect all the direct children - the paragraphs - of the editable container to be HTML elements i.e. no text or comment nodes\n\nFind some custom plugins in DeckDeckGo [repo](https://github.com/deckgo/deckdeckgo/tree/main/studio/src/app/plugins).\n\n## Toolbar\n\nThe inline editor that is uses to style texts (bold, italic, colors, etc.) is a web component named `\u003cstylo-toolbar/\u003e`.\n\nIt is used per default with Stylo on desktop but can also be used as a standalone component.\n\nBecause mobile devices are already shipped with their own tooltip, the toolbar is not activated by Stylo on such device.\n\n## Menus\n\nOptionally, menus can be defined for particular elements - i.e. paragraphs. They will be displayed with an absolute positioning after click events.\n\nCustom menus can be configured following the ([src/types/menu.ts](src/types/menu.ts)) interface.\n\nIf for example you would like to display a custom menu for all `code` paragraphs, this can be done as following:\n\n```js\nexport const editorConfig: Partial\u003cStyloConfig\u003e = {\n  menus: [\n    {\n      match: ({paragraph}: {paragraph: HTMLElement}) =\u003e paragraph.nodeName.toLowerCase() === 'code',\n      actions: [\n        {\n          text: 'Edit code',\n          icon: `\u003csvg ...\n          \u003c/svg\u003e`,\n          action: async ({paragraph}: {paragraph: HTMLElement}) =\u003e {\n            // Apply some modifications or any other actions of your choice\n          }\n        }\n      ]\n    }\n  ]\n};\n```\n\nStylo provides a sample menu for images ([src/menus/img.menu.ts](src/menus/img.menu.ts)).\n\n## Events\n\nIf you are using a rich text editor, there is a chance that you are looking to persist users entries and changes.\n\nFor such purpose, the `\u003cstylo-editor/\u003e` component triggers following custom events:\n\n- `addParagraphs`: triggered each time new paragraph(s) is added to the editable container\n- `deleteParagraphs`: triggered each time paragraph(s) are removed\n- `updateParagraphs`: triggered each time paragraph(s) are updated\n\nEach paragraph is a direct child of the editable container.\n\nUnlike `addParagraphs` and `deleteParagraphs` that are triggered only if elements are such level are added or removed, `updateParagraphs` is triggered if the paragraphs themselves or any of their children (HTML elements and text nodes) are modified.\n\nStylo can detect changes for paragraphs and elements that are added or updated but cannot detect deleted paragraphs without a hint. The Mutation Observer API does not provide yet enough information. To overcome this issue, Stylo set an attribute with empty value to identify what elements are paragraphs.\n\nChanges following keyboard inputs are debounced.\n\n### Attributes\n\nFollowing attributes are ignored to prevent the observer to trigger and keep track of changes that are not made by the user on purpose:\n\n- paragraph_id: the attribute added to identify each paragraph\n- placeholder: the attribute used by Stylo to display the placeholder about the '/'\n- class: only inline style is considered changes\n- spellcheck\n- contenteditable\n- data-gramm, data-gramm_id, data-gramm_editor and data-gr-id: [Grammarly](https://www.grammarly.com/) flooding the DOM\n\nThe list of excluded attributes and the `paragraph_id` hint can be customized through the configuration ([src/types/config.ts](src/types/config.ts)).\n\n## Listener\n\nIf you are manipulating the `contenteditable` - i.e. the DOM - on your side, you might want to add these changes to the \"undo-redo\" history.\n\nFor such purpose, the editor is listening for the events `snapshotParagraph` of type `CustomEvent\u003cvoid\u003e` that can be triggered from the child of the editable element you are about to modify.\n\n## Contributing\n\nWe welcome contributions in the form of issues, pull requests, documentation improvements or thoughtful discussions in the [GitHub issue tracker](https://github.com/papyrs/stylo/issues).\n\nTo provide code changes, make sure you have a recent version of [Node.js installed](https://nodejs.org/en/) (LTS recommended).\n\nFork and clone this repository. Head over to your terminal and run the following command:\n\n```\ngit clone git@github.com:[YOUR_USERNAME]/stylo.git\ncd stylo\nnpm ci\nnpm run start\n```\n\nBefore submitting changes, make sure to have run at least once a build (`npm run build`) to generate the documentation.\n\nTests suite can be run with `npm run test`.\n\nThis project is developed with [Stencil](https://stenciljs.com/).\n\n## i18n\n\nEnglish, German, Spanish and Dutch are currently supported. More translations are also welcomed!\n\n### Contributions\n\n- add a new translation file in [src/assets/i18n](src/assets/i18n)\n- extends the list of supported `Languages` in [src/types/i18.d.ts](src/types/i18n.ts)\n- update README with the new language\n\n### Customization\n\nThe `text` options of plugins and menus can either be static `string` or a translation keys.\n\nTo provide a list of custom translations that matches these keys, Stylo accepts a `custom` record of string ([src/types/config.ts](src/types/config.ts)).\n\nThrough the same configuration it is also possible to switch languages on the fly.\n\n## License\n\nMIT © [David Dal Busco](mailto:david.dalbusco@outlook.com) and [Nicolas Mattia](mailto:nicolas@nmattia.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterpeterparker%2Fstylo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeterpeterparker%2Fstylo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterpeterparker%2Fstylo/lists"}