Compare commits
10 Commits
Author | SHA1 | Date |
---|---|---|
Sol Fisher Romanoff | 982958ce2b | |
Sol Fisher Romanoff | 2dcb6d82be | |
Sol Fisher Romanoff | d6e115fbb8 | |
Sol Fisher Romanoff | cb12e3a149 | |
Sol Fisher Romanoff | 600dd77e50 | |
Sol Fisher Romanoff | 092b141f5b | |
Sol Fisher Romanoff | 4d885754fd | |
Sol Fisher Romanoff | c748bacc0f | |
Sol Fisher Romanoff | 08c65455e5 | |
Sol Fisher Romanoff | 4a6bd142d4 |
19
README.md
19
README.md
|
@ -2,6 +2,7 @@
|
|||
a [Marked.js](https://marked.js.org) custom extension for [Misskey-flavored Markdown](https://github.com/misskey-dev/mfm.js/blob/develop/docs/syntax.md).
|
||||
|
||||
this is a reimplementation of [the original MFM parser](https://github.com/misskey-dev/mfm.js/blob/develop/docs/syntax.md), which was written from the ground up using PEG.js instead of as an extension of CommonMark, and therefore was not up to spec.
|
||||
this extension is mostly compatible with the original, except it only implements `$[tags]`. mentions, hashtags and the `search` markup are out of scope.
|
||||
|
||||
## usage
|
||||
|
||||
|
@ -11,5 +12,21 @@ const markedMfm = require('marked-mfm')
|
|||
|
||||
marked.use(markedMfm)
|
||||
marked.parse('$[x2 beeg text]')
|
||||
// <p><span class="_mfm_x2_" >beeg text</span></p>
|
||||
// <p><span class="mfm _mfm_x2_" >beeg text</span></p>
|
||||
```
|
||||
|
||||
there is also an interactive CLI:
|
||||
|
||||
```html
|
||||
$ npm run dingus
|
||||
marked-mfm interactive parser
|
||||
version 0.0.0
|
||||
> $[x2 beeg text]
|
||||
<p><span class="mfm _mfm_x2_" >beeg text</span></p>
|
||||
```
|
||||
|
||||
you should use it to figure out whether a bug is with marked-mfm or with the frontend it's used on.
|
||||
|
||||
## contributing
|
||||
|
||||
development discussion takes place on [`#mfm`](https://irc.akkoma.dev/#mfm). contribute by sending pull requests, or patches to my [public inbox](mailto:~sfr/public-inbox@lists.sr.ht).
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# Misskey-flavored Markdown syntax and cheat sheet
|
||||
|
||||
this document acts as the canonical source of information about MFM implemented
|
||||
by `marked-mfm`. if you are looking for the implementation used in Misskey, refer
|
||||
to [this page on
|
||||
`mfm.js`](https://github.com/misskey-dev/mfm.js/blob/develop/docs/syntax.md).
|
||||
|
||||
## Markdown-to-HTML parser
|
||||
|
||||
all MFM elements are parsed to HTML like so:
|
||||
|
||||
```html
|
||||
<span class="mfm _mfm_tag_" data-param1 data-param2>text</span>
|
||||
```
|
||||
|
||||
refer to the rest of the document for information on tags and parameters.
|
||||
|
||||
## MFM tags
|
||||
|
||||
| MFM | summary |
|
||||
|------------------|--------------------------------------------|
|
||||
| `$[tada text]` | "tada!"-like animation |
|
||||
| `$[jelly text]` | squashing and stretching animation |
|
||||
| `$[twitch text]` | stuttering animation |
|
||||
| `$[shake text]` | stuttering and rotating animation |
|
||||
| `$[spin text]` | spinning animation |
|
||||
| `$[jump text]` | jumping animation |
|
||||
| `$[bounce text]` | jumping and squashing animation |
|
||||
| `$[flip text]` | mirrors the element |
|
||||
| `$[x2 text]` | big text |
|
||||
| `$[x3 text]` | bigger text |
|
||||
| `$[x4 text]` | even bigger text |
|
||||
| `$[font text]` | sets a font do display the content in |
|
||||
| `$[rotate text]` | rotates the element by a number of degrees |
|
||||
|
||||
## optional parameters
|
||||
|
||||
some MFM elements allow specifying additional attributes in the tag. this is
|
||||
done by adding a `.` to the tag name, followed by a comma-separated list of
|
||||
attributes.
|
||||
|
||||
example: `$[spin.alternate,speed=3s text]`
|
||||
|
||||
- **`speed`**
|
||||
*valid in: `jelly`, `twitch`, `shake`, `spin`*
|
||||
a number, in seconds, that defines the duration of the animation
|
||||
example: `$[jelly.speed=2s text]`
|
||||
- **`h`, `v`**
|
||||
*valid in: `flip`*
|
||||
defines the axis the element is flipped upon; horizontally, vertically, or
|
||||
both
|
||||
example: `$[flip.h.v text]`
|
||||
- **`x`, `y`**
|
||||
*valid in: `spin`*
|
||||
defines the axis on which to spin the element
|
||||
example: `$[spin.x text]`
|
||||
- **`alternate`, `left`**
|
||||
*valid in: `spin`*
|
||||
defines whether to run the spinning animation in reverse, or to alternate it
|
||||
example: `$[spin.left text]`
|
||||
- **`serif`, `monospace`, `cursive`, `fantasy`, `emoji`, `math`**
|
||||
*valid in: `font`*
|
||||
sets the font inside the font element; if unset, the element does nothing
|
||||
example: `$[font.fantasy text]`
|
||||
* **`deg`**
|
||||
*valid in: `rotate`*
|
||||
a number of degrees to rotate the element; if unset, 30° is used
|
||||
example: `$[rotate.deg=120 text]`
|
||||
|
|
@ -94,6 +94,10 @@
|
|||
var walk = 0;
|
||||
|
||||
while (level > 0 || walk === 0) {
|
||||
if (walk >= src.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (src[walk] + src[walk + 1] === '$[') {
|
||||
level++;
|
||||
walk++;
|
||||
|
@ -144,22 +148,37 @@
|
|||
return "$[" + token.tag + " " + this.parser.parseInline(token.tokens) + "]";
|
||||
}
|
||||
}, {
|
||||
name: 'mfmSearch',
|
||||
name: 'escapedMfm',
|
||||
level: 'inline',
|
||||
start: function start(src) {
|
||||
var _src$match2;
|
||||
|
||||
return (_src$match2 = src.match(/(?<=^|\n).+ search(?=$|\n)/)) == null ? void 0 : _src$match2.index;
|
||||
return src.match(/\\\$\[/);
|
||||
},
|
||||
tokenizer: function tokenizer(src, tokens) {
|
||||
var rule = /^(.+) search(?=$|\n)/;
|
||||
if (/^\\\$\[/.exec(src)) {
|
||||
return {
|
||||
type: 'escapedMfm',
|
||||
raw: '\\$['
|
||||
};
|
||||
}
|
||||
},
|
||||
renderer: function renderer(token) {
|
||||
return '$[';
|
||||
}
|
||||
}, {
|
||||
name: 'center',
|
||||
level: 'block',
|
||||
start: function start(src) {
|
||||
return src.match(/<center>/);
|
||||
},
|
||||
tokenizer: function tokenizer(src, tokens) {
|
||||
var rule = /^<center>([\S\s]*)<\/center>/;
|
||||
var match = rule.exec(src);
|
||||
|
||||
if (match) {
|
||||
var token = {
|
||||
type: 'mfmSearch',
|
||||
type: 'center',
|
||||
raw: match[0],
|
||||
query: match[1].trim(),
|
||||
text: match[1],
|
||||
tokens: []
|
||||
};
|
||||
this.lexer.inline(token.text, token.tokens);
|
||||
|
@ -167,7 +186,7 @@
|
|||
}
|
||||
},
|
||||
renderer: function renderer(token) {
|
||||
return "<a href=\"https://google.com/search?q=" + token.query + "\" class=\"mfm _mfm_search_\">" + token.raw + "</a>";
|
||||
return "<center>" + this.parser.parseInline(token.tokens) + "</center>\n";
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "marked-mfm",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "marked-mfm",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.1",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.18.6",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "marked-mfm",
|
||||
"version": "0.2.1",
|
||||
"version": "0.5.0",
|
||||
"description": "Marked.js extension for Misskey-flavored Markdown",
|
||||
"main": "./src/index.js",
|
||||
"browser": "./lib/index.umd.js",
|
||||
|
@ -9,7 +9,8 @@
|
|||
"lint": "eslint .",
|
||||
"lint-fix": "eslint --fix .",
|
||||
"test": "jest --verbose",
|
||||
"build": "rollup -c rollup.config.umd.js"
|
||||
"build": "rollup -c rollup.config.umd.js",
|
||||
"dingus": "node ./src/dingus.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import readline from 'readline'
|
||||
import { marked } from 'marked'
|
||||
import markedMfm from '../src/index.js'
|
||||
|
||||
const prompt = () => (new Promise((resolve) => {
|
||||
const rl = readline.createInterface(process.stdin, process.stdout)
|
||||
rl.question('> ', (input) => {
|
||||
resolve(input)
|
||||
rl.close()
|
||||
})
|
||||
}))
|
||||
|
||||
marked.use(markedMfm)
|
||||
|
||||
console.log('marked-mfm interactive parser')
|
||||
console.log('version', process.env.npm_package_version)
|
||||
|
||||
while (true) {
|
||||
const input = await prompt()
|
||||
console.log(marked(input))
|
||||
}
|
28
src/index.js
28
src/index.js
|
@ -10,6 +10,9 @@ export default {
|
|||
let level = 0
|
||||
let walk = 0
|
||||
while (level > 0 || walk === 0) {
|
||||
if (walk >= src.length) {
|
||||
return null
|
||||
}
|
||||
if (src[walk] + src[walk + 1] === '$[') {
|
||||
level++
|
||||
walk++
|
||||
|
@ -48,17 +51,30 @@ export default {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: 'mfmSearch',
|
||||
name: 'escapedMfm',
|
||||
level: 'inline',
|
||||
start (src) { return src.match(/(?<=^|\n).+ search(?=$|\n)/)?.index },
|
||||
start (src) { return src.match(/\\\$\[/) },
|
||||
tokenizer (src, tokens) {
|
||||
const rule = /^(.+) search(?=$|\n)/
|
||||
if (/^\\\$\[/.exec(src)) {
|
||||
return { type: 'escapedMfm', raw: '\\$[' }
|
||||
}
|
||||
},
|
||||
renderer (token) {
|
||||
return '$['
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'center',
|
||||
level: 'block',
|
||||
start (src) { return src.match(/<center>/) },
|
||||
tokenizer (src, tokens) {
|
||||
const rule = /^<center>([\S\s]*)<\/center>/
|
||||
const match = rule.exec(src)
|
||||
if (match) {
|
||||
const token = {
|
||||
type: 'mfmSearch',
|
||||
type: 'center',
|
||||
raw: match[0],
|
||||
query: match[1].trim(),
|
||||
text: match[1],
|
||||
tokens: [],
|
||||
}
|
||||
this.lexer.inline(token.text, token.tokens)
|
||||
|
@ -66,7 +82,7 @@ export default {
|
|||
}
|
||||
},
|
||||
renderer (token) {
|
||||
return `<a href="https://google.com/search?q=${token.query}" class="mfm _mfm_search_">${token.raw}</a>`
|
||||
return `<center>${this.parser.parseInline(token.tokens)}</center>\n`
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -42,8 +42,13 @@ describe('marked-mfm', () => {
|
|||
expect(marked('$[spin.alternate,speed=0.5s test]')).toBe('<p><span class="mfm _mfm_spin_" data-alternate data-speed="0.5s">test</span></p>\n')
|
||||
})
|
||||
|
||||
test('search', () => {
|
||||
test('escaped', () => {
|
||||
marked.use(markedMfm)
|
||||
expect(marked('syuilo thighs search')).toBe('<p><a href="https://google.com/search?q=syuilo thighs" class="_mfm_search_">syuilo thighs search</a></p>\n')
|
||||
expect(marked('\\$[spin test]')).toBe('<p>$[spin test]</p>\n')
|
||||
})
|
||||
|
||||
test('center', () => {
|
||||
marked.use(markedMfm)
|
||||
expect(marked('<center>test *italic*</center>')).toBe('<center>test <em>italic</em></center>\n')
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue