Last active
March 19, 2024 12:45
-
-
Save jaythomas/908b3a022a7111844409b70227b1d0bc to your computer and use it in GitHub Desktop.
Markup.vue (Parse and display Markdown-ish text)
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
import { strictEqual } from 'assert'; | |
import { describe, it } from '@jest/globals'; | |
import { createLocalVue, mount, RouterLinkStub } from '@vue/test-utils'; | |
import Vue from 'vue'; | |
import { findId } from '~/helpers/test-utils'; | |
import ElMarkup from './Markup'; | |
const createWrapper = (options = {}) => { | |
const localVue = createLocalVue(); | |
localVue.component('RouterLink', RouterLinkStub); | |
return mount(ElMarkup, { | |
localVue, | |
...options | |
}); | |
}; | |
// Convenience function to DRY up all the assertions below | |
const wrapperHas = (wrapper, expectedOutput) => { | |
strictEqual( | |
findId(wrapper, 'markup:text').element.innerHTML.trim(), | |
expectedOutput | |
); | |
}; | |
const wrapperHasTitle = (wrapper, expectedOutput) => { | |
strictEqual( | |
findId(wrapper, 'markup:text').element.title.trim(), | |
expectedOutput | |
); | |
}; | |
describe('ElMarkup component', () => { | |
it('does not parse markup by default', () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: false, | |
val: '<p>Hello <b>W</b>orld</p>' | |
} | |
}); | |
wrapperHas(wrapper, '<p>Hello <b>W</b>orld</p>'); | |
wrapperHasTitle(wrapper, ''); | |
}); | |
it('renders html', () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: true, | |
val: '<p>Hello <b>W</b>orld</p>™🅪' | |
} | |
}); | |
wrapperHas(wrapper, '<p>Hello <b>W</b>orld</p>™🅪'); | |
wrapperHasTitle(wrapper, 'Hello World™🅪'); | |
}); | |
it('renders uncolored tags', () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: true, | |
val: '[My Tag]' | |
} | |
}); | |
wrapperHas(wrapper, '<span class="tag">My Tag</span>'); | |
wrapperHasTitle(wrapper, '[My Tag]'); | |
}); | |
it('renders colored tags', async () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: true, | |
val: 'bl[Blue]' | |
} | |
}); | |
wrapperHas(wrapper, '<span class="tag tag-bl">Blue</span>'); | |
wrapperHasTitle(wrapper, '[Blue]'); | |
wrapper.setProps({ val: 'db[Dark Blue]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-db">Dark Blue</span>'); | |
wrapper.setProps({ val: 'lb[Light Blue]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-lb">Light Blue</span>'); | |
wrapper.setProps({ val: 'lt[Light]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-lt">Light</span>'); | |
wrapper.setProps({ val: 'wh[White]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-wh">White</span>'); | |
wrapper.setProps({ val: 'dk[Dark]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-dk">Dark</span>'); | |
wrapper.setProps({ val: 'dg[Dark (Gizmo)]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-dg">Dark (Gizmo)</span>'); | |
wrapper.setProps({ val: 'bg[Black (Gizmo)]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-bg">Black (Gizmo)</span>'); | |
wrapper.setProps({ val: 'yw[Yellow]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-yw">Yellow</span>'); | |
wrapper.setProps({ val: 'or[Orange]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-or">Orange</span>'); | |
wrapper.setProps({ val: 'pu[Purple]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-pu">Purple</span>'); | |
wrapper.setProps({ val: 'gn[Green]' }); | |
await Vue.nextTick(); | |
wrapperHas(wrapper, '<span class="tag tag-gn">Green</span>'); | |
}); | |
it('renders urls as anchor tags', () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: true, | |
val: 'This is a url: https://example.com' | |
} | |
}); | |
wrapperHas( | |
wrapper, | |
'This is a url: <a target="_blank" href="https://example.com">https://example.com</a>' | |
); | |
wrapperHasTitle( | |
wrapper, | |
'This is a url: https://example.com' | |
); | |
}); | |
it('renders markdown-style inline urls', () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: true, | |
val: 'Now [this is the url](https://example.com)' | |
} | |
}); | |
wrapperHas( | |
wrapper, | |
'Now <a target="_blank" href="https://example.com">this is the url</a>' | |
); | |
wrapperHasTitle( | |
wrapper, | |
'Now [this is the url](https://example.com)' | |
); | |
}); | |
it('renders icon with slash', () => { | |
const wrapper = createWrapper({ | |
propsData: { | |
parse: true, | |
val: '/compass/ Dashboard Text' | |
} | |
}); | |
wrapperHas( | |
wrapper, | |
'<i class="icn fas fa-compass"></i> Dashboard Text' | |
); | |
wrapperHasTitle( | |
wrapper, | |
'Dashboard Text' | |
); | |
}); | |
}); |
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
<template> | |
<!-- eslint-disable vue/no-v-html --> | |
<span> | |
<span | |
v-if="parse" | |
:data-test-id="textTestId + ':text'" | |
:title="stripMarkup(val)" | |
class="el-md" | |
v-html="parseMarkDown(val)" | |
></span> | |
<span | |
v-else | |
:data-test-id="textTestId + ':text'" | |
class="el-md" | |
>{{ val }}</span> | |
</span> | |
</template> | |
<script> | |
export default { | |
name: 'ElMarkup', | |
props: { | |
val: { | |
type: String, | |
required: true | |
}, | |
parse: { | |
type: [Array, Boolean], | |
default: null | |
}, | |
textTestId: { | |
type: String, | |
default: 'markup' | |
} | |
}, | |
methods: { | |
parseMarkDown(val) { | |
// Simple rich text processing. See specs file for example usage. | |
val = ` ${val} `; | |
return val | |
.replace( | |
/(?:\s\/)([^/<\n]+)(?:\/\s)/g, | |
' <i class="icn fas fa-$1"></i> ' | |
) | |
.replace(/(?:\*)([^*<\n]+)(?:\*)/g, '<strong>$1</strong>') | |
.replace(/(?:_)([^_<\n]+)(?:_)/g, '<i>$1</i>') | |
.replace( | |
/(\s(..)\[)([^[<\n]+)(?:\]\s)/gi, | |
' <span class="tag tag-$2">$3</span> ' | |
) | |
.replace( | |
/(\s(..)\[)([^[<\n]+)(?:\](?!\()\s)/gi, | |
' <span class="tag tag-$2">$3</span> ' | |
) | |
.replace(/(?:\[)([^[<\n]+)(?:\](?!\())/g, ' <span class="tag">$1</span> ') | |
.replace( | |
/\s(\b(?:https?:\/\/)(?:(?:[a-z,-]+\.)+)[^\s,]+\b)/g, | |
' <a target="_blank" href="$1">$1</a> ' | |
) | |
.replace( | |
/\s\[([\w\s\d]+)\]\((https?:\/\/[\w\d./?=#-]+)\)\s$/gm, | |
' <a target="_blank" href="$2">$1</a> ' | |
); | |
}, | |
stripMarkup(val) { | |
return ` ${val} ` | |
.replace(/(?:\s\/)([^/<\n]+)(?:\/\s)/g, '') | |
.replace(/(?:\*)([^*<\n]+)(?:\*)/g, '$1') | |
.replace(/(?:_)([^_<\n]+)(?:_)/g, '$1') | |
.replace(/<\/?\w+>/g, '') | |
.replace( | |
/(\s(..)\[)([^[<\n]+)(?:\]\s)/gi, | |
' [$3] ' | |
) | |
.replace( | |
/(\s(..)\[)([^[<\n]+)(?:\](?!\()\s)/gi, | |
' [$3] ' | |
); | |
} | |
} | |
}; | |
</script> | |
<style lang="scss"> | |
.el-md { | |
overflow: hidden; | |
text-overflow: ellipsis; | |
strong { | |
font-weight: bolder; | |
} | |
a { | |
text-decoration: none; | |
color: #5bcefd; | |
} | |
.tag { | |
font-size: 0.8em; | |
padding: 0 0.35em; | |
border-radius: 0.25em; | |
display: inline-block; | |
margin-bottom: 0.15em; | |
vertical-align: middle; | |
line-height: normal; | |
white-space: nowrap; | |
// gizmos stem-case tag | |
&.tag-bl { | |
color: #fff !important; | |
background: #2783a8 !important; | |
} | |
&.tag-db { | |
color: #fff !important; | |
background: #1b5c75 !important; | |
} | |
// gizmos handbook tag | |
&.tag-lb { | |
color: #1b5c75 !important; | |
background: #e1effc !important; | |
} | |
&.tag-lt, | |
&.tag-wh { | |
color: #222 !important; | |
background: #fff !important; | |
} | |
&.tag-dk { | |
color: #fff !important; | |
background: #222 !important; | |
} | |
// gizmos updated tag | |
&.tag-dg { | |
color: #fff !important; | |
background: #757575 !important; | |
} | |
// gizmos metric tag | |
&.tag-bg { | |
color: #fff !important; | |
background: #1A1A1A !important; | |
} | |
&.tag-yw { | |
color: #444 !important; | |
background: rgb(238, 206, 26) !important; | |
} | |
&.tag-or { | |
color: #fff !important; | |
background: #ed9137 !important; | |
} | |
&.tag-pu { | |
color: #fff !important; | |
background: #974bb7 !important; | |
} | |
&.tag-gn { | |
color: #fff !important; | |
background: #41b445 !important; | |
} | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment