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> <script setup lang="ts"></script>
<template> <template>
<RouterView /> <Suspense>
<template #default>
<RouterView />
</template>
<template #fallback>
<div>Loading</div>
</template>
</Suspense>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -68,7 +68,7 @@ header {
main { main {
margin-top: 60px; margin-top: 60px;
padding: 30px 30px 0 30px; padding: 30px;
} }
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {
@ -105,3 +105,16 @@ header .logo-text h1 {
background-color: #d1d; background-color: #d1d;
color: white !important; 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 { ref, reactive, computed, useTemplateRef, watch, onUpdated } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { computedAsync } from '@vueuse/core' 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' import Header from '../components/Header.vue'
@ -24,33 +27,6 @@ const codeDiv = useTemplateRef('codeDiv')
const size = 32 * 1024 const size = 32 * 1024
const maxViewSize = size * 16 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() { function scrollToSelectedSearch() {
if (!searched.value) return if (!searched.value) return
// fragile constants // fragile constants
@ -78,6 +54,7 @@ async function getSection(start: number) {
} }
function getNextSection() { function getNextSection() {
query.value = ""
resetSearch() resetSearch()
getSection(end.value).then((data) => { getSection(end.value).then((data) => {
content.value = content.value + data.content content.value = content.value + data.content
@ -92,6 +69,7 @@ function getNextSection() {
} }
function getPreviousSection() { function getPreviousSection() {
query.value = ""
resetSearch() resetSearch()
if (start.value) { if (start.value) {
getSection(start.value - size).then((data) => { getSection(start.value - size).then((data) => {
@ -144,7 +122,9 @@ function resetSearch() {
// +1 for next, -1 for previous // +1 for next, -1 for previous
function moveSearchResult(n) { function moveSearchResult(n) {
searchResults.current += n if (searchResults.current + n < searchResults.matches.length && searchResults.current + n >= 0)
searchResults.current += n
else return
const matchPos = searchResults.matches[searchResults.current] const matchPos = searchResults.matches[searchResults.current]
getNthByte(matchPos) getNthByte(matchPos)
} }
@ -158,6 +138,53 @@ function findError() {
} }
getNextSection() 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></script> <script></script>
@ -175,7 +202,7 @@ getNextSection()
Previous Previous
</button> </button>
<button <button
:disabled="searchResults.current >= searchResults.matches.length" :disabled="searchResults.current >= searchResults.matches.length - 1"
@click="moveSearchResult(1)" @click="moveSearchResult(1)"
type="button" type="button"
> >
@ -189,8 +216,14 @@ getNextSection()
</form> </form>
</Header> </Header>
<main> <main>
<button @click="getPreviousSection" :disabled="!start">Load Previous</button> <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 ref="codeDiv" v-html="displayedContent"></div>
<button @click="getNextSection" :disabled="end >= totalSize">Load More</button> <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> </main>
</template> </template>