docs: fully revised event system (listener extensions) doc

This commit is contained in:
Antonio 2022-05-13 22:59:38 +03:00
parent 039c9be049
commit b64f0bea9d
No known key found for this signature in database
GPG Key ID: B2925A0862737E97
4 changed files with 243 additions and 228 deletions

View File

@ -1,4 +1,8 @@
:root {
--md-primary-fg-color: rgb(196, 54, 39);
--md-accent-fg-color: rgb(62, 139, 138);
}
table {
box-sizing: initial;
}

238
docs/event-system.md Normal file
View File

@ -0,0 +1,238 @@
## Introduction
The Event System is the basis of the new Listener extensions, replacing the ["old" extension system](extensions.md) altogether.
In short, the Event System lifecycle looks as follows:
1. A sub-parser emits an event.
!!! note ""
Each sub-parser can emit a batch of events (see the list below)
1. Extension A (which is a _Listener Extension_) registers and listens to a specific event.
!!! note ""
An extension can only register for a specific event
1. Extension A receives an event object and modifies it.
!!! note ""
Certain properties of the event object can be changed, which will change the behavior or output of the sub-parser
1. Extension A returns the event object to the converter.
1. The converter passes the received event object to the next extension in the chain.
## Event Object
### Properties
#### matches
**matches** is an object that holds the text captured by the sub-parser. The properties of this object are different for each sub-parser.
Some properties can be read-only: their names start with `_` (underscore).
!!! example "blockquote `onCapture` event"
```js
{
_wholeMatch: "> some awesome quote",
blockquote: "some awesome quote"
}
```
<!-- ## Basic Event -->
## Event types
Events are emitted when a sub-parser runs (or about to be run).
In the sub-parser, the events follow strict order sequence: [`onStart`](#onstart) -> [`onCapture`](#oncapture) -> [`onHash`](#onhash) -> [`onEnd`](#onend)
It means that `.before` events always run before `.captureStart`.
### onStart
**`<converter>.<subparser>.onStart`**: **always runs** except if the sub-parser is disabled.
Emitted when the sub-parser has started, but no capturing or modifications to the text were done.
**Always runs** except if the sub-parser is disabled via options.
!!! hint "When to use `onStart` event"
Use this event when you want to change the input passed to the sub-parser.
!!! warning ""
Please note that the input is the **full text** that was passed to the converter.
**Properties**
| property | type | access | description |
|--------------|----------|---------|----------------------------------------------------------------|
| `input` | `string` | `read` | Full text that was passed to the subparser |
| `output` | `string` | `write` | Full text with modification that will be passed along the chain |
| `regexp` | `null` | | |
| `matches` | `null` | | |
| `attributes` | `null` | | |
### onCapture
**`<converter>.<subparser>.onCapture`**: *might not be run*.
Emitted when a regex match is found and capture was successful.
Further normalization and modification of the regex captured groups might be performed.
Might not be run if no regex match found.
!!! hint "When to use `onCapture` event"
Use this event if you want to:
* modify the sub-parser behavior, text;
* modify the HTML output of the sub-parser.
**Properties**
| property | type | access | description |
|--------------|----------|--------------|-------------------------------------------------------------------|
| `input` | `string` | `readonly` | The captured text |
| `output` | `string` | `write` | `null` or well-formed HTML (see the Important Note below) |
| `regexp` | `RegExp` | `readonly` | Regular Expression to capture groups |
| `matches` | `object` | `read/write` | Match groups. Changes to this object are reflected in the output. |
| `attributes` | `object` | `read/write` | Attributes to add to the HTML output |
!!! warning "IMPORTANT NOTE"
Extensions listening to the `onCapture` event **should avoid** changing the output property.
Instead, they should modify the values of the matches and attribute objects.
The reason is that the **output property takes precedence over the matches objects** and
**prevents showdown from calling other sub-parsers** inside the captured fragment.
The above means the following:
1. If something is passed as the output property, any changes to the matches and attributes objects will be ignored.
1. Any changes made by other extensions to the matches or attributes objects will be ignored.
1. Showdown will not call other sub-parsers, such as encode code or span gamut in the text fragment, which may lead to unexpected results.
**Example**
!!! example ""
```js hl_lines="4"
// Showdown extension 1 that is listening to makehtml.blockquote.onCapture
function extension_1 (showdownEvent) {
// Let's imagine you're a bad person who writes to output
showdownEvent.output = '<blockquote>foo</blockquote>'; // must be a well-formed HTML
showdownEvent.matches.blockquote = 'some nice quote';
}
// Showdown extension 2 that is also listening to makehtml.blockquote.onCapture
function extension_2 (showdownEvent) {
// I make blockquotes bold
let quote = showdownEvent.matches.blockquote;
showdownEvent.matches.blockquote = '<strong>' + quote + '</strong>';
}
```
In the example above, the result will always be `<blockquote>foo</blockquote>`, regardless of the order of extension loading and call.
!!! danger "Infinite loop"
Do not pass the input as output to the `onCapture` event, or you might trigger an infinite loop.
### onHash
**`<converter>.<subparser>.onHash`**: *always runs*.
Raised before the output is hashed.
**Always runs** (except if the sub-parser is disabled via options), even if no hashing is performed.
!!! hint "When to use `onHash` event"
Use this event when you want to change the sub-parser's raw output before it is hashed.
**Properties**
| property | type | access | description |
|--------------|----------|---------|----------------------------------------------|
| `input` | `string` | `read` | The captured text |
| `output` | `string` | `write` | The text that will be passed along the chain |
| `regexp` | `null` | | |
| `matches` | `null` | | |
| `attributes` | `null` | | |
### onEnd
**`<converter>.<subparser>.onEnd`**: **always runs**;
Emitted when the sub-parser has finished its work and is about to exit.
**Always runs** (except if the sub-parser is disabled via options).
!!! hint "When to use `onEnd` event"
Use this event when you want to run code or perform changes to the text after the subparser has run and its output was hashed.
!!! warning ""
Please note that the input is the **full text** and might contain hashed elements.
**Properties**
| property | type | access | description |
|-----------|----------|---------|-------------------------------------------------------------|
| `input` | `string` | `read` | Full text with the subparser modifications (contains hashes) |
| `output` | `string` | `write` | The text that will be passed to other subparsers |
| `regexp` | `null` | | |
| `matches` | `null` | | |
## Special Events
There are special events useful for *"positioning"* a listener extension in the main chain of events.
Usually, these extensions introduce new syntax that, due to precedence
In contrary, the special events will always be called, regardless of options or circumstances.
### .before.{subparserName}
**`.before.{subparserName}`**: **always runs**
Emitted just before the **`{subparserName}` is about to be entered**.
**Properties**
| property | type | access | description |
|-----------|----------|---------|----------------------------------------------------------------|
| `input` | `string` | `read` | Full text that was passed to the subparser |
| `output` | `string` | `write` | Full text with modification that will be passed along the chain |
| `regexp` | `null` | | |
| `matches` | `null` | | |
!!! note "Difference between `before.{subparserName}` and `{subparserName}.start`"
1. **`before.{subparserName}`** is always guaranteed to be called, **even if the subparser is disabled**,
while **{subparserName}.start** isn't.
For example, `makehtml.before.strikethrough` is always called even if the option `strikethrough` is `false`.
1. **`before.{subparserName}`** is only emitted **once** in a span context while **`{subparserName}.start`** is emitted
everytime **`{subparserName}`** is called.
<!-- As a rule of thumb -->
### .after.{subparserName}
**`.after.{subparserName}`**: **always runs**;
Emitted when the **`{subparserName}` has exited** and before the next one is called.
**Properties**
| property | type | access | description |
|-----------|----------|---------|---------------------------------------------------|
| `input` | `string` | `read` | Partial/full text with the subparser modifications |
| `output` | `string` | `write` | The text that will be passed to other subparsers |
| `regexp` | `null` | | |
| `matches` | `null` | | |
<!--
## Events List
### blockquote
### metadata
-->

View File

@ -1,228 +0,0 @@
# Event System )
## Introduction
The Event System is the basis of the new Listener extensions, replacing the old extension system altogether.
The life cycle can be summarized as this:
- A subparsers emmits an event
- Each subparser can emmit a bunch of events (see list below)
- Extension A, which is a _Listener Extension_ registers and listens to a specific event
- An extension can only register to a specific event
- Extension A receives an event object and modifies it
- Certain properties of the event object can be changed, ehich will change the behavior or output of the subparser
- The extension then returns the event object
- The converter passes this event object to the next extension in the chain
Note:
## The Event Object
### Properties
#### matches (type: **object**)
This object holds the text captured by the subparser. The properties of this object
are different for each subparser.
**Properties whose name starts with `_` are readonly**.
Example:
```js
// blockquote onCapture event
{
_wholeMatch: "> some awesome quote",
blockquote: "some awesome quote"
}
```
## Basic Event
## Event Types
Events are raised when a subparser is run (or about to be run).
Within a subparser, the events always follow a certain order (sequence). For instance, **.before** events always run before **.captureStart**.
Each subparser raises several events sequentially:
### onStart
**`<converter>.<subparser>.onStart`**: **always runs** except if the subparser is disabled
Raised when the **subparser has started**, but no capturing or any modification to the text was done.
**Always runs** (except if the subparser is deactivated through options).
***Properties***:
| property | type | access | description |
|------------|-----------|------------|--------------------------------------------------------------------|
| input | string | read | The full text that was passed to the subparser |
| output | string | write | The full text with modification that will be passed along the chain|
| regexp | null | | |
| matches | null | | |
| attributes | null | | |
Usually you would want to use this event if you wish to change the input to the subparser. Please note,
however, that the input text is the **complete text** that was fed to the converter.
### onCapture
**`<converter>.<subparser>.onCapture`**: *might not be run*;
Raised when a regex match is found and a capture was successful. Some normalization and modification
of the regex captured groups might be performed.
Might not be run if no regex match is found.
***Properties***:
| property | type | access | description |
|------------|----------|------------|--------------------------------------------------------------------|
| input | string | readonly | The captured text |
| output | string | write | null or well formed HTML (see note) |
| regexp | RegExp | readonly | Regular Expression used to capture groups |
| matches | object | read/write | Matches groups. Changes to this object are reflected in the output |
| attributes | object | read/write | Attributes to add to the HTML output |
Usually you would want to use this event if you wish to modify the subparser behavior, text or modify the HTML output
of the subparser.
**IMPORTANT NOTE**: Extensions listening to onCapture event should try to AVOID changing the output property.
Instead, they should modify the values of the matches and attributes objects. This is because
the **output property takes precedence over the matches objects** and **prevents showdown to call other subparsers**
inside the captured fragment.This means 3 very important things:
1. If something is passed as the output property, any changes to the matches and attributes objects will be ignored.
2. Changes made by other extensions to the matches or attributes objects will also be ignored
3. Showdown will not call other subparsers, such as encode code or span gamut in the text fragment, which may lead to
weird results.
```javascript
// showdown extension 1 that is listening to makehtml.blockquote.onCapture
function extension_1 (showdownEvent) {
// i'm bad and write to output
showdownEvent.output = '<blockquote>foo</blockquote>'; // must be an well formed html string
showdownEvent.matches.blockquote = 'some nice quote';
}
// showdown extension 1 that is also listening to makehtml.blockquote.onCapture
function extension_2 (showdownEvent) {
// I make blockquotes become bold
let quote = showdownEvent.matches.blockquote;
showdownEvent.matches.blockquote = '<strong>' + quote + '</strong>';
}
// the result will always be <blockquote>foo</blockquote>, regardless of the order of extension loading and call
```
### onHash
**`<converter>.<subparser>.onHash`**: *always runs*;
Raised before the output is hashed. **Always runs** (except if the subparser was deactivated through options),
even if no hashing is performed.
***Properties***:
| property | type | access | description |
|------------|------------|------------|--------------------------------------------------------------------|
| input | string | read | The captured text |
| output | string | write | The text that will be passed to the subparser/other listeners |
| regexp | null | | |
| matches | null | | |
| attributes | null | | |
Usually you would want to use this event if you wish change the subparser's raw output before it is hashed.
### onEnd
**`<converter>.<subparser>.onEnd`**: *always runs*;
Raised when the subparser has finished its work and is about to exit.
Always runs (except if the subparser is deactivated through options).
***Properties***:
| property | type | access | description |
|----------|-----------|------------|--------------------------------------------------------------------|
| input | string | read | The full text with the subparser modifications (contains hashes) |
| output | string | write | The text that will be passed to other subparsers |
| regexp | null | | |
| matches | null | | |
Usually you would want to use this event if you want to run code or perform changes to the text after the subparser was
run and it's output was hashed. Keep in mind that the input is the full text and might contain hashed elements.
### Special Events
There are some special events that are useful for *"positioning"* a listener extension in the main chain of events.
Usually these extensions introduce new syntax that, due to precedence
These events are always guaranteed to be called, regardless of options or circumstances.
1. **.before_{subparserName}**: *always runs*
Raised just before the **{subparserName} is about to be entered**.
***Properties***:
| property | type | access | description |
|----------|-----------|------------|--------------------------------------------------------------------|
| input | string | read | The full text that was passed to the subparser |
| output | string | write | The full text with modification that will be passed along the chain|
| regexp | null | | |
| matches | null | | |
2. **.after**.{subparserName}: *always runs*;
Raised when the **{subparserName} has exited** and before the next one is called.
***Properties***:
| property | type | access | description |
|----------|-----------|------------|--------------------------------------------------------------------|
| input | string | read | The partial/full text with the subparser modifications |
| output | string | write | The text that will be passed to other subparsers |
| regexp | null | | |
| matches | null | | |
### Notes
- There are 2 main differences between **before.{subparserName}** and **{subparserName}.start**.
1. **before.{subparserName}** is always guaranteed to be called, even if the subparser is disabled,
while **{subparserName}.start** doesn't.
ex: `makehtml.before.strikethrough` is always called even if the option `strikethrough` is false
2. **before.{subparserName}** is only raised once in a span context while **{subparserName}.start** is raised
everytime **{subparserName}** is called.
As a rule of thumb,
## Events List
### blockquote
####
### metadata
## Common pitfalls
### Infinite loop
If you pass the input as output in a `onCapture` event, you might trigger an infinite loop.

View File

@ -43,6 +43,7 @@ nav:
- Flavors: flavors.md
- CLI: cli.md
- Integrations: integrations.md
- Event system: event-system.md
- Extensions:
- Overview: extensions.md
- Create an extension: create-extension.md