diff --git a/docs/.vitepress/constants.ts b/docs/.vitepress/constants.ts index 10e73e887..e50944e6c 100644 --- a/docs/.vitepress/constants.ts +++ b/docs/.vitepress/constants.ts @@ -15,51 +15,12 @@ */ import type { DefaultTheme } from 'vitepress' -import consola from 'consola' +import { excluded } from './shared' import { transform, transformGuide } from './transformer' // @unocss-include -export const meta = { - name: 'freemediaheckyeah', - description: 'The largest collection of free stuff on the internet!', - hostname: 'https://fmhy.net', - keywords: ['stream', 'movies', 'gaming', 'reading', 'anime'], - build: { - api: true, - nsfw: true - } -} - -export const excluded = [ - 'readme.md', - 'single-page', - 'feedback.md', - 'index.md', - 'sandbox.md', - 'startpage.md' -] - -if (process.env.FMHY_BUILD_NSFW === 'false') { - consola.info('FMHY_BUILD_NSFW is set to false, disabling NSFW content') - meta.build.nsfw = false -} -if (process.env.FMHY_BUILD_API === 'false') { - consola.info('FMHY_BUILD_API is set to false, disabling API component') - meta.build.api = false -} - -const formatCommitRef = (commitRef: string) => - `${commitRef.slice(0, 8)}` - -export const commitRef = - process.env.CF_PAGES && process.env.CF_PAGES_COMMIT_SHA - ? formatCommitRef(process.env.CF_PAGES_COMMIT_SHA) - : process.env.COMMIT_REF - ? formatCommitRef(process.env.COMMIT_REF) - : 'dev' - -export const feedback = `Made with ❤` +export * from './shared' export const search: DefaultTheme.Config['search'] = { options: { @@ -147,190 +108,3 @@ export const search: DefaultTheme.Config['search'] = { }, provider: 'local' } - -export const socialLinks: DefaultTheme.SocialLink[] = [ - { icon: 'github', link: 'https://github.com/fmhy/edit' }, - { icon: 'discord', link: 'https://github.com/fmhy/FMHY/wiki/FMHY-Discord' }, - { - icon: 'reddit', - link: 'https://reddit.com/r/FREEMEDIAHECKYEAH' - } -] - -export const nav: DefaultTheme.NavItem[] = [ - { text: '📑 Changelog', link: '/posts/changelog-sites' }, - { text: '📖 Glossary', link: 'https://rentry.org/The-Piracy-Glossary' }, - { - text: '💾 Backups', - link: '/other/backups' - }, - { - text: '🌱 Ecosystem', - items: [ - { text: '🌐 Search', link: '/posts/search' }, - { text: '❓ FAQs', link: '/other/FAQ' }, - { text: '🔖 Bookmarks', link: 'https://github.com/fmhy/bookmarks' }, - { text: '✅ SafeGuard', link: 'https://github.com/fmhy/FMHY-SafeGuard' }, - { text: '🚀 Startpage', link: 'https://fmhy.net/startpage' }, - { text: '📋 snowbin', link: 'https://pastes.fmhy.net' }, - { text: '🔎 SearXNG', link: 'https://searx.fmhy.net/' }, - { - text: '💡 Site Hunting', - link: 'https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/find-new-sites/' - }, - { - text: '😇 SFW FMHY', - link: 'https://rentry.org/piracy' - }, - { - text: '🏠 Selfhosting', - link: '/other/selfhosting' - }, - { text: '🏞 Wallpapers', link: '/other/wallpapers' }, - { text: '💙 Feedback', link: '/feedback' } - ] - } -] - -export const sidebar: DefaultTheme.Sidebar | DefaultTheme.NavItemWithLink[] = [ - { - text: ' Beginners Guide', - link: '/beginners-guide' - }, - { - text: ' Posts', - link: '/posts' - }, - { - text: ' Contribute', - link: '/other/contributing' - }, - { - text: 'Wiki', - collapsed: false, - items: [ - { - text: ' Adblocking / Privacy', - link: '/privacy' - }, - { - text: ' Artificial Intelligence', - link: '/ai' - }, - { - text: ' Movies / TV / Anime', - link: '/video' - }, - { - text: ' Music / Podcasts / Radio', - link: '/audio' - }, - { - text: ' Gaming / Emulation', - link: '/gaming' - }, - { - text: ' Books / Comics / Manga', - link: '/reading' - }, - { - text: ' Downloading', - link: '/downloading' - }, - { - text: ' Torrenting', - link: '/torrenting' - }, - { - text: ' Educational', - link: '/educational' - }, - { - text: ' Android / iOS', - link: '/mobile' - }, - { - text: ' Linux / macOS', - link: '/linux-macos' - }, - { - text: ' Non-English', - link: '/non-english' - }, - { - text: ' Miscellaneous', - link: '/misc' - } - ] - }, - { - text: 'Tools', - collapsed: false, - items: [ - { - text: ' System Tools', - link: '/system-tools' - }, - { - text: ' File Tools', - link: '/file-tools' - }, - { - text: ' Internet Tools', - link: '/internet-tools' - }, - { - text: ' Social Media Tools', - link: '/social-media-tools' - }, - { - text: ' Text Tools', - link: '/text-tools' - }, - { - text: ' Gaming Tools', - link: '/gaming-tools' - }, - { - text: ' Image Tools', - link: '/image-tools' - }, - { - text: ' Video Tools', - link: '/video-tools' - }, - { - text: ' Audio Tools', - link: '/audio#audio-tools' - }, - { - text: ' Educational Tools', - link: '/educational#educational-tools' - }, - { - text: ' Developer Tools', - link: '/developer-tools' - } - ] - }, - { - text: 'More', - collapsed: true, - items: [ - meta.build.nsfw - ? { - text: ' NSFW', - link: 'https://rentry.org/NSFW-Checkpoint' - } - : {}, - { - text: ' Unsafe Sites', - link: '/unsafe' - }, - { - text: ' Storage', - link: '/storage' - } - ] - } -] diff --git a/docs/.vitepress/shared.ts b/docs/.vitepress/shared.ts new file mode 100644 index 000000000..1bf7a7284 --- /dev/null +++ b/docs/.vitepress/shared.ts @@ -0,0 +1,250 @@ +/** + * Copyright (c) 2025 taskylizard. Apache License 2.0. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DefaultTheme } from 'vitepress' + +// @unocss-include + +export const meta = { + name: 'freemediaheckyeah', + description: 'The largest collection of free stuff on the internet!', + hostname: 'https://fmhy.net', + keywords: ['stream', 'movies', 'gaming', 'reading', 'anime'], + build: { + api: true, + nsfw: true + } +} + +export const excluded = [ + 'readme.md', + 'single-page', + 'feedback.md', + 'index.md', + 'sandbox.md', + 'startpage.md' +] + +const safeEnv = (key: string) => typeof process !== 'undefined' ? process.env?.[key] : undefined + +if (safeEnv('FMHY_BUILD_NSFW') === 'false') { + meta.build.nsfw = false +} +if (safeEnv('FMHY_BUILD_API') === 'false') { + meta.build.api = false +} + +const formatCommitRef = (commitRef: string) => + `${commitRef.slice(0, 8)}` + +const cfStart = safeEnv('CF_PAGES_COMMIT_SHA') +const commitStart = safeEnv('COMMIT_REF') + +export const commitRef = + safeEnv('CF_PAGES') && cfStart + ? formatCommitRef(cfStart) + : commitStart + ? formatCommitRef(commitStart) + : 'dev' + +export const feedback = `Made with ❤` + +export const socialLinks: DefaultTheme.SocialLink[] = [ + { icon: 'github', link: 'https://github.com/fmhy/edit' }, + { icon: 'discord', link: 'https://github.com/fmhy/FMHY/wiki/FMHY-Discord' }, + { + icon: 'reddit', + link: 'https://reddit.com/r/FREEMEDIAHECKYEAH' + } +] + +export const nav: DefaultTheme.NavItem[] = [ + { text: '📑 Changelog', link: '/posts/changelog-sites' }, + { text: '📖 Glossary', link: 'https://rentry.org/The-Piracy-Glossary' }, + { + text: '💾 Backups', + link: '/other/backups' + }, + { + text: '🌱 Ecosystem', + items: [ + { text: '🌐 Search', link: '/posts/search' }, + { text: '❓ FAQs', link: '/other/FAQ' }, + { text: '🔖 Bookmarks', link: 'https://github.com/fmhy/bookmarks' }, + { text: '✅ SafeGuard', link: 'https://github.com/fmhy/FMHY-SafeGuard' }, + { text: '🚀 Startpage', link: 'https://fmhy.net/startpage' }, + { text: '📋 snowbin', link: 'https://pastes.fmhy.net' }, + { text: '🔎 SearXNG', link: 'https://searx.fmhy.net/' }, + { + text: '💡 Site Hunting', + link: 'https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/find-new-sites/' + }, + { + text: '😇 SFW FMHY', + link: 'https://rentry.org/piracy' + }, + { + text: '🏠 Selfhosting', + link: '/other/selfhosting' + }, + { text: '🏞 Wallpapers', link: '/other/wallpapers' }, + { text: '💙 Feedback', link: '/feedback' } + ] + } +] + +export const sidebar: DefaultTheme.Sidebar | DefaultTheme.NavItemWithLink[] = [ + { + text: ' Beginners Guide', + link: '/beginners-guide' + }, + { + text: ' Posts', + link: '/posts' + }, + { + text: ' Contribute', + link: '/other/contributing' + }, + { + text: 'Wiki', + collapsed: false, + items: [ + { + text: ' Adblocking / Privacy', + link: '/privacy' + }, + { + text: ' Artificial Intelligence', + link: '/ai' + }, + { + text: ' Movies / TV / Anime', + link: '/video' + }, + { + text: ' Music / Podcasts / Radio', + link: '/audio' + }, + { + text: ' Gaming / Emulation', + link: '/gaming' + }, + { + text: ' Books / Comics / Manga', + link: '/reading' + }, + { + text: ' Downloading', + link: '/downloading' + }, + { + text: ' Torrenting', + link: '/torrenting' + }, + { + text: ' Educational', + link: '/educational' + }, + { + text: ' Android / iOS', + link: '/mobile' + }, + { + text: ' Linux / macOS', + link: '/linux-macos' + }, + { + text: ' Non-English', + link: '/non-english' + }, + { + text: ' Miscellaneous', + link: '/misc' + } + ] + }, + { + text: 'Tools', + collapsed: false, + items: [ + { + text: ' System Tools', + link: '/system-tools' + }, + { + text: ' File Tools', + link: '/file-tools' + }, + { + text: ' Internet Tools', + link: '/internet-tools' + }, + { + text: ' Social Media Tools', + link: '/social-media-tools' + }, + { + text: ' Text Tools', + link: '/text-tools' + }, + { + text: ' Gaming Tools', + link: '/gaming-tools' + }, + { + text: ' Image Tools', + link: '/image-tools' + }, + { + text: ' Video Tools', + link: '/video-tools' + }, + { + text: ' Audio Tools', + link: '/audio#audio-tools' + }, + { + text: ' Educational Tools', + link: '/educational#educational-tools' + }, + { + text: ' Developer Tools', + link: '/developer-tools' + } + ] + }, + { + text: 'More', + collapsed: true, + items: [ + meta.build.nsfw + ? { + text: ' NSFW', + link: 'https://rentry.org/NSFW-Checkpoint' + } + : {}, + { + text: ' Unsafe Sites', + link: '/unsafe' + }, + { + text: ' Storage', + link: '/storage' + } + ] + } +] diff --git a/docs/.vitepress/theme/components/VPLocalSearchBox.vue b/docs/.vitepress/theme/components/VPLocalSearchBox.vue index a29a82027..e0eb66426 100644 --- a/docs/.vitepress/theme/components/VPLocalSearchBox.vue +++ b/docs/.vitepress/theme/components/VPLocalSearchBox.vue @@ -59,6 +59,7 @@ import { LRUCache } from 'vitepress/dist/client/theme-default/support/lru' import { createSearchTranslate } from 'vitepress/dist/client/theme-default/support/translation' import Tooltip from './Tooltip.vue' import FloatingVue from 'floating-vue' +import { sidebar } from '../../shared' const emit = defineEmits<{ (e: 'close'): void @@ -236,9 +237,34 @@ debouncedWatch( } } - results.value = index + function findPageTitle(items: any[], path: string): string | null { + for (const item of items) { + if (item.link === path) return item.text + if (item.items) { + const found = findPageTitle(item.items, path) + if (found) return found + } + } + return null + } + + const rawResults = index .search(query, searchOptions) .slice(0, 16) as (SearchResult & Result)[] + + results.value = rawResults.map((r) => { + const [id] = r.id.split('#') + const cleanPath = '/' + id.replace(/\.html$/, '').replace(/^\//, '') + const pageTitle = findPageTitle(Array.isArray(sidebar) ? sidebar : [], cleanPath) + const titles = [...r.titles] + + if (pageTitle && !titles.includes(pageTitle) && r.title !== pageTitle) { + titles.unshift(pageTitle) + } + + return { ...r, titles } + }) + enableNoResults.value = true // Fetch and process excerpts for detailed view highlighting @@ -256,11 +282,13 @@ debouncedWatch( const [id, anchor] = r.id.split('#') const map = cache.get(id) const text = map?.get(anchor) ?? '' + if (isFuzzySearch.value) { for (const term in r.match) { terms.add(term) } } + return { ...r, text } }) @@ -705,8 +733,8 @@ function formMarkRegex(terms: Set) { function onMouseMove(e: MouseEvent) { if (!disableMouseOver.value) return - const el = (e.target as HTMLElement)?.closest('.result') - const index = Number.parseInt(el?.dataset.index!) + const el = (e.target as HTMLElement)?.closest('.result-item') + const index = el?.dataset?.index ? Number.parseInt(el.dataset.index) : -1 if (index >= 0 && index !== selectedIndex.value) { selectedIndex.value = index } @@ -819,6 +847,8 @@ function onMouseMove(e: MouseEvent) { :id="'localsearch-item-' + index" :aria-selected="selectedIndex === index ? 'true' : 'false'" role="option" + class="result-item" + :data-index="index" >
+
-
- - {{ (currentMarkIndex.get(index) ?? 0) + 1 }}/{{ resultMarks.get(index)?.length }} - -
+
+ + {{ (currentMarkIndex.get(index) ?? 0) + 1 }}/{{ resultMarks.get(index)?.length }} + +