• Stars
    star
    716
  • Rank 63,241 (Top 2 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created almost 3 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Another kind of rich text editor

Stylo

Another kind of rich text editor.

  • Interactive design 🎯
  • Customizable 💪
  • Framework agnostic 😎
  • Lightweight 🪶
  • Future Proof 🚀
  • Open Source ⭐️

A project from Papyrs, a blogging platform on web3.

GitHub release Tweet

Table of contents

Getting Started

Stylo 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.

Concept

The library - a web component - needs as bare minimum property a reference to an editable HTML element (contenteditable="true").

It needs only one single top container set as editable and will maintain a list of children, paragraphs, that are themselves HTML elements.

<article contenteditable="true">
  <div>Lorem ipsum dolor sit amet.</div>
  <hr />
  <ul>
    <li>Hello</li>
    <li>World</li>
  </ul>
  <div>In ac tortor suscipit.</div>
</article>

To 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.

It also uses some keyboard, mouse or touch events to present UI elements or apply styling changes.

Installation

You can use Stylo via CDN or by installing it locally.

CDN

Add the following code to your page to load the editor.

<script type="module" src="https://unpkg.com/@papyrs/stylo@latest/dist/stylo/stylo.esm.js"></script>

That's it, the component is imported and loaded.

Local Installation

Install the editor in your project from npm:

npm install @papyrs/stylo

Afterwards 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.

Loader

Lazy load the components with the help of a loader. This is the recommended solution to load Stylo in vite projects.

import {defineCustomElements} from '@papyrs/stylo/dist/loader';
defineCustomElements();

Import

Import the library.

import '@papyrs/stylo';

Custom Elements

It is also possible to import only selected element, as for example the <stylo-color /> component.

import {StyloColor} from '@papyrs/stylo/dist/components/stylo-color';
customElements.define('stylo-color', StyloColor);

Note: it will recursively define all children components for a component when it is registered.

Usage

To integrate the editor to your application, add the following tag next to your editable element:

<stylo-editor></stylo-editor>

The component needs to find place at the same level because its UI elements are absolute positioned.

Once added, provide a reference to your container.

// Your editable element
const article = document.querySelector('article[contenteditable="true"]');

// Stylo
const stylo = document.querySelector('stylo-editor');

// Set the `containerRef` property
stylo.containerRef = article;

Config

The editor is provided with a default configuration. It can be customized by setting the property config of the <stylo-editor/> component.

For more information:

Plugins

A plugin is a transform function that adds a new paragraph to the editable container.

You can contribute by adding new plugins to this repo or create custom plugins for your application only.

The list of plugins available at runtime by the editor is fully customizable.

Development

Stylo exposes interfaces and utilities to ease the development of new plugins. Basically, a plugin should provide:

  • text: the text, a string, displayed to the user in the UI popover
  • icon: an icon displayed to the user in the UI popover. it can be one of the built-in icons (src/types/plugin.ts) or an inline SVG - i.e. an SVG provided as string
  • 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

For example, a plugin that generates a new paragraph that is itself a Web Component name <hello-world/> would look as following:

import {
  createEmptyElement,
  StyloPlugin,
  StyloPluginCreateParagraphsParams,
  transformParagraph
} from '@papyrs/stylo';

export const hr: StyloPlugin = {
  text: 'My Hello World',
  icon: `<svg width="32" height="32" viewBox="0 0 512 512">
        ...
    </svg>
  `,
  createParagraphs: async ({container, paragraph}: StyloPluginCreateParagraphsParams) => {
    // Create your Web Component or HTML Element
    const helloWorld = document.createElement('hello-world');

    // Set properties, attributes or styles
    helloWorld.setAttributes('yolo', 'true');

    transformParagraph({
      elements: [helloWorld, createEmptyElement({nodeName: 'div'})],
      paragraph,
      container,
      focus: 'first'
    });
  }
};

In 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.

Things to pay attention to:

  • 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)
  • Stylo expect all the direct children - the paragraphs - of the editable container to be HTML elements i.e. no text or comment nodes

Find some custom plugins in DeckDeckGo repo.

Toolbar

The inline editor that is uses to style texts (bold, italic, colors, etc.) is a web component named <stylo-toolbar/>.

It is used per default with Stylo on desktop but can also be used as a standalone component.

Because mobile devices are already shipped with their own tooltip, the toolbar is not activated by Stylo on such device.

Menus

Optionally, menus can be defined for particular elements - i.e. paragraphs. They will be displayed with an absolute positioning after click events.

Custom menus can be configured following the (src/types/menu.ts) interface.

If for example you would like to display a custom menu for all code paragraphs, this can be done as following:

export const editorConfig: Partial<StyloConfig> = {
  menus: [
    {
      match: ({paragraph}: {paragraph: HTMLElement}) => paragraph.nodeName.toLowerCase() === 'code',
      actions: [
        {
          text: 'Edit code',
          icon: `<svg ...
          </svg>`,
          action: async ({paragraph}: {paragraph: HTMLElement}) => {
            // Apply some modifications or any other actions of your choice
          }
        }
      ]
    }
  ]
};

Stylo provides a sample menu for images (src/menus/img.menu.ts).

Events

If you are using a rich text editor, there is a chance that you are looking to persist users entries and changes.

For such purpose, the <stylo-editor/> component triggers following custom events:

  • addParagraphs: triggered each time new paragraph(s) is added to the editable container
  • deleteParagraphs: triggered each time paragraph(s) are removed
  • updateParagraphs: triggered each time paragraph(s) are updated

Each paragraph is a direct child of the editable container.

Unlike 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.

Stylo 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.

Changes following keyboard inputs are debounced.

Attributes

Following attributes are ignored to prevent the observer to trigger and keep track of changes that are not made by the user on purpose:

  • paragraph_id: the attribute added to identify each paragraph
  • placeholder: the attribute used by Stylo to display the placeholder about the '/'
  • class: only inline style is considered changes
  • spellcheck
  • contenteditable
  • data-gramm, data-gramm_id, data-gramm_editor and data-gr-id: Grammarly flooding the DOM

The list of excluded attributes and the paragraph_id hint can be customized through the configuration (src/types/config.ts).

Listener

If you are manipulating the contenteditable - i.e. the DOM - on your side, you might want to add these changes to the "undo-redo" history.

For such purpose, the editor is listening for the events snapshotParagraph of type CustomEvent<void> that can be triggered from the child of the editable element you are about to modify.

Contributing

We welcome contributions in the form of issues, pull requests, documentation improvements or thoughtful discussions in the GitHub issue tracker.

To provide code changes, make sure you have a recent version of Node.js installed (LTS recommended).

Fork and clone this repository. Head over to your terminal and run the following command:

git clone [email protected]:[YOUR_USERNAME]/stylo.git
cd stylo
npm ci
npm run start

Before submitting changes, make sure to have run at least once a build (npm run build) to generate the documentation.

Tests suite can be run with npm run test.

This project is developed with Stencil.

i18n

English, German, Spanish and Dutch are currently supported. More translations are also welcomed!

Contributions

Customization

The text options of plugins and menus can either be static string or a translation keys.

To provide a list of custom translations that matches these keys, Stylo accepts a custom record of string (src/types/config.ts).

Through the same configuration it is also possible to switch languages on the fly.

License

MIT © David Dal Busco and Nicolas Mattia

More Repositories

1

tietracker

A simple, open source and free time tracking app ⏱️
TypeScript
136
star
2

web-photo-filter

A Web Component to apply Instagram-like WebGL filters to photos
HTML
116
star
3

web-social-share

A Web Component to share url and text on social networks
HTML
78
star
4

tsdoc-markdown

Generates markdown documentation from TypeScript source code.
TypeScript
42
star
5

daviddalbusco.com

Freelance Web Developer
Svelte
24
star
6

discoverweekly.dev

The playlists made by devs, every Wednesday.
JavaScript
18
star
7

create-ic

A CLI for initializing projects with Juno or directing devs to the Internet Computer docs
TypeScript
15
star
8

rebelscan

A little scanner app made with the web, you rebel scum!
TypeScript
13
star
9

wooof

An application to browse the internet's biggest collection of open source dog pictures.
TypeScript
10
star
10

cycles.watch

Track the cycles consumption of canister smart contracts on the Internet Computer
Svelte
8
star
11

juno-openai

A demo showcasing the integration of OpenAI with Juno.
Svelte
7
star
12

jsf-dogs

Trick JavaServer Faces, load your bean data from the client side
Java
5
star
13

watamato

A Trello look alike board for my flat hunting in Zürich
TypeScript
5
star
14

proposals.network

Browse and submit proposals on the Internet Computer (ICP) 🏛️📊🗳️
Svelte
5
star
15

motoko_to_rust_migration

JavaScript
3
star
16

icwebworker

JavaScript
3
star
17

certifiedcustomassets

Rust
3
star
18

motoko_rust_interop

Create Rust based canister on the fly from Motoko.
JavaScript
3
star
19

icstreaming

https://forum.dfinity.org/t/http-request-streaming-callback-does-not-stream-chunks-anymore/11298
Motoko
3
star
20

manager

Sample repo for my articles about querying canister smart contracts with NodeJS scripts on the Internet Computer
JavaScript
3
star
21

json-to-enum

Map a JSON file to ENUM in Java
Java
2
star
22

ionic-zurich

Slides presented at the Ionic Zurich Meetup
2
star
23

stencilpwa

TypeScript
2
star
24

icdraw

A whiteboard for sketching hand-drawn like diagrams on Web3
TypeScript
2
star
25

ic_assets

JavaScript
2
star
26

firestore-gatsby

Build Gatsby Websites Using Firestore Data (Without Plugin)
JavaScript
2
star
27

webviewer-links

TypeScript
1
star
28

rxjs-no-subscribe

TypeScript
1
star
29

manifest

https://forum.dfinity.org/t/how-d-they-code-the-nns-front-end-to-do-this/11967
JavaScript
1
star
30

agentjswebworker

JavaScript
1
star
31

webviewer-react-sample

Support issue #20402
JavaScript
1
star
32

my-app-reproducibility-sort

JavaScript
1
star
33

debugstorage

Motoko
1
star
34

web-photo-filter-react

React specific wrapper for web-photo-filter
TypeScript
1
star
35

ng-force-e2e

TypeScript
1
star
36

stencilassets

TypeScript
1
star
37

web-photo-filter-demo

A demo for the Web Photo Filter Web Component
TypeScript
1
star
38

delcan

https://forum.dfinity.org/t/cannot-delete-canisters-anymore/11594
JavaScript
1
star
39

angular-store-demo

Angular State Management Without RxJS  -  An Experiment
TypeScript
1
star
40

stencil-svg-foreignobject

TypeScript
1
star
41

cypress-mock-blob

HTML
1
star
42

dummy-chat2

TypeScript
1
star
43

cordova-plugin-facebook4-lab

A demo application for the cordova-plugin-facebook4
TypeScript
1
star
44

webviewer-angular-sample

There may be some degradation of performance. Your server has not been configured to serve .gz. and .br. files with the expected Content-Encoding.
TypeScript
1
star
45

my-app-hash

Svelte
1
star
46

sns

A collection of SNS.yaml files
1
star