You've already forked AstralRinth
forked from didirus/AstralRinth
Add navrow + markdown parsing
This commit is contained in:
@@ -38,6 +38,7 @@ export default {
|
|||||||
{ text: 'Search Filter', link: '/components/search-filter' },
|
{ text: 'Search Filter', link: '/components/search-filter' },
|
||||||
{ text: 'Toggle', link: '/components/toggle' },
|
{ text: 'Toggle', link: '/components/toggle' },
|
||||||
{ text: 'Promotion', link: '/components/promotion' },
|
{ 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 {
|
a {
|
||||||
|
cursor: pointer;
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
|
|
||||||
&:focus-visible,
|
&:focus-visible,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { fileIsValid } from '@/components/utils'
|
import { fileIsValid } from '@/helpers/utils.js'
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Modal, Chips, XIcon, CheckIcon, DropdownSelect } from '@/components'
|
import { Modal, Chips, XIcon, CheckIcon, DropdownSelect } from '@/components'
|
||||||
import { renderString } from '@/components/parse.js'
|
import { renderString } from '@/helpers/parse.js'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const modal = ref('modal')
|
const modal = ref('modal')
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ import {
|
|||||||
Categories,
|
Categories,
|
||||||
EnvironmentIndicator,
|
EnvironmentIndicator,
|
||||||
} from '@/components'
|
} from '@/components'
|
||||||
import { formatNumber } from '@/components/utils'
|
import { formatNumber } from '@/helpers/utils.js'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|||||||
@@ -1,51 +1,166 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="omorphia__navrow">
|
<nav class="navigation">
|
||||||
<slot />
|
<router-link
|
||||||
<div class="right-slot">
|
v-for="(link, index) in filteredLinks"
|
||||||
<slot name="right" />
|
v-show="link.shown === undefined ? true : link.shown"
|
||||||
</div>
|
:key="index"
|
||||||
</div>
|
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>
|
</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>
|
<style lang="scss" scoped>
|
||||||
.omorphia__navrow {
|
.navigation {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
grid-gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.right-slot {
|
.nav-link {
|
||||||
margin-left: auto;
|
text-transform: capitalize;
|
||||||
}
|
font-weight: var(--font-weight-bold);
|
||||||
|
color: var(--color-text);
|
||||||
:deep(.btn) {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::after {
|
&:hover {
|
||||||
content: '';
|
color: var(--color-text);
|
||||||
position: absolute;
|
|
||||||
width: calc(100% - var(--gap-lg) * 2);
|
&::after {
|
||||||
height: 4px;
|
opacity: 0.4;
|
||||||
bottom: 4px;
|
}
|
||||||
border-radius: var(--radius-max);
|
|
||||||
background-color: var(--color-brand);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover::after {
|
&:active::after {
|
||||||
opacity: 50%;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.router-link-exact-active {
|
||||||
color: var(--color-contrast);
|
color: var(--color-text);
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
opacity: 1;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatCategory } from '@/components/utils'
|
import { formatCategory } from '@/helpers/utils.js'
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
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})?$/,
|
/^https?:\/\/(www\.)?youtube(-nocookie)?\.com\/embed\/[a-zA-Z0-9_-]{11}(\?&autoplay=[0-1]{1})?$/,
|
||||||
remove: ['&autoplay=1'], // Prevents autoplay
|
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) {
|
for (const source of allowedSources) {
|
||||||
@@ -82,9 +86,7 @@ export const md = (options = {}) => {
|
|||||||
if (allowedHostnames.includes(url.hostname)) {
|
if (allowedHostnames.includes(url.hostname)) {
|
||||||
return defaultLinkOpenRenderer(tokens, idx, options, env, self)
|
return defaultLinkOpenRenderer(tokens, idx, options, env, self)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens[idx].attrSet('rel', 'noopener nofollow ugc')
|
tokens[idx].attrSet('rel', 'noopener nofollow ugc')
|
||||||
@@ -109,21 +111,22 @@ export const md = (options = {}) => {
|
|||||||
const url = new URL(src)
|
const url = new URL(src)
|
||||||
|
|
||||||
const allowedHostnames = [
|
const allowedHostnames = [
|
||||||
|
'imgur.com',
|
||||||
'i.imgur.com',
|
'i.imgur.com',
|
||||||
'cdn-raw.modrinth.com',
|
'cdn-raw.modrinth.com',
|
||||||
'cdn.modrinth.com',
|
'cdn.modrinth.com',
|
||||||
'staging-cdn-raw.modrinth.com',
|
'staging-cdn-raw.modrinth.com',
|
||||||
'staging-cdn.modrinth.com',
|
'staging-cdn.modrinth.com',
|
||||||
|
'github.com',
|
||||||
'raw.githubusercontent.com',
|
'raw.githubusercontent.com',
|
||||||
'img.shields.io',
|
'img.shields.io',
|
||||||
|
'i.postimg.cc',
|
||||||
]
|
]
|
||||||
|
|
||||||
if (allowedHostnames.includes(url.hostname)) {
|
if (allowedHostnames.includes(url.hostname)) {
|
||||||
return defaultImageRenderer(tokens, idx, options, env, self)
|
return defaultImageRenderer(tokens, idx, options, env, self)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
/* empty */
|
|
||||||
}
|
|
||||||
token.attrs[index][1] = `//wsrv.nl/?url=${encodeURIComponent(src)}`
|
token.attrs[index][1] = `//wsrv.nl/?url=${encodeURIComponent(src)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,5 +10,6 @@ function install(app) {
|
|||||||
|
|
||||||
export default { install }
|
export default { install }
|
||||||
export * from './components'
|
export * from './components'
|
||||||
|
export * from './helpers'
|
||||||
|
|
||||||
import './assets/omorphia.scss'
|
import './assets/omorphia.scss'
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"floating-vue": "^2.0.0-beta.20",
|
"floating-vue": "^2.0.0-beta.20",
|
||||||
|
"highlight.js": "^11.7.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
|
|||||||
@@ -1064,6 +1064,11 @@ has@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
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:
|
ignore@^5.2.0:
|
||||||
version "5.2.4"
|
version "5.2.4"
|
||||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
|
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user