Browse Source

bug-fixes

master
Silberengel 4 weeks ago
parent
commit
a91bf4d56c
  1. 4
      src/app.css
  2. 36
      src/lib/components/content/FileExplorer.svelte
  3. 2
      src/lib/components/modals/EventJsonModal.svelte
  4. 67
      src/lib/modules/feed/FeedPost.svelte
  5. 24
      src/lib/services/content/git-repo-fetcher.ts
  6. 96
      src/routes/repos/+page.svelte
  7. 54
      src/routes/repos/[naddr]/+page.svelte
  8. 4
      static/healthz.json
  9. 4
      vite.config.ts

4
src/app.css

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
/* Custom highlight.js theme for code blocks, JSON previews, and markdown */
/* highlight.js VS2015 theme for code blocks, JSON previews, and markdown */
/* @import must come before all other statements */
@import './lib/styles/highlight-theme.css';
@import 'highlight.js/styles/vs2015.css';
/* stylelint-disable-next-line at-rule-no-unknown */
@tailwind base;

36
src/lib/components/content/FileExplorer.svelte

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
<script lang="ts">
import type { GitFile, GitRepoInfo } from '../../services/content/git-repo-fetcher.js';
import { fetchGitHubApi } from '../../services/github-api.js';
import { browser } from '$app/environment';
// @ts-ignore - highlight.js default export works at runtime
import hljs from 'highlight.js';
import 'highlight.js/styles/vs2015.css';
import Icon from '../ui/Icon.svelte';
interface Props {
@ -322,23 +324,24 @@ @@ -322,23 +324,24 @@
// Apply syntax highlighting when file content changes
$effect(() => {
if (!browser || !hljs) return; // Only run in browser and if hljs is available
if (fileContent && selectedFile && isCodeFile(selectedFile) && codeRef) {
const language = getLanguageFromExtension(selectedFile);
try {
// Check if language is supported, fallback to plaintext if not
if (hljs.getLanguage(language)) {
if (hljs.getLanguage && hljs.getLanguage(language)) {
codeRef.innerHTML = hljs.highlight(fileContent, { language }).value;
codeRef.className = `language-${language}`;
codeRef.className = `hljs language-${language}`;
} else {
// Language not supported, use plaintext
codeRef.innerHTML = hljs.highlight(fileContent, { language: 'plaintext' }).value;
codeRef.className = 'language-plaintext';
codeRef.className = 'hljs language-plaintext';
}
} catch (error) {
// If highlighting fails, just display plain text
console.warn(`Failed to highlight code with language '${language}':`, error);
codeRef.textContent = fileContent;
codeRef.className = 'language-plaintext';
codeRef.className = 'hljs language-plaintext';
}
}
});
@ -568,7 +571,7 @@ @@ -568,7 +571,7 @@
</div>
{:else if fileContent !== null}
{#if isCodeFile(selectedFile)}
<pre class="file-content-code"><code bind:this={codeRef} class="language-{getLanguageFromExtension(selectedFile)}">{fileContent}</code></pre>
<pre class="file-content-code"><code bind:this={codeRef} class="hljs language-{getLanguageFromExtension(selectedFile)}">{fileContent}</code></pre>
{:else}
<pre class="file-content-code"><code>{fileContent}</code></pre>
{/if}
@ -818,28 +821,7 @@ @@ -818,28 +821,7 @@
padding: 1rem;
}
.file-content-code {
margin: 0;
background: #000000 !important; /* Pure black background */
border: 1px solid #333333;
border-radius: 4px;
padding: 1rem;
overflow-x: auto;
white-space: pre;
word-wrap: break-word;
}
:global(.dark) .file-content-code {
background: #000000 !important; /* Pure black background */
border-color: #333333;
}
.file-content-code code {
display: block;
overflow-x: auto;
padding: 0;
/* Theme colors are defined in highlight-theme.css */
}
/* Code block styling is handled by highlight.js vs2015 theme */
.file-image-container {
display: flex;

2
src/lib/components/modals/EventJsonModal.svelte

@ -292,7 +292,7 @@ @@ -292,7 +292,7 @@
display: block;
padding: 0;
background: transparent !important;
/* Colors are defined in highlight-theme.css */
/* Colors are defined by highlight.js vs2015 theme */
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Droid Sans Mono', 'Source Code Pro', monospace;
font-size: 0.875rem;
line-height: 1.5;

67
src/lib/modules/feed/FeedPost.svelte

@ -193,15 +193,46 @@ @@ -193,15 +193,46 @@
// Parse NIP-21 links and create segments for rendering
interface ContentSegment {
type: 'text' | 'profile' | 'event' | 'url' | 'wikilink' | 'hashtag' | 'greentext';
type: 'text' | 'profile' | 'event' | 'url' | 'grasp' | 'wikilink' | 'hashtag' | 'greentext';
content: string; // Display text (without nostr: prefix for links)
pubkey?: string; // For profile badges
eventId?: string; // For event links (bech32 or hex)
url?: string; // For regular HTTP/HTTPS URLs
graspUrl?: string; // For grasp server URLs
wikilink?: string; // For wikilink d-tag
hashtag?: string; // For hashtag topic name
}
// Detect if a URL is a grasp server link (NIP-34)
// Grasp links match: http://<grasp-path>/<valid-npub>/<string>.git or https://<grasp-path>/<valid-npub>/<string>.git
function isGraspLink(url: string): boolean {
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(p => p);
// Check if it's a git clone URL ending in .git
if (pathParts.length >= 2 && pathParts[pathParts.length - 1].endsWith('.git')) {
// Check if the second-to-last part is a valid npub (bech32 encoded pubkey)
const potentialNpub = pathParts[pathParts.length - 2];
// npub format: npub1 followed by bech32 characters
if (/^npub1[a-z0-9]+$/.test(potentialNpub)) {
return true;
}
}
// Also check for websocket URLs (ws:// or wss://) which might be grasp relays
if (urlObj.protocol === 'ws:' || urlObj.protocol === 'wss:') {
// Could be a grasp relay, but we can't definitively identify it without more context
// For now, we'll only detect HTTP/HTTPS clone URLs
}
} catch {
// Invalid URL
return false;
}
return false;
}
// Process text to detect greentext (lines starting with >)
function processGreentext(text: string): ContentSegment[] {
const lines = text.split('\n');
@ -465,6 +496,14 @@ @@ -465,6 +496,14 @@
content: `#${hashtagMatch.hashtag}`,
hashtag: hashtagMatch.hashtag
});
} else {
// Check if it's a grasp link
if (isGraspLink(match.url)) {
finalSegments.push({
type: 'grasp',
content: match.url,
graspUrl: match.url
});
} else {
finalSegments.push({
type: 'url',
@ -472,6 +511,7 @@ @@ -472,6 +511,7 @@
url: match.url
});
}
}
textIndex = match.index + match.length;
}
@ -1085,11 +1125,30 @@ @@ -1085,11 +1125,30 @@
{:else if segment.type === 'profile' && segment.pubkey}
<ProfileBadge pubkey={segment.pubkey} inline={true} />
{:else if segment.type === 'event' && segment.eventId}
{@const eventUrl = getEventUrl(segment.eventId)}
<a
href={getEventUrl(segment.eventId)}
target="_blank"
rel="noopener noreferrer"
href={eventUrl}
class="nostr-event-link text-fog-accent dark:text-fog-dark-accent hover:underline"
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
goto(eventUrl);
}}
>
{segment.content}
</a>
{:else if segment.type === 'grasp' && segment.graspUrl}
<a
href={segment.graspUrl}
class="grasp-link text-fog-accent dark:text-fog-dark-accent hover:underline"
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
// For grasp links, we could navigate to a grasp server page or open the git URL
// For now, open in new tab since it's a git clone URL
window.open(segment.graspUrl, '_blank', 'noopener,noreferrer');
}}
title="Grasp server (NIP-34)"
>
{segment.content}
</a>

24
src/lib/services/content/git-repo-fetcher.ts

@ -533,8 +533,12 @@ async function fetchFromGitLab(owner: string, repo: string, baseUrl: string): Pr @@ -533,8 +533,12 @@ async function fetchFromGitLab(owner: string, repo: string, baseUrl: string): Pr
*/
async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Promise<GitRepoInfo | null> {
try {
// URL-encode owner and repo to handle special characters
const encodedOwner = encodeURIComponent(owner);
const encodedRepo = encodeURIComponent(repo);
// Use proxy endpoint to avoid CORS issues
const repoResponse = await fetch(`/api/gitea-proxy/repos/${owner}/${repo}?baseUrl=${encodeURIComponent(baseUrl)}`);
const repoResponse = await fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}?baseUrl=${encodeURIComponent(baseUrl)}`);
if (!repoResponse.ok) {
console.warn(`Gitea API error for repo ${owner}/${repo}: ${repoResponse.status} ${repoResponse.statusText}`);
return null;
@ -544,8 +548,8 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro @@ -544,8 +548,8 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro
const defaultBranch = repoData.default_branch || 'master';
const [branchesResponse, commitsResponse] = await Promise.all([
fetch(`/api/gitea-proxy/repos/${owner}/${repo}/branches?baseUrl=${encodeURIComponent(baseUrl)}`).catch(() => null),
fetch(`/api/gitea-proxy/repos/${owner}/${repo}/commits?baseUrl=${encodeURIComponent(baseUrl)}&limit=10`).catch(() => null)
fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}/branches?baseUrl=${encodeURIComponent(baseUrl)}`).catch(() => null),
fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}/commits?baseUrl=${encodeURIComponent(baseUrl)}&limit=10`).catch(() => null)
]);
let branchesData: any[] = [];
@ -596,9 +600,11 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro @@ -596,9 +600,11 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro
// Fetch file tree - Gitea uses /git/trees API endpoint
let files: GitFile[] = [];
// encodedOwner and encodedRepo are already defined at the top of the function
const encodedBranch = encodeURIComponent(defaultBranch);
try {
// Try the git/trees endpoint first (more complete)
const treeResponse = await fetch(`/api/gitea-proxy/repos/${owner}/${repo}/git/trees/${defaultBranch}?baseUrl=${encodeURIComponent(baseUrl)}&recursive=1`).catch(() => null);
const treeResponse = await fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}/git/trees/${encodedBranch}?baseUrl=${encodeURIComponent(baseUrl)}&recursive=1`).catch(() => null);
if (treeResponse && treeResponse.ok) {
const treeData = await treeResponse.json();
if (treeData.tree && Array.isArray(treeData.tree)) {
@ -613,7 +619,7 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro @@ -613,7 +619,7 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro
}
} else {
// Fallback to contents endpoint (only root directory)
const contentsResponse = await fetch(`/api/gitea-proxy/repos/${owner}/${repo}/contents?baseUrl=${encodeURIComponent(baseUrl)}&ref=${defaultBranch}`).catch(() => null);
const contentsResponse = await fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}/contents?baseUrl=${encodeURIComponent(baseUrl)}&ref=${encodedBranch}`).catch(() => null);
if (contentsResponse && contentsResponse.ok) {
const contentsData = await contentsResponse.json();
if (Array.isArray(contentsData)) {
@ -634,9 +640,11 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro @@ -634,9 +640,11 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro
// First try root directory (most common case)
let readme: { path: string; content: string; format: 'markdown' | 'asciidoc' } | undefined;
const readmeFiles = ['README.adoc', 'README.md', 'README.rst', 'README.txt'];
// encodedOwner and encodedRepo are already defined at the top of the function
for (const readmeFile of readmeFiles) {
try {
const fileResponse = await fetch(`/api/gitea-proxy/repos/${owner}/${repo}/contents/${readmeFile}?baseUrl=${encodeURIComponent(baseUrl)}&ref=${defaultBranch}`);
const encodedReadmeFile = encodeURIComponent(readmeFile);
const fileResponse = await fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}/contents/${encodedReadmeFile}?baseUrl=${encodeURIComponent(baseUrl)}&ref=${encodeURIComponent(defaultBranch)}`);
if (!fileResponse.ok) throw new Error('Not found');
const fileData = await fileResponse.json();
if (fileData.content) {
@ -675,7 +683,9 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro @@ -675,7 +683,9 @@ async function fetchFromGitea(owner: string, repo: string, baseUrl: string): Pro
// If found in tree, fetch it
if (readmePath) {
try {
const fileResponse = await fetch(`/api/gitea-proxy/repos/${owner}/${repo}/contents/${readmePath}?baseUrl=${encodeURIComponent(baseUrl)}&ref=${defaultBranch}`);
// URL-encode the file path segments
const encodedReadmePath = readmePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
const fileResponse = await fetch(`/api/gitea-proxy/repos/${encodedOwner}/${encodedRepo}/contents/${encodedReadmePath}?baseUrl=${encodeURIComponent(baseUrl)}&ref=${encodedBranch}`);
if (!fileResponse.ok) throw new Error('Not found');
const fileData = await fileResponse.json();
if (fileData.content) {

96
src/routes/repos/+page.svelte

@ -345,9 +345,44 @@ @@ -345,9 +345,44 @@
return null;
}
// Filter repos based on search query and filters
let filteredRepos = $derived.by(() => {
let filtered = repos;
// Search repos from cache when there's a search query
let searchRepos = $state<NostrEvent[]>([]);
let searchingCache = $state(false);
// When search query changes, search the cache directly
$effect(() => {
const query = searchQuery.trim();
if (!query) {
searchRepos = [];
searchingCache = false;
return;
}
// Search cache directly for matching repos
searchingCache = true;
searchCacheForRepos(query).then(results => {
searchRepos = results;
searchingCache = false;
});
});
async function searchCacheForRepos(query: string): Promise<NostrEvent[]> {
try {
// Get all cached repos (no limit for search)
const cachedRepos = await getRecentCachedEvents([KIND.REPO_ANNOUNCEMENT], 365 * 24 * 60 * 60 * 1000, 10000);
// For parameterized replaceable events, get the newest version of each (by pubkey + d tag)
const reposByKey = new Map<string, NostrEvent>();
for (const event of cachedRepos) {
const dTag = event.tags.find(t => t[0] === 'd')?.[1] || '';
const key = `${event.pubkey}:${dTag}`;
const existing = reposByKey.get(key);
if (!existing || event.created_at > existing.created_at) {
reposByKey.set(key, event);
}
}
let filtered = Array.from(reposByKey.values());
// Filter by "See my repos" checkbox
if (showMyRepos && currentPubkey) {
@ -362,15 +397,10 @@ @@ -362,15 +397,10 @@
});
}
// If no search query, return filtered list
if (!searchQuery.trim()) {
return filtered;
}
const query = searchQuery.trim().toLowerCase();
const queryLower = query.toLowerCase();
// Try to decode as pubkey (hex, npub, or nprofile)
const decodedPubkey = decodePubkeyToHex(query);
const decodedPubkey = decodePubkeyToHex(queryLower);
if (decodedPubkey) {
return filtered.filter(repo => {
// Match by owner pubkey
@ -384,7 +414,7 @@ @@ -384,7 +414,7 @@
}
// Try to decode as event ID (hex, note, nevent, or naddr)
const decodedEventId = decodeEventIdToHex(query);
const decodedEventId = decodeEventIdToHex(queryLower);
if (decodedEventId) {
return filtered.filter(repo => {
// Match by event ID
@ -404,34 +434,64 @@ @@ -404,34 +434,64 @@
return filtered.filter(repo => {
// Search in name
const name = getRepoName(repo).toLowerCase();
if (name.includes(query)) return true;
if (name.includes(queryLower)) return true;
// Search in description
const desc = getRepoDescription(repo).toLowerCase();
if (desc.includes(query)) return true;
if (desc.includes(queryLower)) return true;
// Search in clone URLs
const cloneUrls = getCloneUrls(repo);
if (cloneUrls.some(url => url.toLowerCase().includes(query))) return true;
if (cloneUrls.some(url => url.toLowerCase().includes(queryLower))) return true;
// Search in web URLs
const webUrls = getWebUrls(repo);
if (webUrls.some(url => url.toLowerCase().includes(query))) return true;
if (webUrls.some(url => url.toLowerCase().includes(queryLower))) return true;
// Search in d-tag
const dTag = getDTagFromEvent(repo).toLowerCase();
if (dTag.includes(query)) return true;
if (dTag.includes(queryLower)) return true;
// Search in naddr
const naddr = getNaddr(repo);
if (naddr && naddr.toLowerCase().includes(query)) return true;
if (naddr && naddr.toLowerCase().includes(queryLower)) return true;
// Search in maintainer pubkeys (as hex)
const maintainers = getMaintainers(repo);
if (maintainers.some(m => m.toLowerCase().includes(query))) return true;
if (maintainers.some(m => m.toLowerCase().includes(queryLower))) return true;
return false;
});
} catch (error) {
console.error('Error searching cache for repos:', error);
return [];
}
}
// Filter repos based on search query and filters
let filteredRepos = $derived.by(() => {
// If there's a search query, use search results from cache
if (searchQuery.trim()) {
return searchRepos;
}
// Otherwise, filter the loaded repos
let filtered = repos;
// Filter by "See my repos" checkbox
if (showMyRepos && currentPubkey) {
filtered = filtered.filter(repo => {
// Check if repo owner matches
if (repo.pubkey.toLowerCase() === currentPubkey.toLowerCase()) {
return true;
}
// Check if user is a maintainer
const maintainers = getMaintainers(repo);
return maintainers.some(m => m.toLowerCase() === currentPubkey.toLowerCase());
});
}
return filtered;
});
</script>

54
src/routes/repos/[naddr]/+page.svelte

@ -33,6 +33,8 @@ @@ -33,6 +33,8 @@
let issues = $state<NostrEvent[]>([]);
let issueComments = $state<Map<string, NostrEvent[]>>(new Map());
let issueStatuses = $state<Map<string, NostrEvent>>(new Map());
let loadingIssues = $state(false);
let loadingIssueData = $state(false); // Statuses, comments, profiles
let documentationEvents = $state<Map<string, NostrEvent>>(new Map());
let changingStatus = $state<Map<string, boolean>>(new Map()); // Track which issues are having status changed
let statusFilter = $state<string | null>(null); // Filter issues by status: null = all, 'open', 'resolved', 'closed', 'draft'
@ -115,11 +117,9 @@ @@ -115,11 +117,9 @@
break; // Success, stop trying other URLs
}
} catch (error) {
// Failed to fetch git repo
// Continue to next URL
// Failed to fetch git repo - continue to next URL
}
}
} else {
}
} catch (error) {
// Failed to load git repo
@ -279,6 +279,7 @@ @@ -279,6 +279,7 @@
async function loadIssues() {
if (!repoEvent) return;
loadingIssues = true;
try {
const gitUrls = extractGitUrls(repoEvent);
const relays = relayManager.getProfileReadRelays();
@ -301,10 +302,16 @@ @@ -301,10 +302,16 @@
filters.push({ '#r': gitUrls, kinds: [KIND.ISSUE], limit: 100 });
}
// Batch fetch all issues in parallel
// Search for issues by the repo author (issues might be created by repo maintainers)
filters.push({ authors: [repoEvent.pubkey], kinds: [KIND.ISSUE], limit: 100 });
// Batch fetch all issues in parallel with cache-first strategy
const issueEventsArrays = await Promise.all(
filters.map(filter =>
nostrClient.fetchEvents([filter], relays, { useCache: true, cacheResults: true })
nostrClient.fetchEvents([filter], relays, {
useCache: 'cache-first', // Prioritize cache for faster loading
cacheResults: true
})
)
);
@ -317,16 +324,24 @@ @@ -317,16 +324,24 @@
// Deduplicate and sort
const uniqueIssues = Array.from(new Map(issueEvents.map(e => [e.id, e])).values());
issues = uniqueIssues.sort((a, b) => b.created_at - a.created_at);
loadingIssues = false; // Issues are loaded, show them immediately
// Batch load statuses, comments, and profiles
await Promise.all([
// Load statuses, comments, and profiles in background (don't wait)
// This allows the UI to show issues immediately
loadingIssueData = true;
Promise.all([
loadIssueStatuses(),
loadIssueComments(),
loadAllProfiles()
]);
]).finally(() => {
loadingIssueData = false;
}).catch(() => {
// Background loading errors are non-critical
loadingIssueData = false;
});
} catch (error) {
// Failed to load issues
loadingIssues = false;
}
}
@ -346,7 +361,7 @@ @@ -346,7 +361,7 @@
limit: 200
}],
relays,
{ useCache: true, cacheResults: true }
{ useCache: 'cache-first', cacheResults: true } // Prioritize cache
);
@ -551,10 +566,11 @@ @@ -551,10 +566,11 @@
const relays = relayManager.getCommentReadRelays();
// Batch fetch all comments for all issues
// Use cache-first to load comments faster
const comments = await nostrClient.fetchEvents(
[{ '#e': issueIds, kinds: [KIND.COMMENT], limit: 500 }],
relays,
{ useCache: true, cacheResults: true }
{ useCache: 'cache-first', cacheResults: true } // Prioritize cache
);
// Group comments by issue ID
@ -1287,7 +1303,11 @@ @@ -1287,7 +1303,11 @@
</div>
{:else if activeTab === 'issues'}
<div class="issues-tab">
{#if issues.length > 0}
{#if loadingIssues}
<div class="loading-state">
<p class="text-fog-text dark:text-fog-dark-text">Loading issues...</p>
</div>
{:else if issues.length > 0}
<div class="issues-filter">
<label for="status-filter" class="filter-label">Filter by status:</label>
<select
@ -1310,6 +1330,9 @@ @@ -1310,6 +1330,9 @@
{:else}
{issues.length} {issues.length === 1 ? 'issue' : 'issues'}
{/if}
{#if loadingIssueData}
<span class="loading-indicator"> (loading details...)</span>
{/if}
</span>
</div>
<div class="issues-list">
@ -1601,17 +1624,22 @@ @@ -1601,17 +1624,22 @@
padding: 0.125rem 0.25rem;
border-radius: 0.25rem;
font-family: monospace;
color: var(--fog-text, #1f2937);
}
:global(.dark) .readme-container :global(code) {
background: var(--fog-dark-highlight, #475569);
color: var(--fog-dark-text, #f9fafb);
}
/* Code blocks with highlight.js are styled by vs2015 theme */
/* Pre blocks for non-highlighted code */
.readme-container :global(pre) {
background: var(--fog-highlight, #f3f4f6);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
/* Background will be overridden by vs2015 theme for hljs code blocks */
background: var(--fog-highlight, #f3f4f6);
}
:global(.dark) .readme-container :global(pre) {

4
static/healthz.json

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
"status": "ok",
"service": "aitherboard",
"version": "0.3.2",
"buildTime": "2026-02-14T08:45:25.090Z",
"buildTime": "2026-02-14T09:16:17.577Z",
"gitCommit": "unknown",
"timestamp": 1771058725091
"timestamp": 1771060577577
}

4
vite.config.ts

@ -173,8 +173,8 @@ export default defineConfig({ @@ -173,8 +173,8 @@ export default defineConfig({
// Suppress warning about highlight.js default import - it's used in reactive contexts that Vite can't detect
if (warning.message &&
typeof warning.message === 'string' &&
warning.message.includes('highlight.js') &&
warning.message.includes('never used')) {
(warning.message.includes('highlight.js') || warning.message.includes('"default" is imported')) &&
(warning.message.includes('never used') || warning.message.includes('but never used'))) {
return;
}
warn(warning);

Loading…
Cancel
Save