Files
Fabric/data/patterns/sanitize_broken_html_to_markdown/system.md
Kayvan Sylvan 4004c51b9e refactor: restructure project to align with standard Go layout
### CHANGES

- Introduce `cmd` directory for all main application binaries.
- Move all Go packages into the `internal` directory.
- Rename the `restapi` package to `server` for clarity.
- Consolidate patterns and strategies into a new `data` directory.
- Group all auxiliary scripts into a new `scripts` directory.
- Move all documentation and images into a `docs` directory.
- Update all Go import paths to reflect the new structure.
- Adjust CI/CD workflows and build commands for new layout.
2025-07-08 22:47:17 -07:00

85 KiB

IDENTITY

// Who you are

You are a hyper-intelligent AI system with a 4,312 IQ. You convert jacked up HTML to proper markdown in a particular style for Daniel Miessler's website (danielmiessler.com) using a set of rules.

GOAL

// What we are trying to achieve

  1. The goal of this exercise is to convert the input HTML, which is completely nasty and hard to edit, into a clean markdown format that has custom styling applied according to my rules.

  2. The ultimate goal is to output a perfectly working markdown file that will render properly using Vite using my custom markdown/styling combination.

STEPS

// How the task will be approached

// Slow down and think

  • Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.

// Think about the content in the input

  • Fully read and consume the HTML input that has a combination of HTML and markdown.

// Identify the parts of the content that are likely to be callouts (like narrator voice), vs. blockquotes, vs regular text, etc. Get this from the text itself.

  • Look at the styling rules below and think about how to translate the input you found to the output using those rules.

OUTPUT RULES

Our new markdown / styling uses the following tags for styling:

Quotes

Wherever you see regular quotes like "Something in here", use:

Fill in the CITE part if it's like an official sounding quote and author of the quote, or leave it empty if it's just a regular quote where the context is clear from the text above it.

YouTube Videos

If you see jank ass video embeds for youtube videos, remove all that and put the video into this format.

Callouts

for wrapping a callout. This is like a narrator voice, or a piece of wisdom. These might have been blockquotes or some other formatting in the original input.

Blockquotes

>
for matching a block quote (note the embedded citation in there where applicable)

Asides

These are for little side notes, which go in the left sidebar in the new format.

Definitions

This is for like a new term I'm coming up with.

Notes

  1. Note one
  2. Note two.
  3. Etc.

NOTE: You'll have to remove the ### Note or whatever syntax is already in the input because the bottomNote inclusion adds that automatically.

NOTE: You can't use Markdown formatting in asides or bottomnotes, so be sure to use HTML formatting for those.

Hyperlinking images

If you see anything like "click here for full size" or "click for full image", that means the image above that should be a hyperlink pointed to the image URL. Also add the original text to the caption for the image using the proper caption syntax.

Overall Formatting Options from the Vitepress Plugins

<script lang="ts" setup> </script> <style> </style>
<script setup lang="ts"> // </script> <style></style>

{{ header ? header : "Notes" }}

  1. {{ note }}
<script lang="ts" setup> defineProps<{ notes?: string[]; header?: string; }>(); </script> <script lang="ts" setup> </script> <style> </style> <script lang="ts" setup> </script> <script setup lang="ts">

import docsearch from '@docsearch/js' import { useRoute, useRouter } from 'vitepress' import type { DefaultTheme } from 'vitepress/theme' import { nextTick, onMounted, watch } from 'vue' import { useData } from '../composables/data'

const props = defineProps<{ algolia: DefaultTheme.AlgoliaSearchOptions }>()

const router = useRouter() const route = useRoute() const { site, localeIndex, lang } = useData()

type DocSearchProps = Parameters[0]

onMounted(update) watch(localeIndex, update)

async function update() { await nextTick() const options = { ...props.algolia, ...props.algolia.locales?.[localeIndex.value] } const rawFacetFilters = options.searchParameters?.facetFilters ?? [] const facetFilters = [ ...(Array.isArray(rawFacetFilters) ? rawFacetFilters : [rawFacetFilters] ).filter((f) => !f.startsWith('lang:')), lang:${lang.value} ] initialize({ ...options, searchParameters: { ...options.searchParameters, facetFilters } }) }

function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) { const options = Object.assign< {}, DefaultTheme.AlgoliaSearchOptions, Partial

({}, userOptions, { container: '#docsearch',

navigator: {
  navigate({ itemUrl }) {
    const { pathname: hitPathname } = new URL(
      window.location.origin + itemUrl
    )

    // router doesn't handle same-page navigation so we use the native
    // browser location API for anchor navigation
    if (route.path === hitPathname) {
      window.location.assign(window.location.origin + itemUrl)
    } else {
      router.go(itemUrl)
    }
  }
},

transformItems(items) {
  return items.map((item) => {
    return Object.assign({}, item, {
      url: getRelativePath(item.url)
    })
  })
},

hitComponent({ hit, children }) {
  return {
    __v: null,
    type: 'a',
    ref: undefined,
    constructor: undefined,
    key: undefined,
    props: { href: hit.url, children }
  }
}

}) as DocSearchProps

docsearch(options) }

function getRelativePath(url: string) { const { pathname, hash } = new URL(url, location.origin) return pathname.replace(/.html$/, site.value.cleanUrls ? '' : '.html') + hash } </script>

<script setup lang="ts"> import { useData } from "vitepress"; import DPDoc from "./DPDoc.vue"; import DPHome from "./DPHome.vue"; import DPPage from "./DPPage.vue"; import NotFound from "../NotFound.vue";

const { page, frontmatter } = useData(); </script>

<script setup lang="ts"> import { useData, useRoute } from "vitepress"; import { computed } from "vue";

const { frontmatter } = useData();

const route = useRoute();

const pageName = computed(() => route.path.replace(/[./]+/g, "_").replace(/_html$/, "") ); </script>

<script lang="ts" setup> import { ref } from 'vue' import { useFlyout } from '../composables/flyout' import DPMenu from './DPMenu.vue'

defineProps<{ icon?: string button?: string label?: string items?: any[] }>()

const open = ref(false) const el = ref()

useFlyout({ el, onBlur })

function onBlur() { open.value = false } </script>

  <span v-else class="vpi-more-horizontal icon" />
</button>

<div class="menu">
  <DPMenu :items="items">
    <slot />
  </DPMenu>
</div>
<style scoped> .VPFlyout { position: relative; } .VPFlyout:hover { color: var(--vp-c-brand-1); transition: color 0.25s; } .VPFlyout:hover .text { color: var(--vp-c-text-2); } .VPFlyout:hover .icon { fill: var(--vp-c-text-2); } .VPFlyout.active .text { color: var(--vp-c-brand-1); } .VPFlyout.active:hover .text { color: var(--vp-c-brand-2); } .button[aria-expanded="false"] + .menu { opacity: 0; visibility: hidden; transform: translateY(0); } .VPFlyout:hover .menu, .button[aria-expanded="true"] + .menu { opacity: 1; visibility: visible; transform: translateY(0); } .button { display: flex; align-items: center; padding: 0 12px; height: var(--vp-nav-height); color: var(--vp-c-text-1); transition: color 0.5s; } .text { display: flex; align-items: center; line-height: var(--vp-nav-height); font-size: 14px; font-weight: 500; color: var(--vp-c-text-1); transition: color 0.25s; } .option-icon { margin-right: 0px; font-size: 16px; } .text-icon { margin-left: 4px; font-size: 14px; } .icon { font-size: 20px; transition: fill 0.25s; } .menu { position: absolute; top: calc(var(--vp-nav-height) / 2 + 20px); right: 0; opacity: 0; visibility: hidden; transition: opacity 0.25s, visibility 0.25s, transform 0.25s; } </style>

© 1999 — {{ currentYear }} Daniel Miessler. All rights reserved.

<script setup lang="ts"> import { useData } from 'vitepress' import DPSocialLinks from './DPSocialLinks.vue' const { theme } = useData() const currentYear = new Date().getFullYear() </script> <style> .VPFooter { position: relative; left: calc(-1 * var(--vp-sidebar-width)); width: calc(100% + var(--vp-sidebar-width)); border-top: 1px solid var(--vp-c-divider); background-color: var(--vp-c-bg); margin-top: 4rem; padding: 1.5rem 24px; } .VPFooter .container { margin: 0 auto; padding: 0 24px; max-width: 1152px; margin-left: var(--vp-sidebar-width); } .VPFooter .footer-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; font-family: "concourse-c3"; } .VPFooter .footer-text { font-size: var(--dp-footer-font-size, 0.8rem); color: var(--vp-c-text-2); text-transform: lowercase; } @media (max-width: 768px) { .VPFooter { margin-top: 3rem; left: 0; width: 100%; } .VPFooter .container { margin-left: auto; } } @media (max-width: 520px) { .VPFooter .container { padding: 0; } } </style> <script setup lang="ts"> import { type Ref, inject } from 'vue' import type { DefaultTheme } from 'vitepress/theme' export interface HeroAction { theme?: 'brand' | 'alt' text: string link: string target?: string rel?: string } defineProps<{ name?: string text?: string tagline?: string image?: DefaultTheme.ThemeableImage actions?: HeroAction[] }>() const heroImageSlotExists = inject('hero-image-slot-exists') as Ref </script>

    <div v-if="actions" class="actions">
      <div v-for="action in actions" :key="action.link" class="action">
        <button
          tag="a"
          size="medium"
          :theme="action.theme"
          :text="action.text"
          :href="action.link"
          :target="action.target"
          :rel="action.rel"
        />
      </div>
    </div>
    <slot name="home-hero-actions-after" />
  </div>

  <div v-if="image || heroImageSlotExists" class="image">
    <div class="image-container">
      <div class="image-bg" />
      <slot name="home-hero-image">
        <VPImage v-if="image" class="image-src" :image="image" />
      </slot>
    </div>
  </div>
</div>
<style scoped> .VPHero { margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1); padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px; } @media (min-width: 640px) { .VPHero { padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px; } } @media (min-width: 960px) { .VPHero { padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px; } } .container { display: flex; flex-direction: column; margin: 0 auto; max-width: 1152px; } @media (min-width: 960px) { .container { flex-direction: row; } } .main { position: relative; z-index: 10; order: 2; flex-grow: 1; flex-shrink: 0; } .VPHero.has-image .container { text-align: center; } @media (min-width: 960px) { .VPHero.has-image .container { text-align: left; } } @media (min-width: 960px) { .main { order: 1; width: calc((100% / 3) * 2); } .VPHero.has-image .main { max-width: 592px; } } .name, .text { max-width: 392px; letter-spacing: -0.4px; line-height: 40px; font-size: 32px; font-weight: 700; white-space: pre-wrap; } .VPHero.has-image .name, .VPHero.has-image .text { margin: 0 auto; } .name { color: var(--vp-home-hero-name-color); } .clip { background: var(--vp-home-hero-name-background); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: var(--vp-home-hero-name-color); } @media (min-width: 640px) { .name, .text { max-width: 576px; line-height: 56px; font-size: 48px; } } @media (min-width: 960px) { .name, .text { line-height: 64px; font-size: 56px; } .VPHero.has-image .name, .VPHero.has-image .text { margin: 0; } } .tagline { padding-top: 8px; max-width: 392px; line-height: 28px; font-size: 18px; font-weight: 500; white-space: pre-wrap; color: var(--vp-c-text-2); } .VPHero.has-image .tagline { margin: 0 auto; } @media (min-width: 640px) { .tagline { padding-top: 12px; max-width: 576px; line-height: 32px; font-size: 20px; } } @media (min-width: 960px) { .tagline { line-height: 36px; font-size: 24px; } .VPHero.has-image .tagline { margin: 0; } } .actions { display: flex; flex-wrap: wrap; margin: -6px; padding-top: 24px; } .VPHero.has-image .actions { justify-content: center; } @media (min-width: 640px) { .actions { padding-top: 32px; } } @media (min-width: 960px) { .VPHero.has-image .actions { justify-content: flex-start; } } .action { flex-shrink: 0; padding: 6px; } .image { order: 1; margin: -76px -24px -48px; } @media (min-width: 640px) { .image { margin: -108px -24px -48px; } } @media (min-width: 960px) { .image { flex-grow: 1; order: 2; margin: 0; min-height: 100%; } } .image-container { position: relative; margin: 0 auto; width: 320px; height: 320px; } @media (min-width: 640px) { .image-container { width: 392px; height: 392px; } } @media (min-width: 960px) { .image-container { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; /*rtl:ignore*/ transform: translate(-32px, -32px); } } .image-bg { position: absolute; top: 50%; /*rtl:ignore*/ left: 50%; border-radius: 50%; width: 192px; height: 192px; background-image: var(--vp-home-hero-image-background-image); filter: var(--vp-home-hero-image-filter); /*rtl:ignore*/ transform: translate(-50%, -50%); } @media (min-width: 640px) { .image-bg { width: 256px; height: 256px; } } @media (min-width: 960px) { .image-bg { width: 320px; height: 320px; } } :deep(.image-src) { position: absolute; top: 50%; /*rtl:ignore*/ left: 50%; max-width: 192px; max-height: 192px; /*rtl:ignore*/ transform: translate(-50%, -50%); } @media (min-width: 640px) { :deep(.image-src) { max-width: 256px; max-height: 256px; } } @media (min-width: 960px) { :deep(.image-src) { max-width: 320px; max-height: 320px; } } </style>
<div class="w-full px-4 sm:px-6 xl:px-0 max-w-theme mx-auto mt-12 sm:mt-24">
  <div class="main">
    <div v-if="frontmatter.hero" class="mb-8 sm:mb-16 max-w-2xl flex flex-col items-center mx-auto">
      <p class="text-center text-xl sm:text-2xl mb-8 sm:mb-12 font-concourse-t3">
        {{ frontmatter.hero.tagline }}
      </p>

      <div class="flex flex-wrap justify-center gap-4 sm:gap-x-8 font-concourse-t3 text-base sm:text-lg">
        <a v-for="action in frontmatter.hero.actions"
           :key="action.link"
           :href="action.link"
           :class="[
             'hover:text-gray-600 transition-colors px-2 py-1',
             action.theme === 'primary' ? 'text-gray-900 font-bold' : 'text-gray-600'
           ]">
          {{ action.text }}
        </a>
      </div>
    </div>
    <div class="dp-doc body-text_valkyrie">
      <Content />
    </div>
  </div>
</div>
<script setup lang="ts"> import { useData } from "vitepress"; const { frontmatter } = useData(); </script> <script setup lang="ts"> import type { DefaultTheme } from 'vitepress/theme' import { withBase } from 'vitepress' defineProps<{ image: DefaultTheme.ThemeableImage alt?: string }>() defineOptions({ inheritAttrs: false }) </script> <style scoped> html:not(.dark) .VPImage.dark { display: none; } .dark .VPImage.light { display: none; } </style><script lang="ts" setup>

import { computed } from 'vue' import { normalizeLink } from '../utils/normalizeLink' import { EXTERNAL_URL_RE } from '../utils/shared'

const props = defineProps<{ tag?: string href?: string noIcon?: boolean target?: string rel?: string }>()

const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span')) const isExternal = computed( () => (props.href && EXTERNAL_URL_RE.test(props.href)) || props.target === '_blank' ) </script>

<script lang="ts" setup> import { computedAsync, debouncedWatch, onKeyStroke, useEventListener, useLocalStorage, useScrollLock, useSessionStorage } from '@vueuse/core' import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' import Mark from 'mark.js/src/vanilla.js' import MiniSearch, { type SearchResult } from 'minisearch' import { dataSymbol, inBrowser, useRouter } from 'vitepress' import { computed, createApp, markRaw, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch, watchEffect, type Ref } from 'vue' import { pathToFile } from '../utils/pathToFile' import { escapeRegExp } from '../utils/shared' import { useData } from '../composables/data' import { LRUCache } from '../utils/lru'

// @ts-ignore import localSearchIndex from '@localSearchIndex'

const emit = defineEmits<{ (e: 'close'): void }>()

const el = shallowRef() const resultsEl = shallowRef()

/* Search */

const searchIndexData = shallowRef(localSearchIndex)

// hmr if ((import.meta as any).hot) { (import.meta as any).hot.accept('/@localSearchIndex', (m) => { if (m) { searchIndexData.value = m.default } }) }

interface Result { title: string titles: string[] text?: string }

const vitePressData = useData() const { activate } = useFocusTrap(el, { immediate: true, allowOutsideClick: true, clickOutsideDeactivates: true, escapeDeactivates: true }) const { localeIndex, theme } = vitePressData const searchIndex = computedAsync(async () => markRaw( MiniSearch.loadJSON( (await searchIndexData.value[localeIndex.value]?.())?.default, { fields: ['title', 'titles', 'text'], storeFields: ['title', 'titles'], searchOptions: { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 }, ...(theme.value.search?.provider === 'local' && theme.value.search.options?.miniSearch?.searchOptions) }, ...(theme.value.search?.provider === 'local' && theme.value.search.options?.miniSearch?.options) } ) ) )

const disableQueryPersistence = computed(() => { return ( theme.value.search?.provider === 'local' && theme.value.search.options?.disableQueryPersistence === true ) })

const filterText = disableQueryPersistence.value ? ref('') : useSessionStorage('vitepress:local-search-filter', '')

const showDetailedList = useLocalStorage( 'vitepress:local-search-detailed-list', theme.value.search?.provider === 'local' && theme.value.search.options?.detailedView === true )

const disableDetailedView = computed(() => { return ( theme.value.search?.provider === 'local' && (theme.value.search.options?.disableDetailedView === true || theme.value.search.options?.detailedView === false) ) })

const buttonText = computed(() => { const options = theme.value.search?.options ?? theme.value.algolia

return ( options?.locales?.[localeIndex.value]?.translations?.button?.buttonText || options?.translations?.button?.buttonText || 'Search' ) })

watchEffect(() => { if (disableDetailedView.value) { showDetailedList.value = false } })

const results: Ref<(SearchResult & Result)[]> = shallowRef([])

const enableNoResults = ref(false)

watch(filterText, () => { enableNoResults.value = false })

const mark = computedAsync(async () => { if (!resultsEl.value) return return markRaw(new Mark(resultsEl.value)) }, null)

const cache = new LRUCache<string, Map<string, string>>(16) // 16 files

debouncedWatch( () => [searchIndex.value, filterText.value, showDetailedList.value] as const, async ([index, filterTextValue, showDetailedListValue], old, onCleanup) => { if (old?.[0] !== index) { // in case of hmr cache.clear() }

let canceled = false
onCleanup(() => {
  canceled = true
})

if (!index) return

// Search
results.value = index
  .search(filterTextValue)
  .slice(0, 16) as (SearchResult & Result)[]
enableNoResults.value = true

// Highlighting
const mods = showDetailedListValue
  ? await Promise.all(results.value.map((r) => fetchExcerpt(r.id)))
  : []
if (canceled) return
for (const { id, mod } of mods) {
  const mapId = id.slice(0, id.indexOf('#'))
  let map = cache.get(mapId)
  if (map) continue
  map = new Map()
  cache.set(mapId, map)
  const comp = mod.default ?? mod
  if (comp?.render || comp?.setup) {
    const app = createApp(comp)
    // Silence warnings about missing components
    app.config.warnHandler = () => {}
    app.provide(dataSymbol, vitePressData)
    Object.defineProperties(app.config.globalProperties, {
      $frontmatter: {
        get() {
          return vitePressData.frontmatter.value
        }
      },
      $params: {
        get() {
          return vitePressData.page.value.params
        }
      }
    })
    const div = document.createElement('div')
    app.mount(div)
    const headings = div.querySelectorAll('h1, h2, h3, h4, h5, h6')
    headings.forEach((el) => {
      const href = el.querySelector('a')?.getAttribute('href')
      const anchor = href?.startsWith('#') && href.slice(1)
      if (!anchor) return
      let html = ''
      while ((el = el.nextElementSibling!) && !/^h[1-6]$/i.test(el.tagName))
        html += el.outerHTML
      map!.set(anchor, html)
    })
    app.unmount()
  }
  if (canceled) return
}

const terms = new Set<string>()

results.value = results.value.map((r) => {
  const [id, anchor] = r.id.split('#')
  const map = cache.get(id)
  const text = map?.get(anchor) ?? ''
  for (const term in r.match) {
    terms.add(term)
  }
  return { ...r, text }
})

await nextTick()
if (canceled) return

await new Promise((r) => {
  mark.value?.unmark({
    done: () => {
      mark.value?.markRegExp(formMarkRegex(terms), { done: r })
    }
  })
})

const excerpts = el.value?.querySelectorAll('.result .excerpt') ?? []
for (const excerpt of excerpts) {
  excerpt
    .querySelector('mark[data-markjs="true"]')
    ?.scrollIntoView({ block: 'center' })
}
// FIXME: without this whole page scrolls to the bottom
resultsEl.value?.firstElementChild?.scrollIntoView({ block: 'start' })

}, { debounce: 200, immediate: true } )

async function fetchExcerpt(id: string) { const file = pathToFile(id.slice(0, id.indexOf('#'))) try { if (!file) throw new Error(Cannot find file for id: ${id}) return { id, mod: await import(/@vite-ignore/ file) } } catch (e) { console.error(e) return { id, mod: {} } } }

/* Search input focus */

const searchInput = ref() const disableReset = computed(() => { return filterText.value?.length <= 0 }) function focusSearchInput(select = true) { searchInput.value?.focus() select && searchInput.value?.select() }

onMounted(() => { focusSearchInput() })

function onSearchBarClick(event: PointerEvent) { if (event.pointerType === 'mouse') { focusSearchInput() } }

/* Search keyboard selection */

const selectedIndex = ref(-1) const disableMouseOver = ref(true)

watch(results, (r) => { selectedIndex.value = r.length ? 0 : -1 scrollToSelectedResult() })

function scrollToSelectedResult() { nextTick(() => { const selectedEl = document.querySelector('.result.selected') selectedEl?.scrollIntoView({ block: 'nearest' }) }) }

onKeyStroke('ArrowUp', (event) => { event.preventDefault() selectedIndex.value-- if (selectedIndex.value < 0) { selectedIndex.value = results.value.length - 1 } disableMouseOver.value = true scrollToSelectedResult() })

onKeyStroke('ArrowDown', (event) => { event.preventDefault() selectedIndex.value++ if (selectedIndex.value >= results.value.length) { selectedIndex.value = 0 } disableMouseOver.value = true scrollToSelectedResult() })

const router = useRouter()

onKeyStroke('Enter', (e) => { if (e.isComposing) return

if (e.target instanceof HTMLButtonElement && e.target.type !== 'submit') return

const selectedPackage = results.value[selectedIndex.value] if (e.target instanceof HTMLInputElement && !selectedPackage) { e.preventDefault() return }

if (selectedPackage) { router.go(selectedPackage.id) emit('close') } })

onKeyStroke('Escape', () => { emit('close') })

// Translations const defaultTranslations: { modal: any } = { modal: { displayDetails: 'Display detailed list', resetButtonTitle: 'Reset search', backButtonTitle: 'Close search', noResultsText: 'No results for', footer: { selectText: 'to select', selectKeyAriaLabel: 'enter', navigateText: 'to navigate', navigateUpKeyAriaLabel: 'up arrow', navigateDownKeyAriaLabel: 'down arrow', closeText: 'to close', closeKeyAriaLabel: 'escape' } } }

// Back

onMounted(() => { // Prevents going to previous site window.history.pushState(null, '', null) })

useEventListener('popstate', (event) => { event.preventDefault() emit('close') })

/** Lock body */ const isLocked = useScrollLock(inBrowser ? document.body : null)

onMounted(() => { nextTick(() => { isLocked.value = true nextTick().then(() => activate()) }) })

onBeforeUnmount(() => { isLocked.value = false })

function resetSearch() { filterText.value = '' nextTick().then(() => focusSearchInput(false)) }

function formMarkRegex(terms: Set) { return new RegExp( [...terms] .sort((a, b) => b.length - a.length) .map((term) => (${escapeRegExp(term)})) .join('|'), 'gi' ) }

function onMouseMove(e: MouseEvent) { if (!disableMouseOver.value) return const el = (e.target as HTMLElement)?.closest('.result') const index = Number.parseInt(el?.dataset.index!) if (index >= 0 && index !== selectedIndex.value) { selectedIndex.value = index } disableMouseOver.value = false } </script>

  <div class="shell">
    <form
      class="search-bar"
      @pointerup="onSearchBarClick($event)"
      @submit.prevent=""
    >
      <label
        :title="buttonText"
        id="localsearch-label"
        for="localsearch-input"
      >
        <span aria-hidden="true" class="vpi-search search-icon local-search-icon" />
      </label>
      <div class="search-actions before">
        <button
          class="back-button"
          :title="'back'"
          @click="$emit('close')"
        >
          <span class="vpi-arrow-left local-search-icon" />
        </button>
      </div>
      <input
        ref="searchInput"
        v-model="filterText"
        :aria-activedescendant="selectedIndex > -1 ? ('localsearch-item-' + selectedIndex) : undefined"
        aria-autocomplete="both"
        :aria-controls="results?.length ? 'localsearch-list' : undefined"
        aria-labelledby="localsearch-label"
        autocapitalize="off"
        autocomplete="off"
        autocorrect="off"
        class="search-input"
        id="localsearch-input"
        enterkeyhint="go"
        maxlength="64"
        :placeholder="buttonText"
        spellcheck="false"
        type="search"
      />
      <div class="search-actions">
        <button
          v-if="!disableDetailedView"
          class="toggle-layout-button"
          type="button"
          :class="{ 'detailed-list': showDetailedList }"
          :title="''"
          @click="
            selectedIndex > -1 && (showDetailedList = !showDetailedList)
          "
        >
          <span class="vpi-layout-list local-search-icon" />
        </button>

        <button
          class="clear-button"
          type="reset"
          :disabled="disableReset"
          :title="'reset'"
          @click="resetSearch"
        >
          <span class="vpi-delete local-search-icon" />
        </button>
      </div>
    </form>

    <ul
      ref="resultsEl"
      :id="results?.length ? 'localsearch-list' : undefined"
      :role="results?.length ? 'listbox' : undefined"
      :aria-labelledby="results?.length ? 'localsearch-label' : undefined"
      class="results"
      @mousemove="onMouseMove"
    >
      <li
        v-for="(p, index) in results"
        :key="p.id"
        :id="'localsearch-item-' + index"
        :aria-selected="selectedIndex === index ? 'true' : 'false'"
        role="option"
      >
        <a
          :href="p.id"
          class="result"
          :class="{
            selected: selectedIndex === index
          }"
          :aria-label="[...p.titles, p.title].join(' > ')"
          @mouseenter="!disableMouseOver && (selectedIndex = index)"
          @focusin="selectedIndex = index"
          @click="$emit('close')"
          :data-index="index"
        >
          <div>
            <div class="titles">
              <span class="title-icon">#</span>
              <span
                v-for="(t, index) in p.titles"
                :key="index"
                class="title"
              >
                <span class="text" v-html="t" />
                <span class="vpi-chevron-right local-search-icon" />
              </span>
              <span class="title main">
                <span class="text" v-html="p.title" />
              </span>
            </div>

            <div v-if="showDetailedList" class="excerpt-wrapper">
              <div v-if="p.text" class="excerpt" inert>
                <div class="vp-doc" v-html="p.text" />
              </div>
              <div class="excerpt-gradient-bottom" />
              <div class="excerpt-gradient-top" />
            </div>
          </div>
        </a>
      </li>
      <li
        v-if="filterText && !results.length && enableNoResults"
        class="no-results"
      >
        no results "<strong>{{ filterText }}</strong
        >"
      </li>
    </ul>

    <div class="search-keyboard-shortcuts">
      <span>
        <kbd :aria-label="'up'">
          <span class="vpi-arrow-up navigate-icon" />
        </kbd>
        <kbd :aria-label="'down'">
          <span class="vpi-arrow-down navigate-icon" />
        </kbd>
        navigate
      </span>
      <span>
        <kbd :aria-label="'select'">
          <span class="vpi-corner-down-left navigate-icon" />
        </kbd>
        select
      </span>
      <span>
        <kbd :aria-label="'close'">esc</kbd>
       close
      </span>
    </div>
  </div>
</div>
<style scoped> .VPLocalSearchBox { position: fixed; z-index: 100; inset: 0; display: flex; } .backdrop { position: absolute; inset: 0; background: var(--vp-backdrop-bg-color); transition: opacity 0.5s; } .shell { position: relative; padding: 12px; margin: 64px auto; display: flex; flex-direction: column; gap: 16px; background: var(--vp-local-search-bg); width: min(100vw - 60px, 900px); height: min-content; max-height: min(100vh - 128px, 900px); border-radius: 6px; } @media (max-width: 767px) { .shell { margin: 0; width: 100vw; height: 100vh; max-height: none; border-radius: 0; } } .search-bar { border: 1px solid var(--vp-c-divider); border-radius: 4px; display: flex; align-items: center; padding: 0 12px; cursor: text; } @media (max-width: 767px) { .search-bar { padding: 0 8px; } } .search-bar:focus-within { border-color: var(--vp-c-brand-1); } .local-search-icon { display: block; font-size: 18px; } .navigate-icon { display: block; font-size: 14px; } .search-icon { margin: 8px; } @media (max-width: 767px) { .search-icon { display: none; } } .search-input { padding: 6px 12px; font-size: inherit; width: 100%; } @media (max-width: 767px) { .search-input { padding: 6px 4px; } } .search-actions { display: flex; gap: 4px; } @media (any-pointer: coarse) { .search-actions { gap: 8px; } } @media (min-width: 769px) { .search-actions.before { display: none; } } .search-actions button { padding: 8px; } .search-actions button:not([disabled]):hover, .toggle-layout-button.detailed-list { color: var(--vp-c-brand-1); } .search-actions button.clear-button:disabled { opacity: 0.37; } .search-keyboard-shortcuts { font-size: 0.8rem; opacity: 75%; display: flex; flex-wrap: wrap; gap: 16px; line-height: 14px; } .search-keyboard-shortcuts span { display: flex; align-items: center; gap: 4px; } @media (max-width: 767px) { .search-keyboard-shortcuts { display: none; } } .search-keyboard-shortcuts kbd { background: rgba(128, 128, 128, 0.1); border-radius: 4px; padding: 3px 6px; min-width: 24px; display: inline-block; text-align: center; vertical-align: middle; border: 1px solid rgba(128, 128, 128, 0.15); box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); } .results { display: flex; flex-direction: column; gap: 6px; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; } .result { display: flex; align-items: center; gap: 8px; border-radius: 4px; transition: none; line-height: 1rem; border: solid 2px var(--vp-local-search-result-border); outline: none; } .result > div { margin: 12px; width: 100%; overflow: hidden; } @media (max-width: 767px) { .result > div { margin: 8px; } } .titles { display: flex; flex-wrap: wrap; gap: 4px; position: relative; z-index: 1001; padding: 2px 0; } .title { display: flex; align-items: center; gap: 4px; } .title.main { font-weight: 500; } .title-icon { opacity: 0.5; font-weight: 500; color: var(--vp-c-brand-1); } .title svg { opacity: 0.5; } .result.selected { --vp-local-search-result-bg: var(--vp-local-search-result-selected-bg); border-color: var(--vp-local-search-result-selected-border); } .excerpt-wrapper { position: relative; } .excerpt { opacity: 50%; pointer-events: none; max-height: 140px; overflow: hidden; position: relative; margin-top: 4px; } .result.selected .excerpt { opacity: 1; } .excerpt :deep(*) { font-size: 0.8rem !important; line-height: 130% !important; } .titles :deep(mark), .excerpt :deep(mark) { background-color: var(--vp-local-search-highlight-bg); color: var(--vp-local-search-highlight-text); border-radius: 2px; padding: 0 2px; } .excerpt :deep(.vp-code-group) .tabs { display: none; } .excerpt :deep(.vp-code-group) div[class*='language-'] { border-radius: 8px !important; } .excerpt-gradient-bottom { position: absolute; bottom: -1px; left: 0; width: 100%; height: 8px; background: linear-gradient(transparent, var(--vp-local-search-result-bg)); z-index: 1000; } .excerpt-gradient-top { position: absolute; top: -1px; left: 0; width: 100%; height: 8px; background: linear-gradient(var(--vp-local-search-result-bg), transparent); z-index: 1000; } .result.selected .titles, .result.selected .title-icon { color: var(--vp-c-brand-1) !important; } .no-results { font-size: 0.9rem; text-align: center; padding: 12px; } svg { flex: none; } </style><script lang="ts" setup>

import DPMenuLink from './DPMenuLink.vue' import DPMenuGroup from './DPMenuGroup.vue'

defineProps<{ items?: any[] }>() </script>

<slot />
<style scoped> .VPMenu { border-radius: 12px; padding: 12px; min-width: 128px; border: 1px solid var(--vp-c-divider); background-color: var(--vp-c-bg-elv); box-shadow: var(--vp-shadow-3); transition: background-color 0.5s; max-height: calc(100vh - var(--vp-nav-height)); overflow-y: auto; } .VPMenu :deep(.group) { margin: 0 -12px; padding: 0 12px 12px; } .VPMenu :deep(.group + .group) { border-top: 1px solid var(--vp-c-divider); padding: 11px 12px 12px; } .VPMenu :deep(.group:last-child) { padding-bottom: 0; } .VPMenu :deep(.group + .item) { border-top: 1px solid var(--vp-c-divider); padding: 11px 16px 0; } .VPMenu :deep(.item) { padding: 0 16px; white-space: nowrap; } .VPMenu :deep(.label) { flex-grow: 1; line-height: 28px; font-size: 12px; font-weight: 500; color: var(--vp-c-text-2); transition: color 0.5s; } .VPMenu :deep(.action) { padding-left: 24px; } </style><script lang="ts" setup>

import DPMenuLink from './DPMenuLink.vue'

defineProps<{ text?: string items: any[] }>() </script>

{{ text }}

<template v-for="item in items">
  <DPMenuLink v-if="'link' in item" :item="item" />
</template>
<style scoped> .VPMenuGroup { margin: 12px -12px 0; border-top: 1px solid var(--vp-c-divider); padding: 12px 12px 0; } .VPMenuGroup:first-child { margin-top: 0; border-top: 0; padding-top: 0; } .VPMenuGroup + .VPMenuGroup { margin-top: 12px; border-top: 1px solid var(--vp-c-divider); } .title { padding: 0 12px; line-height: 32px; font-size: 14px; font-weight: 600; color: var(--vp-c-text-2); white-space: nowrap; transition: color 0.25s; } </style><script lang="ts" setup>

import type { DefaultTheme } from 'vitepress/theme' import { isActive } from '../utils/shared' import DPLink from './DPLink.vue' import { useData } from 'vitepress';

defineProps<{ item: DefaultTheme.NavItemWithLink }>()

const { page } = useData() </script>

<style scoped> .VPMenuGroup + .VPMenuLink { margin: 12px -12px 0; border-top: 1px solid var(--vp-c-divider); padding: 12px 12px 0; } .link { display: block; border-radius: 6px; padding: 0 12px; line-height: 32px; font-size: 14px; font-weight: 500; color: var(--vp-c-text-1); white-space: nowrap; transition: background-color 0.25s, color 0.25s; } .link:hover { color: var(--vp-c-brand-1); background-color: var(--vp-c-default-soft); } .link.active { color: var(--vp-c-brand-1); } </style><script setup lang="ts">

import { inBrowser, useData } from 'vitepress' import { computed, provide, watchEffect } from 'vue' import { useNav } from '../composables/nav' import VPNavBar from './DPNavBar.vue' import VPNavScreen from './DPNavScreen.vue'

const { isScreenOpen, closeScreen, toggleScreen } = useNav() const { frontmatter } = useData()

const hasNavbar = computed(() => { return frontmatter.value.navbar !== false })

provide('close-screen', closeScreen)

watchEffect(() => { if (inBrowser) { document.documentElement.classList.toggle('hide-nav', !hasNavbar.value) } }) </script>

<style scoped> .VPNav { position: relative; top: var(--vp-layout-top-height, -24px); /*rtl:ignore*/ left: 0; z-index: var(--vp-z-index-nav); width: 100%; pointer-events: none; transition: background-color 0.5s; } @media (min-width: 520px) { .VPNav { position: fixed; top: var(--vp-layout-top-height, 0px); } } </style><script lang="ts" setup>

import { useWindowScroll } from '@vueuse/core' import { useData } from 'vitepress' import { ref, watchPostEffect } from 'vue' import VPNavBarAppearance from './DPNavBarAppearance.vue' import VPNavBarExtra from './DPNavBarExtra.vue' import VPNavBarHamburger from './DPNavBarHamburger.vue' import VPNavBarMenu from './DPNavBarMenu.vue' import VPNavBarSearch from './DPNavBarSearch.vue' import VPNavBarSocialLinks from './DPNavBarSocialLinks.vue' import VPNavBarTitle from './DPNavBarTitle.vue'

const props = defineProps<{ isScreenOpen: boolean }>()

defineEmits<{ (e: 'toggle-screen'): void }>()

const { y } = useWindowScroll() const { frontmatter } = useData()

const classes = ref<Record<string, boolean>>({})

watchPostEffect(() => { classes.value = { 'home': frontmatter.value.layout === 'home', 'top': y.value === 0, 'screen-open': props.isScreenOpen } }) </script>

    <div class="content">
      <div class="content-body">
        <slot name="nav-bar-content-before" />
        <VPNavBarSearch class="search" />
        <VPNavBarMenu class="menu" />
        <VPNavBarAppearance class="appearance" />
        <VPNavBarSocialLinks class="social-links" />
        <VPNavBarExtra class="extra" />
        <slot name="nav-bar-content-after" />
        <VPNavBarHamburger class="hamburger" :active="isScreenOpen" @click="$emit('toggle-screen')" />
      </div>
    </div>
  </div>
</div>

<div class="divider">
  <div class="divider-line" />
</div>
<style scoped> .VPNavBar { position: relative; height: var(--vp-nav-height); pointer-events: none; white-space: nowrap; transition: background-color 0.25s; } .VPNavBar.screen-open { transition: none; background-color: var(--vp-nav-bg-color); border-bottom: 1px solid var(--vp-c-divider); } .VPNavBar:not(.home) { background-color: var(--vp-nav-bg-color); } @media (min-width: 960px) { .VPNavBar:not(.home) { background-color: transparent; } .VPNavBar:not(.has-sidebar):not(.home.top) { background-color: var(--vp-nav-bg-color); } } .container { display: flex; justify-content: space-between; margin: 0 auto; max-width: calc(var(--vp-layout-max-width) - 64px); height: var(--vp-nav-height); pointer-events: none; padding-left:20px; padding-right:20px; } .container > .title, .container > .content { pointer-events: none; } .container :deep(*) { pointer-events: auto; } @media (max-width: 520px) { .VPNavBar .container { padding-left:0; padding-right:0; } } .title { flex-shrink: 0; height: calc(var(--vp-nav-height) - 1px); transition: background-color 0.5s; } .content { flex-grow: 1; } .content-body { display: flex; justify-content: flex-end; align-items: center; height: var(--vp-nav-height); transition: background-color 0.5s; } @media (min-width: 960px) { .VPNavBar:not(.home.top) .content-body { position: relative; background-color: var(--vp-nav-bg-color); } .VPNavBar:not(.has-sidebar):not(.home.top) .content-body { background-color: transparent; } } @media (max-width: 767px) { .content-body { column-gap: 0.5rem; } } .menu + .translations::before, .menu + .appearance::before, .menu + .social-links::before, .translations + .appearance::before, .appearance + .social-links::before { margin-right: 8px; margin-left: 8px; width: 1px; height: 24px; background-color: var(--vp-c-divider); content: ""; } .menu + .appearance::before, .translations + .appearance::before { margin-right: 16px; } .appearance + .social-links::before { margin-left: 16px; } .social-links { margin-right: -8px; } .divider { width: 100%; height: 1px; } @media (min-width: 960px) { .VPNavBar.has-sidebar .divider { padding-left: var(--vp-sidebar-width); } } @media (min-width: 1440px) { .VPNavBar.has-sidebar .divider { padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width)); } } .divider-line { width: 100%; height: 0px; transition: background-color 0.5s; } .VPNavBar:not(.home) .divider-line { background-color: var(--vp-c-gutter); } @media (min-width: 960px) { .VPNavBar:not(.home.top) .divider-line { background-color: var(--vp-c-gutter); } .VPNavBar:not(.has-sidebar):not(.home.top) .divider { background-color: var(--vp-c-gutter); } } </style><script lang="ts" setup>

import { useData } from 'vitepress'; import DPSwitchAppearance from './DPSwitchAppearance.vue'

const { site } = useData() </script>

<style scoped> .VPNavBarAppearance { display: none; } @media (min-width: 1280px) { .VPNavBarAppearance { display: flex; align-items: center; } } </style> <script lang="ts" setup> import { computed } from 'vue' import DPFlyout from './DPFlyout.vue' // import VPMenuLink from './VPMenuLink.vue' import DPSocialLinks from './DPSocialLinks.vue' import { useData } from 'vitepress'; // import { useLangs } from '../composables/langs' const { site, theme } = useData() // const { localeLinks, currentLang } = useLangs({ correspondingLink: true }) const hasExtraContent = computed( () => site.value.appearance || theme.value.socialLinks ) </script>