Optimize shiki and add skipped and left data
This commit is contained in:
		| @@ -1,7 +1,14 @@ | ||||
| <script setup lang="ts"></script> | ||||
|  | ||||
| <template> | ||||
|   <RouterView /> | ||||
|   <Suspense> | ||||
|     <template #default> | ||||
|       <RouterView /> | ||||
|     </template> | ||||
|     <template #fallback> | ||||
|       <div>Loading</div> | ||||
|     </template> | ||||
|   </Suspense> | ||||
| </template> | ||||
|  | ||||
| <style scoped></style> | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|   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] | ||||
|   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> | ||||
|     <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> | ||||
|     <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> | ||||
| </template> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user