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 <antreesy.web@gmail.com>
  • Loading branch information
Antreesy committed Jan 23, 2024
1 parent 0952f53 commit 772be39
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 8 deletions.
96 changes: 96 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, {
propsData: {
text: '~strikethrough single~',
useExtendedMarkdown: true,
},
})

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

it('strikethrough text (with double tilda syntax)', () => {
mount(NcRichText, {
propsData: {
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, {
propsData: {
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, {
propsData: {
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,49 @@ 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, {
propsData: {
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) => {
// Vue 2.7 renders three non-breaking spaces here for some reason
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, {
propsData: {
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
66 changes: 63 additions & 3 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 @@ -298,6 +340,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(h) {
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 All @@ -366,6 +415,17 @@ export default {
)
if (!tag.startsWith('#')) {
if (this.useExtendedMarkdown) {
if (tag === 'li' && Array.isArray(children)
&& children[0].tag === 'input'
&& children[0].data.attrs.type === 'checkbox') {
const [inputNode, , label] = children
const inputComponent = h(NcCheckboxRadioSwitch,
{ attrs: inputNode.data.attrs },
[label])
return h(tag, attrs, [inputComponent])
}
}
return h(tag, attrs, children)
}
Expand Down Expand Up @@ -409,7 +469,7 @@ export default {
},
},
render(h) {
return this.useMarkdown
return this.useMarkdown || this.useExtendedMarkdown
? this.renderMarkdown(h)
: this.renderPlaintext(h)
},
Expand Down
7 changes: 4 additions & 3 deletions src/components/NcRichText/autolink.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ 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 which can not be disabled
// and thus a local one is not needed
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 772be39

Please sign in to comment.