Add navrow + markdown parsing
This commit is contained in:
parent
de844e9b23
commit
8167f6f232
@ -38,6 +38,7 @@ export default {
|
||||
{ text: 'Search Filter', link: '/components/search-filter' },
|
||||
{ text: 'Toggle', link: '/components/toggle' },
|
||||
{ text: 'Promotion', link: '/components/promotion' },
|
||||
{ text: 'Navigation Row', link: '/components/nav-row' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
47
docs/components/nav-row.md
Normal file
47
docs/components/nav-row.md
Normal file
@ -0,0 +1,47 @@
|
||||
# NavRow
|
||||
|
||||
Note: the links and animation do not work in the documentation as Vue Router is not used for routing
|
||||
|
||||
<DemoContainer>
|
||||
<NavRow :links="[
|
||||
{
|
||||
label: 'Avatar',
|
||||
href: '/components/avatar.html',
|
||||
},
|
||||
{
|
||||
label: 'Badge',
|
||||
href: '/components/nav-row.html',
|
||||
},
|
||||
{
|
||||
label: 'Pagination',
|
||||
href: '/components/Pagination.html',
|
||||
},
|
||||
{
|
||||
label: 'NavRow',
|
||||
href: '/components/nav-row.html',
|
||||
}
|
||||
]"
|
||||
/>
|
||||
</DemoContainer>
|
||||
|
||||
```vue
|
||||
<NavRow :links="[
|
||||
{
|
||||
label: 'Avatar',
|
||||
href: '/components/avatar.html',
|
||||
},
|
||||
{
|
||||
label: 'Badge',
|
||||
href: '/components/nav-row.html',
|
||||
},
|
||||
{
|
||||
label: 'Pagination',
|
||||
href: '/components/Pagination.html',
|
||||
},
|
||||
{
|
||||
label: 'NavRow',
|
||||
href: '/components/nav-row.html',
|
||||
}
|
||||
]"
|
||||
/>
|
||||
```
|
||||
@ -609,6 +609,7 @@ a,
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: var(--color-link);
|
||||
|
||||
&:focus-visible,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fileIsValid } from '@/components/utils'
|
||||
import { fileIsValid } from '@/helpers/utils.js'
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import { Modal, Chips, XIcon, CheckIcon, DropdownSelect } from '@/components'
|
||||
import { renderString } from '@/components/parse.js'
|
||||
import { renderString } from '@/helpers/parse.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const modal = ref('modal')
|
||||
|
||||
@ -76,7 +76,7 @@ import {
|
||||
Categories,
|
||||
EnvironmentIndicator,
|
||||
} from '@/components'
|
||||
import { formatNumber } from '@/components/utils'
|
||||
import { formatNumber } from '@/helpers/utils.js'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
@ -1,51 +1,166 @@
|
||||
<script setup>
|
||||
defineProps({})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="omorphia__navrow">
|
||||
<slot />
|
||||
<div class="right-slot">
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navigation">
|
||||
<router-link
|
||||
v-for="(link, index) in filteredLinks"
|
||||
v-show="link.shown === undefined ? true : link.shown"
|
||||
:key="index"
|
||||
ref="linkElements"
|
||||
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
|
||||
class="nav-link button-animation"
|
||||
>
|
||||
<span>{{ link.label }}</span>
|
||||
</router-link>
|
||||
<div
|
||||
class="nav-indicator"
|
||||
:style="{
|
||||
left: positionToMoveX,
|
||||
top: positionToMoveY,
|
||||
width: sliderWidth,
|
||||
opacity: activeIndex === -1 ? 0 : 1,
|
||||
}"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
links: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
query: {
|
||||
default: null,
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sliderPositionX: 0,
|
||||
sliderPositionY: 18,
|
||||
selectedElementWidth: 0,
|
||||
activeIndex: -1,
|
||||
oldIndex: -1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredLinks() {
|
||||
return this.links.filter((x) => (x.shown === undefined ? true : x.shown))
|
||||
},
|
||||
positionToMoveX() {
|
||||
return `${this.sliderPositionX}px`
|
||||
},
|
||||
positionToMoveY() {
|
||||
return `${this.sliderPositionY}px`
|
||||
},
|
||||
sliderWidth() {
|
||||
return `${this.selectedElementWidth}px`
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.path': {
|
||||
handler() {
|
||||
this.pickLink()
|
||||
},
|
||||
},
|
||||
'$route.query': {
|
||||
handler() {
|
||||
if (this.query) this.pickLink()
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.pickLink)
|
||||
this.pickLink()
|
||||
},
|
||||
unmounted() {
|
||||
window.removeEventListener('resize', this.pickLink)
|
||||
},
|
||||
methods: {
|
||||
pickLink() {
|
||||
this.activeIndex = this.query
|
||||
? this.filteredLinks.findIndex(
|
||||
(x) => (x.href === '' ? undefined : x.href) === this.$route.path[this.query]
|
||||
)
|
||||
: this.filteredLinks.findIndex((x) => x.href === decodeURIComponent(this.$route.path))
|
||||
|
||||
if (this.activeIndex !== -1) {
|
||||
this.startAnimation()
|
||||
} else {
|
||||
this.oldIndex = -1
|
||||
this.sliderPositionX = 0
|
||||
this.selectedElementWidth = 0
|
||||
}
|
||||
},
|
||||
startAnimation() {
|
||||
const el = this.$refs.linkElements[this.activeIndex].$el
|
||||
|
||||
this.sliderPositionX = el.offsetLeft
|
||||
this.sliderPositionY = el.offsetTop + el.offsetHeight
|
||||
this.selectedElementWidth = el.offsetWidth
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.omorphia__navrow {
|
||||
.navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
grid-gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
|
||||
.right-slot {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
:deep(.btn) {
|
||||
.nav-link {
|
||||
text-transform: capitalize;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: calc(100% - var(--gap-lg) * 2);
|
||||
height: 4px;
|
||||
bottom: 4px;
|
||||
border-radius: var(--radius-max);
|
||||
background-color: var(--color-brand);
|
||||
opacity: 0;
|
||||
&:hover {
|
||||
color: var(--color-text);
|
||||
|
||||
&::after {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
opacity: 50%;
|
||||
&:active::after {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
color: var(--color-contrast);
|
||||
font-weight: bold;
|
||||
&.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.use-animation {
|
||||
.nav-link {
|
||||
&.is-active::after {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-indicator {
|
||||
position: absolute;
|
||||
height: 0.25rem;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
width: 3rem;
|
||||
transition: all ease-in-out 0.2s;
|
||||
border-radius: var(--size-rounded-max);
|
||||
background-color: var(--color-brand);
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { formatCategory } from '@/components/utils'
|
||||
import { formatCategory } from '@/helpers/utils.js'
|
||||
</script>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
63
lib/helpers/highlight.js
Normal file
63
lib/helpers/highlight.js
Normal file
@ -0,0 +1,63 @@
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
// Scripting
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import python from 'highlight.js/lib/languages/python'
|
||||
import lua from 'highlight.js/lib/languages/lua'
|
||||
// Coding
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import kotlin from 'highlight.js/lib/languages/kotlin'
|
||||
import scala from 'highlight.js/lib/languages/scala'
|
||||
import groovy from 'highlight.js/lib/languages/groovy'
|
||||
// Configs
|
||||
import gradle from 'highlight.js/lib/languages/gradle'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import ini from 'highlight.js/lib/languages/ini'
|
||||
import yaml from 'highlight.js/lib/languages/yaml'
|
||||
import xml from 'highlight.js/lib/languages/xml'
|
||||
import properties from 'highlight.js/lib/languages/properties'
|
||||
import { md, configuredXss } from '@/helpers/parse'
|
||||
|
||||
/* REGISTRATION */
|
||||
// Scripting
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('python', python)
|
||||
hljs.registerLanguage('lua', lua)
|
||||
// Coding
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('kotlin', kotlin)
|
||||
hljs.registerLanguage('scala', scala)
|
||||
hljs.registerLanguage('groovy', groovy)
|
||||
// Configs
|
||||
hljs.registerLanguage('gradle', gradle)
|
||||
hljs.registerLanguage('json', json)
|
||||
hljs.registerLanguage('ini', ini)
|
||||
hljs.registerLanguage('yaml', yaml)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('properties', properties)
|
||||
|
||||
/* ALIASES */
|
||||
// Scripting
|
||||
hljs.registerAliases(['js'], { languageName: 'javascript' })
|
||||
hljs.registerAliases(['py'], { languageName: 'python' })
|
||||
// Coding
|
||||
hljs.registerAliases(['kt'], { languageName: 'kotlin' })
|
||||
// Configs
|
||||
hljs.registerAliases(['json5'], { languageName: 'json' })
|
||||
hljs.registerAliases(['toml'], { languageName: 'ini' })
|
||||
hljs.registerAliases(['yml'], { languageName: 'yaml' })
|
||||
hljs.registerAliases(['html', 'htm', 'xhtml', 'mcui', 'fxml'], { languageName: 'xml' })
|
||||
|
||||
export const renderHighlightedString = (string) =>
|
||||
configuredXss.process(
|
||||
md({
|
||||
highlight: function (str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(str, { language: lang }).value
|
||||
} catch (__) { /* empty */ }
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
}).render(string)
|
||||
)
|
||||
3
lib/helpers/index.js
Normal file
3
lib/helpers/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './highlight'
|
||||
export * from './parse'
|
||||
export * from './utils'
|
||||
@ -31,6 +31,10 @@ export const configuredXss = new xss.FilterXSS({
|
||||
/^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[a-zA-Z0-9_-]{11}(\?&autoplay=[0-1]{1})?$/,
|
||||
remove: ['&autoplay=1'], // Prevents autoplay
|
||||
},
|
||||
{
|
||||
regex: /^https?:\/\/(www\.)?discord\.com\/widget\?id=\d{18,19}(&theme=\w+)?$/,
|
||||
remove: [/&theme=\w+/],
|
||||
},
|
||||
]
|
||||
|
||||
for (const source of allowedSources) {
|
||||
@ -82,9 +86,7 @@ export const md = (options = {}) => {
|
||||
if (allowedHostnames.includes(url.hostname)) {
|
||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
tokens[idx].attrSet('rel', 'noopener nofollow ugc')
|
||||
@ -109,21 +111,22 @@ export const md = (options = {}) => {
|
||||
const url = new URL(src)
|
||||
|
||||
const allowedHostnames = [
|
||||
'imgur.com',
|
||||
'i.imgur.com',
|
||||
'cdn-raw.modrinth.com',
|
||||
'cdn.modrinth.com',
|
||||
'staging-cdn-raw.modrinth.com',
|
||||
'staging-cdn.modrinth.com',
|
||||
'github.com',
|
||||
'raw.githubusercontent.com',
|
||||
'img.shields.io',
|
||||
'i.postimg.cc',
|
||||
]
|
||||
|
||||
if (allowedHostnames.includes(url.hostname)) {
|
||||
return defaultImageRenderer(tokens, idx, options, env, self)
|
||||
}
|
||||
} catch (err) {
|
||||
/* empty */
|
||||
}
|
||||
} catch (err) {}
|
||||
token.attrs[index][1] = `//wsrv.nl/?url=${encodeURIComponent(src)}`
|
||||
}
|
||||
|
||||
@ -10,5 +10,6 @@ function install(app) {
|
||||
|
||||
export default { install }
|
||||
export * from './components'
|
||||
export * from './helpers'
|
||||
|
||||
import './assets/omorphia.scss'
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.7",
|
||||
"floating-vue": "^2.0.0-beta.20",
|
||||
"highlight.js": "^11.7.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6",
|
||||
|
||||
@ -1064,6 +1064,11 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
highlight.js@^11.7.0:
|
||||
version "11.7.0"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
|
||||
integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==
|
||||
|
||||
ignore@^5.2.0:
|
||||
version "5.2.4"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user