-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1dcd686
commit c06652d
Showing
17 changed files
with
1,266 additions
and
2,118 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__tests__/ | ||
specs/ | ||
.npmignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# `micromark-extension-ping` | ||
|
||
**[micromark][]** extension that parses custom Markdown syntax to handle | ||
user mentions, or pings. | ||
This syntax extension follows a [specification][spec]; | ||
in short, you can ping an user with the syntax `@user`. | ||
For usernames containing a space, use the alternative syntax `@**user space**`. | ||
|
||
This package provides the low-level modules for integrating with the micromark | ||
tokenizer and the micromark HTML compiler. | ||
|
||
## Install | ||
|
||
[npm][]: | ||
|
||
```sh | ||
npm install micromark-extension-ping | ||
``` | ||
|
||
## API | ||
|
||
### `html` | ||
|
||
### `syntax(options?)` | ||
|
||
> Note: `syntax` is the default export of this module, `html` is available at | ||
> `micromark-extension-ping/lib/html`. | ||
Support custom syntax to handle user mentions. | ||
The export of `syntax` is a function that can be called with options and returns | ||
an extension for the micromark parser (to tokenize user mentions; can be passed | ||
in `extensions`). | ||
The export of `html` is an extension for the default HTML compiler (to compile | ||
as `<a href="/@user">` elements; can be passed in `htmlExtensions`). | ||
|
||
##### `options` | ||
|
||
- `options.pingChar`: the pipe character used to ping a simple user name. Defaults to `@`. | ||
- `options.sequenceChar`: the star character added to ping user names containing a space. Defaults to `*` (star character). | ||
|
||
## License | ||
|
||
[MIT][license] © [Zeste de Savoir][zds] | ||
|
||
<!-- Definitions --> | ||
|
||
[license]: LICENCE | ||
|
||
[micromark]: https://github.com/micromark/micromark | ||
|
||
[npm]: https://docs.npmjs.com/cli/install | ||
|
||
[spec]: specs/extension.md | ||
|
||
[zds]: https://zestedesavoir.com |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { micromark } from 'micromark' | ||
import micromarkPing from '../lib/index' | ||
import micromarkPingHtml from '../lib/html' | ||
|
||
const specificationTests = { | ||
'works': ['@foo', '<p><a href="/@foo">@foo</a></p>'], | ||
'with stars': ['@**foo bar**', '<p><a href="/@foo%20bar">@foo bar</a></p>'], | ||
'opening with two stars': ['@*foo bar**', '<p>@<em>foo bar</em>*</p>'], | ||
'closing with two stars': ['@**foo bar*', '<p>@*<em>foo bar</em></p>'], | ||
'opening must close': ['@**foo bar', '<p>@**foo bar</p>'], | ||
'escape opening': ['@\\**foo bar**', '<p>@**foo bar**</p>'], | ||
'escape closing': ['@**foo bar\\**', '<p>@*<em>foo bar*</em></p>'], | ||
'escape at': ['\\@foo', '<p>@foo</p>'], | ||
'needs content - simple': ['@', '<p>@</p>'], | ||
'needs content - starred': ['@****', '<p>@****</p>'], | ||
'can contain Unicode': ['@Moté', '<p><a href="/@Mot%C3%A9">@Moté</a></p>'], | ||
'can contain star - lonely': ['@*', '<p><a href="/@*">@*</a></p>'], | ||
'can contain star - surrounded': ['@foo*bar', '<p><a href="/@foo*bar">@foo*bar</a></p>'], | ||
'no unescaped star': ['@*****', '<p>@*****</p>'], | ||
'escaped star': ['@**\\***', '<p><a href="/@*">@*</a></p>'], | ||
'space break ping': ['@foo bar', '<p><a href="/@foo">@foo</a> bar</p>'], | ||
'cannot contain inline - link': ['@**[link](hello)**', '<p><a href="/@%5Blink%5D(hello)">@[link](hello)</a></p>'], | ||
'is textual': ['**@foo**', '<p><strong><a href="/@foo">@foo</a></strong></p>', true], | ||
'intertwines with strong': ['**@**foo**', '<p><strong>@</strong>foo**</p>', true], | ||
'can contain references': ['@**#**', '<p><a href="/@#">@#</a></p>'], | ||
'can contain references': ['@#', '<p><a href="/@&amp;#35;">@&#35;</a></p>'], | ||
} | ||
|
||
const renderString = (fixture) => | ||
micromark(fixture, { | ||
extensions: [micromarkPing()], | ||
htmlExtensions: [micromarkPingHtml] | ||
}) | ||
|
||
describe('conforms to the specification', () => { | ||
for (const test in specificationTests) { | ||
const jestFunction = (!specificationTests[test][2]) ? it : it.skip | ||
|
||
jestFunction(test, () => { | ||
const [input, expectedOutput] = specificationTests[test] | ||
const output = renderString(input) | ||
|
||
expect(output).toEqual(expectedOutput) | ||
}) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { sanitizeUri } from 'micromark-util-sanitize-uri' | ||
|
||
export default { | ||
enter: { | ||
pingCall: enterPingCall | ||
}, | ||
exit: { | ||
pingCall: exitPingCall | ||
} | ||
} | ||
|
||
function enterPingCall () { | ||
this.buffer() | ||
} | ||
|
||
function exitPingCall () { | ||
const pingName = '@'.concat(this.resume()) | ||
const url = sanitizeUri('/'.concat(pingName)) | ||
|
||
this.tag(`<a href="${url}">`) | ||
this.raw(pingName) | ||
this.tag('</a>') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
import { markdownLineEnding, markdownLineEndingOrSpace } from 'micromark-util-character' | ||
import { codes } from 'micromark-util-symbol' | ||
|
||
export default function micromarkPing (options = {}) { | ||
// Character definitions, see specification, part 1 | ||
const escapeChar = 92 | ||
const atChar = options.pingChar || 64 | ||
const sequenceChar = options.sequenceChar || 42 | ||
|
||
const call = { | ||
name: 'ping', | ||
tokenize: tokenizeFactory({ | ||
atChar, | ||
escapeChar, | ||
sequenceChar | ||
}) | ||
} | ||
|
||
// Inject a hook on the at symbol | ||
return { | ||
text: { [atChar]: call } | ||
} | ||
} | ||
|
||
function tokenizeFactory (charCodes) { | ||
// Extract character code | ||
const { | ||
atChar, | ||
escapeChar, | ||
sequenceChar | ||
} = charCodes | ||
|
||
return tokenizePing | ||
|
||
function pingEnd (code) { | ||
return (markdownLineEndingOrSpace(code) || code === codes.eof) | ||
} | ||
|
||
function pingForcedEnd (code) { | ||
return (markdownLineEnding(code) || code === codes.eof) | ||
} | ||
|
||
function tokenizePing (effects, ok, nok) { | ||
let hasSequence = false | ||
let hasContent = false | ||
let token | ||
|
||
return atSymbol | ||
|
||
// Define a state `pingAtSymbol` that consumes the at symbol | ||
function atSymbol (code) { | ||
// Discard invalid characters | ||
if (code !== atChar) return nok(code) | ||
|
||
effects.enter('pingCall') | ||
effects.enter('pingAtSymbol') | ||
effects.consume(code) | ||
effects.exit('pingAtSymbol') | ||
|
||
return start | ||
} | ||
|
||
// Define a state `pingStart` that matches starting star sequence | ||
function start (code) { | ||
// Disallow empty pings | ||
if (pingEnd(code)) return nok(code) | ||
// Handle star sequences | ||
if (code === sequenceChar) return potentialStartSequence(code) | ||
// Handle escaped opening sequence | ||
if (code === escapeChar) return nok(code) | ||
|
||
effects.enter('pingContent') | ||
effects.enter('data') | ||
|
||
return content(code) | ||
} | ||
|
||
// Define a state `pingPotentialStartSequence` that consumes the first star in a sequence | ||
function potentialStartSequence (code) { | ||
if (code !== sequenceChar) return nok(code) | ||
|
||
token = effects.enter('pingStarSequence') | ||
effects.consume(code) | ||
|
||
return startSequence | ||
} | ||
|
||
// Define a state `pingStartSequence` that handles a star sequence | ||
function startSequence (code) { | ||
// Sequences of only one star are content if ending | ||
if (pingEnd(code)) { | ||
token.type = 'pingContent' | ||
|
||
const { start } = token | ||
|
||
token = effects.enter('data') | ||
token.start = start | ||
effects.exit('data') | ||
effects.exit('pingContent') | ||
effects.exit('pingCall') | ||
|
||
return ok(code) | ||
} | ||
|
||
if (code !== sequenceChar) return nok(code) | ||
|
||
hasSequence = true | ||
|
||
effects.consume(code) | ||
effects.exit('pingStarSequence') | ||
|
||
effects.enter('pingContent') | ||
effects.enter('chunkString', { contentType: 'string' }) | ||
|
||
return content | ||
} | ||
|
||
// Define a state `pingContent` that consumes the ping content | ||
function content (code) { | ||
// May end with star sequence | ||
if (code === sequenceChar) { | ||
return potentialEndSequence(code) | ||
} | ||
|
||
// Ends with space | ||
if (!hasSequence && pingEnd(code)) { | ||
effects.exit('data') | ||
effects.exit('pingContent') | ||
effects.exit('pingCall') | ||
|
||
return ok(code) | ||
} | ||
|
||
// Forced end | ||
if (pingForcedEnd(code)) return nok(code) | ||
|
||
hasContent = true | ||
effects.consume(code) | ||
|
||
return (code === escapeChar) ? contentEscape : content | ||
} | ||
|
||
// Define a state `pingContentEscape` to allow end sequence escape | ||
function contentEscape (code) { | ||
effects.consume(code) | ||
|
||
return content | ||
} | ||
|
||
// Define a state `pingPotentialEndSequence` that matches an end star sequence | ||
function potentialEndSequence (code) { | ||
if (code !== sequenceChar) { | ||
return content(code) | ||
} | ||
|
||
effects.exit('chunkString') | ||
effects.exit('pingContent') | ||
token = effects.enter('pingStarSequence') | ||
effects.consume(code) | ||
|
||
return endSequence | ||
} | ||
|
||
// Define a state `pingEndSequence` that handles a star sequence | ||
function endSequence (code) { | ||
// Ends with star sequence | ||
if (code === sequenceChar) { | ||
if (!hasContent) { | ||
return nok(code) | ||
} | ||
|
||
effects.consume(code) | ||
effects.exit('pingStarSequence') | ||
effects.exit('pingCall') | ||
|
||
return ok | ||
} | ||
|
||
// Sequences of only one star are content if ending | ||
if (pingEnd(code)) return nok(code) | ||
|
||
token.type = 'pingContent' | ||
|
||
const { start } = token | ||
token = effects.enter('chunkString', { contentType: 'string' }) | ||
token.start = start | ||
|
||
return content(code) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"name": "micromark-extension-ping", | ||
"version": "0.0.0", | ||
"description": "Add Markdown syntax to handle user mentions", | ||
"type": "module", | ||
"keywords": [ | ||
"micromark", | ||
"ping", | ||
"mentions", | ||
"plugin", | ||
"extension" | ||
], | ||
"author": "Titouan (Stalone) S. <[email protected]>", | ||
"homepage": "https://github.com/zestedesavoir/zmarkdown/tree/master/packages/micromark-extension-ping", | ||
"license": "MIT", | ||
"main": "lib/index.js", | ||
"module": "lib/index.js", | ||
"directories": { | ||
"lib": "lib", | ||
"test": "__tests__" | ||
}, | ||
"files": [ | ||
"lib" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/zestedesavoir/zmarkdown.git#master" | ||
}, | ||
"scripts": { | ||
"pretest": "eslint .", | ||
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", | ||
"coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/zestedesavoir/zmarkdown/issues" | ||
}, | ||
"dependencies": { | ||
"micromark-util-character": "^2.1.0", | ||
"micromark-util-symbol": "^2.0.0", | ||
"micromark-util-sanitize-uri": "^2.0.0" | ||
} | ||
} |
Oops, something went wrong.