Optimize shiki and add skipped and left data

This commit is contained in:
Ceda EI 2025-05-02 22:50:48 +05:30
parent 2c50459501
commit f6123f53e7
3 changed files with 87 additions and 34 deletions

View File

@ -1,7 +1,14 @@
<script setup lang="ts"></script>
<template>
<Suspense>
<template #default>
<RouterView />
</template>
<template #fallback>
<div>Loading</div>
</template>
</Suspense>
</template>
<style scoped></style>

View File

@ -68,7 +68,7 @@ header {
main {
margin-top: 60px;
padding: 30px 30px 0 30px;
padding: 30px;
}
@media only screen and (max-width: 800px) {
@ -105,3 +105,16 @@ header .logo-text h1 {
background-color: #d1d;
color: white !important;
}
.code-action {
display: flex;
justify-content: space-between;
align-items: center;
}
.data {
padding: 10px;
color: var(--primary);
border: none;
border-radius: 10px;
}

View File

@ -2,7 +2,10 @@
import { ref, reactive, computed, useTemplateRef, watch, onUpdated } from 'vue'
import { useRoute } from 'vue-router'
import { computedAsync } from '@vueuse/core'
import { codeToHtml } from 'shiki'
import { createHighlighterCore } from 'shiki/core'
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
import catppuccinLatte from '@shikijs/themes/catppuccin-latte'
import bash from '@shikijs/langs/bash'
import Header from '../components/Header.vue'
@ -24,33 +27,6 @@ const codeDiv = useTemplateRef('codeDiv')
const size = 32 * 1024
const maxViewSize = size * 16
// Add ellipsis to start or end if there is more content available
const displayedContent = computedAsync(async () => {
const prefix = start.value ? '...\n' : ''
const suffix = end.value < totalSize.value ? '\n...' : ''
const decorations = []
if (searched) {
const currentMatch = searchResults.matches[searchResults.current]
searchResults.matches.forEach((match) => {
if (match < end.value && match > start.value) {
decorations.push({
start: match - start.value + prefix.length,
end: match - start.value + query.value.length + prefix.length,
properties: {
class: currentMatch === match ? 'current-highlighted-word' : 'highlighted-word',
},
})
}
})
}
return await codeToHtml(prefix + content.value + suffix, {
lang: 'bash',
theme: 'catppuccin-latte',
decorations,
})
}, '')
function scrollToSelectedSearch() {
if (!searched.value) return
// fragile constants
@ -78,6 +54,7 @@ async function getSection(start: number) {
}
function getNextSection() {
query.value = ""
resetSearch()
getSection(end.value).then((data) => {
content.value = content.value + data.content
@ -92,6 +69,7 @@ function getNextSection() {
}
function getPreviousSection() {
query.value = ""
resetSearch()
if (start.value) {
getSection(start.value - size).then((data) => {
@ -144,7 +122,9 @@ function resetSearch() {
// +1 for next, -1 for previous
function moveSearchResult(n) {
if (searchResults.current + n < searchResults.matches.length && searchResults.current + n >= 0)
searchResults.current += n
else return
const matchPos = searchResults.matches[searchResults.current]
getNthByte(matchPos)
}
@ -158,6 +138,53 @@ function findError() {
}
getNextSection()
function convertBytes(bytes) {
const suffixes = ["bytes", "KiB", "MiB", "GiB"];
for (let suffix of suffixes) {
if (bytes < 1024)
return `${bytes.toFixed(2).replace(/\.?0*$/,'')} ${suffix}`
bytes /= 1024;
}
return `${bytes.toFixed(2).replace(/\.?0*$/,'')} TiB`
}
const dataSkipped = computed(() => convertBytes(start.value))
const dataLeft = computed(() => convertBytes(totalSize.value - end.value))
const highlighter = await createHighlighterCore({
langs: [bash],
themes: [catppuccinLatte],
engine: createJavaScriptRegexEngine(),
});
// Add ellipsis to start or end if there is more content available
const displayedContent = computed(() => {
const prefix = start.value ? '...\n' : ''
const suffix = end.value < totalSize.value ? '\n...' : ''
const decorations = []
if (searched) {
const currentMatch = searchResults.matches[searchResults.current]
searchResults.matches.forEach((match) => {
if (match < end.value && match > start.value) {
decorations.push({
start: match - start.value + prefix.length,
end: match - start.value + query.value.length + prefix.length,
properties: {
class: currentMatch === match ? 'current-highlighted-word' : 'highlighted-word',
},
})
}
})
}
return highlighter.codeToHtml(prefix + content.value + suffix, {
lang: 'bash',
theme: 'catppuccin-latte',
decorations,
})
}, '')
</script>
<script></script>
@ -175,7 +202,7 @@ getNextSection()
Previous
</button>
<button
:disabled="searchResults.current >= searchResults.matches.length"
:disabled="searchResults.current >= searchResults.matches.length - 1"
@click="moveSearchResult(1)"
type="button"
>
@ -189,8 +216,14 @@ getNextSection()
</form>
</Header>
<main>
<div class="code-action">
<button @click="getPreviousSection" :disabled="!start">Load Previous</button>
<span v-if="start" class="data">{{ dataSkipped }} skipped</span>
</div>
<div ref="codeDiv" v-html="displayedContent"></div>
<div class="code-action">
<button @click="getNextSection" :disabled="end >= totalSize">Load More</button>
<span v-if="end < totalSize" class="data">{{ dataLeft }} left</span>
</div>
</main>
</template>