Skip to content

Commit

Permalink
enh(NcRichText): render flavored markdown
Browse files Browse the repository at this point in the history
Signed-off-by: Maksim Sukharev <[email protected]>
  • Loading branch information
Antreesy committed Jan 18, 2024
1 parent 7dd46b1 commit 13b441e
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 9 deletions.
95 changes: 95 additions & 0 deletions cypress/component/richtext.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,59 @@ describe('NcRichText', () => {
})
})

describe('strikethrough text', () => {
it('strikethrough text (with single tilda syntax)', () => {
mount(NcRichText, {
props: {
text: '~strikethrough single~',
useExtendedMarkdown: true,
},
})

cy.get('del').should('have.text', 'strikethrough single')
})

it('strikethrough text (with double tilda syntax)', () => {
mount(NcRichText, {
props: {
text: '~~strikethrough double~~',
useExtendedMarkdown: true,
},
})

cy.get('del').should('have.text', 'strikethrough double')
})

it('strikethrough text (several in line with different syntax)', () => {
const outputs = ['strikethrough single', 'strikethrough double']
mount(NcRichText, {
props: {
text: 'normal text ~strikethrough single~ normal text ~~strikethrough double~~ normal text',
useExtendedMarkdown: true,
},
})

cy.get('del').should('have.length', 2)
cy.get('del').each((item, index) => {
expect(item).have.text(outputs[index])
})
})

it('strikethrough text (between normal texts with different syntax)', () => {
mount(NcRichText, {
props: {
text: 'text~strikethrough~text~~strikethrough~~text',
useExtendedMarkdown: true,
},
})

cy.get('del').should('have.length', 2)
cy.get('del').each((item) => {
expect(item).have.text('strikethrough')
})
})
})

describe('inline code', () => {
it('inline code (single with backticks syntax)', () => {
mount(NcRichText, {
Expand Down Expand Up @@ -515,6 +568,48 @@ describe('NcRichText', () => {
})
})

describe('task lists', () => {
it('task list (with `- [ ]` and `- [x]` syntax divided with space from text)', () => {
const testCases = [
{ input: '- [ ] item 1', output: 'item 1', checked: false },
{ input: '- [x] item 2', output: 'item 2', checked: true },
{ input: '- [ ] item 3', output: 'item 3', checked: false },
]

mount(NcRichText, {
props: {
text: testCases.map(i => i.input).join('\n'),
useExtendedMarkdown: true,
},
})

cy.get('ul').should('exist')
cy.get('li').should('have.length', testCases.length)
cy.get('li').each((item, index) => {
expect(item).have.text(testCases[index].output)
})
cy.get('input:checked').should('have.length', testCases.filter(test => test.checked).length)
})
})

describe('tables', () => {
it('table (with `-- | --` syntax)', () => {
mount(NcRichText, {
props: {
text: 'Table | Column A | Column B\n-- | -- | --\nRow 1 | Value A1 | Value B1\nRow 2 | Value A2 | Value B2',
useExtendedMarkdown: true,
},
})

cy.get('table').should('exist')
cy.get('thead').should('exist')
cy.get('tbody').should('exist')
cy.get('tr').should('have.length', 3)
cy.get('th').should('have.length', 3)
cy.get('td').should('have.length', 6)
})
})

describe('dividers', () => {
it('dividers (with different syntax)', () => {
mount(NcRichText, {
Expand Down
70 changes: 66 additions & 4 deletions src/components/NcRichText/NcRichText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,46 @@ textarea {
</style>
```

### Flavored Markdown

This component can support [Github Flavored Markdown](https://github.github.com/gfm/).
It adds such elements, as tables, task lists, strikethrough, and supports autolinks by default

```vue
<template>
<div>
<textarea v-model="text" />

<NcRichText :text="text" :use-extended-markdown="true"/>
</div>
</template>
<script>
export default {
data() {
return {
text: `## Try flavored markdown right now!

~~strikethrough~~

- [ ] task to be done
- [x] task completed

Table header | Column A | Column B
-- | -- | --
Table row | value A | value B
`,
}
},
}
</script>
<style lang="scss">
textarea {
width: 100%;
height: 200px;
}
</style>
```

### Usage with NcRichContenteditable

See [NcRichContenteditable](#/Components/NcRichContenteditable) documentation for more information
Expand Down Expand Up @@ -235,11 +275,13 @@ See [NcRichContenteditable](#/Components/NcRichContenteditable) documentation fo

<script>
import NcReferenceList from './NcReferenceList.vue'
import NcCheckboxRadioSwitch from '../NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue'
import { remarkAutolink } from './autolink.js'
import { remarkPlaceholder, prepareTextNode } from './placeholder.js'

import { unified } from 'unified'
import markdown from 'remark-parse'
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import breaks from 'remark-breaks'
import remark2rehype from 'remark-rehype'
import rehype2react from 'rehype-react'
Expand Down Expand Up @@ -299,6 +341,11 @@ export default {
type: Boolean,
default: false,
},
/** Provide GitHub Flavored Markdown syntax */
useExtendedMarkdown: {
type: Boolean,
default: false,
},
autolink: {
type: Boolean,
default: true,
Expand Down Expand Up @@ -338,11 +385,13 @@ export default {
},
renderMarkdown() {
const renderedMarkdown = unified()
.use(markdown)
.use(remarkParse)
.use(remarkAutolink, {
autolink: this.autolink,
useMarkdown: this.useMarkdown,
useExtendedMarkdown: this.useExtendedMarkdown,
})
.use(this.useExtendedMarkdown ? remarkGfm : undefined)
.use(breaks)
.use(remark2rehype, {
handlers: {
Expand Down Expand Up @@ -385,7 +434,7 @@ export default {
if (key) {
props.key = key
}
// Children should be always an array
// Children could be an array of VNodes, a single VNode or a string
let children = props.children ?? []
delete props.children

Expand All @@ -395,6 +444,19 @@ export default {
}

if (!String(type).startsWith('#')) {
if (this.useExtendedMarkdown) {
if (String(type) === 'li' && Array.isArray(children)
&& String(children[0].type) === 'input'
&& children[0].props?.type === 'checkbox') {
const [inputNode, , label] = children
const id = 'markdown-input-' + GenRandomId(5)
const inputComponent = h(NcCheckboxRadioSwitch, {
...inputNode.props,
}, label)
return h(type, props, inputComponent)
}
}

return h(type, props, children)
}

Expand All @@ -419,7 +481,7 @@ export default {
},
},
render() {
return this.useMarkdown
return this.useMarkdown || this.useExtendedMarkdown
? this.renderMarkdown()
: this.renderPlaintext()
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/NcRichText/autolink.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const NcLink = {
},
}

export const remarkAutolink = function({ autolink, useMarkdown }) {
export const remarkAutolink = function({ autolink, useMarkdown, useExtendedMarkdown }) {
return function(tree) {

if (!useMarkdown || !autolink) {
// remark-gfm has its own autolink parser
if (useExtendedMarkdown || !useMarkdown || !autolink) {
return
}

Expand Down
35 changes: 33 additions & 2 deletions src/components/NcRichText/richtext.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@

.rich-text--wrapper-markdown {
div > *:first-child,
blockquote > *:first-child{
blockquote > *:first-child {
margin-top: 0 !important;
}
div > *:last-child ,
div > *:last-child,
blockquote > *:last-child {
margin-bottom: 0 !important;
}
Expand All @@ -164,6 +164,37 @@
list-style-type: disc;
}

ul.contains-task-list {
list-style-type: none;
padding: 0;
}

table {
border-collapse: collapse;
border: 2px solid var(--color-border-maxcontrast);

th,
td {
padding: var(--default-grid-baseline);
border: 1px solid var(--color-border-maxcontrast);
&:first-child {
border-left: 0;
}
&:last-child {
border-right: 0;
}
}

tr {
&:first-child th {
border-top: 0;
}
&:last-child td {
border-bottom: 0;
}
}
}

blockquote {
padding-left: 13px;
border-left: 2px solid var(--color-border-dark);
Expand Down

0 comments on commit 13b441e

Please sign in to comment.