components
This commit is contained in:
parent
8b1f8d3d88
commit
5c8fb0015c
@ -28,6 +28,8 @@
|
||||
"find-up": "^7.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.3",
|
||||
"hast-util-select": "^6.0.4",
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"html-entities": "^2.5.2",
|
||||
"imagetools": "file:../imagetools",
|
||||
"marked": "^16.1.2",
|
||||
|
||||
730
packages/polymech/src/components/FileTree.astro
Normal file
730
packages/polymech/src/components/FileTree.astro
Normal file
@ -0,0 +1,730 @@
|
||||
---
|
||||
import { processFileTree } from './rehype-file-tree';
|
||||
import path from "node:path";
|
||||
import { globBase, globParent, pathInfo } from "@polymech/commons";
|
||||
import { Icons } from './Icons';
|
||||
import { definitions } from './file-tree-icons';
|
||||
import { Img } from "imagetools/components";
|
||||
|
||||
export interface Props {
|
||||
glob?: string; // Glob pattern to auto-generate file tree
|
||||
maxDepth?: number; // Maximum directory depth to show
|
||||
showHidden?: boolean; // Show hidden files (starting with .)
|
||||
exclude?: string[]; // Patterns to exclude
|
||||
urlPrefix?: string; // URL prefix for file links (defaults to current dev server)
|
||||
linkFiles?: boolean; // Whether to make files clickable (default: true)
|
||||
view?: 'tree' | 'thumbs'; // Display mode: tree view or thumbnail grid (default: 'tree')
|
||||
thumbSize?: 'small' | 'medium' | 'large'; // Thumbnail size for thumbs view (default: 'medium')
|
||||
}
|
||||
|
||||
const {
|
||||
glob: globPattern,
|
||||
maxDepth = 5,
|
||||
showHidden = false,
|
||||
exclude = [],
|
||||
urlPrefix,
|
||||
linkFiles = true,
|
||||
view = 'tree',
|
||||
thumbSize = 'medium'
|
||||
} = Astro.props;
|
||||
|
||||
// Default URL prefix to current dev server if not provided
|
||||
const defaultUrlPrefix = `${Astro.url.protocol}//${Astro.url.host}`;
|
||||
const finalUrlPrefix = urlPrefix || defaultUrlPrefix;
|
||||
|
||||
// Get current content directory dynamically from URL (like RelativeGallery)
|
||||
const currentUrl = Astro.url.pathname;
|
||||
const pathSegments = currentUrl.split('/').filter(Boolean);
|
||||
|
||||
let contentSubdir = 'resources'; // fallback
|
||||
if (pathSegments.length >= 1) {
|
||||
contentSubdir = pathSegments[0]; // first segment after domain
|
||||
}
|
||||
|
||||
const contentDir = path.join(process.cwd(), 'src', 'content', contentSubdir);
|
||||
|
||||
let fileTreeHtml = '';
|
||||
let basePath = ''; // Declare basePath in outer scope for error handling
|
||||
let thumbnailFiles: any[] = []; // Store files data for thumbnail view
|
||||
|
||||
// Generate file tree from glob pattern if provided
|
||||
if (globPattern) {
|
||||
try {
|
||||
// Use pathInfoEx like RelativeGallery does
|
||||
const globInfo = globBase(globPattern);
|
||||
const parentDir = globParent(globPattern);
|
||||
basePath = path.join(contentDir, parentDir);
|
||||
|
||||
const pathInfoResult = pathInfo(globPattern, false, basePath);
|
||||
const files = pathInfoResult.FILES || [];
|
||||
|
||||
if (files.length > 0) {
|
||||
if (view === 'thumbs') {
|
||||
// For thumbnail view, we'll use a different approach with Astro components
|
||||
// Store the files data for rendering in the template
|
||||
thumbnailFiles = files.map(filePath => {
|
||||
const relativePath = path.relative(basePath, filePath);
|
||||
const fileName = path.basename(filePath);
|
||||
const fileExt = path.extname(filePath).toLowerCase();
|
||||
const isImage = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp'].includes(fileExt);
|
||||
|
||||
// Generate proper image path for imagetools
|
||||
let imagePath = '';
|
||||
if (isImage) {
|
||||
const relativeFromContentDir = path.relative(contentDir, filePath);
|
||||
imagePath = relativeFromContentDir.startsWith('.')
|
||||
? `/src/content/${contentSubdir}/${relativeFromContentDir.replace(/^\.\//, '')}`
|
||||
: `/src/content/${contentSubdir}/${relativeFromContentDir}`;
|
||||
}
|
||||
|
||||
return {
|
||||
fileName,
|
||||
filePath,
|
||||
relativePath,
|
||||
fileExt,
|
||||
isImage,
|
||||
imagePath: imagePath.replace(/\\/g, '/'),
|
||||
fileUrl: linkFiles ? generateFileUrl(relativePath, basePath, contentDir, finalUrlPrefix) : '',
|
||||
iconName: isImage ? null : getFileIconName(fileName),
|
||||
isMarkdown: ['.md', '.mdx'].includes(fileExt)
|
||||
};
|
||||
});
|
||||
fileTreeHtml = ''; // Will be handled in template
|
||||
} else {
|
||||
// Build tree structure from discovered files
|
||||
const tree = buildFileTreeFromPaths(files, basePath, maxDepth, showHidden, exclude);
|
||||
// Generate HTML directly since we're not going through markdown processing
|
||||
fileTreeHtml = generateFileTreeHTML(tree, basePath, contentDir, finalUrlPrefix, linkFiles);
|
||||
}
|
||||
} else {
|
||||
// No files found - thumbnailFiles will be empty array for thumbnail view
|
||||
if (view === 'thumbs') {
|
||||
thumbnailFiles = [];
|
||||
} else {
|
||||
const searchedPath = basePath.replace(/\\/g, '/');
|
||||
fileTreeHtml = `<ul><li>No files found matching pattern: <code>${globPattern}</code><br/><small>Searched in: <code>${searchedPath}</code></small></li></ul>`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('FileTree glob pattern failed:', error);
|
||||
if (view === 'thumbs') {
|
||||
thumbnailFiles = [];
|
||||
} else {
|
||||
const searchedPath = basePath ? basePath.replace(/\\/g, '/') : 'unknown path';
|
||||
fileTreeHtml = `<ul><li>Error loading files: <code>${error.message}</code><br/><small>Pattern: <code>${globPattern}</code><br/>Searched in: <code>${searchedPath}</code></small></li></ul>`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use slot content (original behavior)
|
||||
fileTreeHtml = await Astro.slots.render('default');
|
||||
}
|
||||
|
||||
// Only process through rehype for tree view, and only if there's content to process
|
||||
let html = '';
|
||||
if (view === 'tree' && fileTreeHtml) {
|
||||
html = processFileTree(fileTreeHtml, 'Directory');
|
||||
} else if (view === 'thumbs' && globPattern && thumbnailFiles.length > 0) {
|
||||
// For thumbnail view, HTML is handled directly in the template
|
||||
html = '';
|
||||
}
|
||||
|
||||
// Helper function to build tree structure from file paths
|
||||
function buildFileTreeFromPaths(filePaths: string[], basePath: string, maxDepth: number, showHidden: boolean, exclude: string[]) {
|
||||
const tree = new Map();
|
||||
|
||||
filePaths.forEach(filePath => {
|
||||
const relativePath = path.relative(basePath, filePath);
|
||||
const parts = relativePath.split(path.sep).filter(Boolean);
|
||||
|
||||
// Skip if exceeds max depth
|
||||
if (parts.length > maxDepth) return;
|
||||
|
||||
// Skip hidden files if not showing them
|
||||
if (!showHidden && parts.some(part => part.startsWith('.'))) return;
|
||||
|
||||
// Skip excluded patterns
|
||||
if (exclude.some(pattern => relativePath.includes(pattern))) return;
|
||||
|
||||
let current = tree;
|
||||
|
||||
parts.forEach((part, index) => {
|
||||
if (!current.has(part)) {
|
||||
current.set(part, {
|
||||
isDirectory: index < parts.length - 1,
|
||||
children: new Map(),
|
||||
fullPath: parts.slice(0, index + 1).join('/'),
|
||||
name: part
|
||||
});
|
||||
}
|
||||
current = current.get(part).children;
|
||||
});
|
||||
});
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Helper function to generate HTML markup from tree structure (for processFileTree)
|
||||
function generateFileTreeHTML(tree: Map<string, any>, basePath: string, contentDir: string, urlPrefix: string, linkFiles: boolean): string {
|
||||
const items = Array.from(tree.entries())
|
||||
.sort(([a, aData], [b, bData]) => {
|
||||
// Directories first, then files, both alphabetically
|
||||
if (aData.isDirectory && !bData.isDirectory) return -1;
|
||||
if (!aData.isDirectory && bData.isDirectory) return 1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
if (items.length === 0) return '<ul></ul>';
|
||||
|
||||
let html = '<ul>';
|
||||
|
||||
items.forEach(([name, data]) => {
|
||||
if (data.isDirectory) {
|
||||
html += `<li>${name}/`;
|
||||
const childrenHtml = generateFileTreeHTML(data.children, basePath, contentDir, urlPrefix, linkFiles);
|
||||
if (childrenHtml !== '<ul></ul>') {
|
||||
html += childrenHtml;
|
||||
}
|
||||
html += '</li>';
|
||||
} else {
|
||||
// Generate clickable link for files
|
||||
if (linkFiles) {
|
||||
const relativePath = data.fullPath;
|
||||
const fileUrl = generateFileUrl(relativePath, basePath, contentDir, urlPrefix);
|
||||
const fileExt = path.extname(name).toLowerCase();
|
||||
const isMarkdown = ['.md', '.mdx'].includes(fileExt);
|
||||
const target = isMarkdown ? '_self' : '_blank';
|
||||
const rel = isMarkdown ? '' : 'noopener noreferrer';
|
||||
html += `<li><a href="${fileUrl}" target="${target}" rel="${rel}">${name}</a></li>`;
|
||||
} else {
|
||||
html += `<li>${name}</li>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Helper functions for file icons (from rehype-file-tree.ts)
|
||||
function getFileIconName(fileName: string): string | undefined {
|
||||
let icon: string | undefined = definitions.files[fileName];
|
||||
if (icon) return icon;
|
||||
icon = getFileIconTypeFromExtension(fileName);
|
||||
if (icon) return icon;
|
||||
for (const [partial, partialIcon] of Object.entries(definitions.partials)) {
|
||||
if (fileName.includes(partial)) return partialIcon;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
function getFileIconTypeFromExtension(fileName: string): string | undefined {
|
||||
const firstDotIndex = fileName.indexOf('.');
|
||||
if (firstDotIndex === -1) return;
|
||||
let extension = fileName.slice(firstDotIndex);
|
||||
while (extension !== '') {
|
||||
const icon = definitions.extensions[extension];
|
||||
if (icon) return icon;
|
||||
const nextDotIndex = extension.indexOf('.', 1);
|
||||
if (nextDotIndex === -1) return;
|
||||
extension = extension.slice(nextDotIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper function to generate file URLs
|
||||
function generateFileUrl(relativePath: string, basePath: string, contentDir: string, urlPrefix: string): string {
|
||||
// Get the actual file path
|
||||
const fullPath = path.join(basePath, relativePath);
|
||||
const fileExt = path.extname(relativePath).toLowerCase();
|
||||
|
||||
// Special handling for markdown/MDX files - link to their rendered Astro pages
|
||||
if ((fileExt === '.md' || fileExt === '.mdx') && urlPrefix.startsWith('http')) {
|
||||
// Get the relative path from the content directory
|
||||
const relativeFromContentDir = path.relative(contentDir, fullPath);
|
||||
// Remove the file extension to get the Astro route
|
||||
const routePath = relativeFromContentDir.replace(/\.(md|mdx)$/, '').replace(/\\/g, '/');
|
||||
// Determine the collection name from contentDir
|
||||
const collectionName = path.basename(contentDir);
|
||||
return `/${collectionName}/${routePath}`;
|
||||
}
|
||||
|
||||
// Handle different URL prefix types for non-markdown files
|
||||
if (urlPrefix.startsWith('vscode://')) {
|
||||
// VS Code protocol - use full system path
|
||||
return `vscode://file/${fullPath.replace(/\\/g, '/')}`;
|
||||
} else if (urlPrefix.includes('github.com') || urlPrefix.includes('gitlab.com')) {
|
||||
// Git repository - use relative path from project root
|
||||
const projectRelativePath = path.relative(process.cwd(), fullPath);
|
||||
return `${urlPrefix}/${projectRelativePath.replace(/\\/g, '/')}`;
|
||||
} else if (urlPrefix.startsWith('file://')) {
|
||||
// File protocol - use full system path
|
||||
return `file:///${fullPath.replace(/\\/g, '/')}`;
|
||||
} else if (urlPrefix.startsWith('http')) {
|
||||
// HTTP/HTTPS - for thumbnails, use the same path logic as images
|
||||
const relativeFromContentDir = path.relative(contentDir, fullPath);
|
||||
const webPath = `/src/content/${path.basename(contentDir)}/${relativeFromContentDir}`;
|
||||
return `${urlPrefix}${webPath.replace(/\\/g, '/')}`;
|
||||
} else {
|
||||
// Default fallback - treat as file:// protocol
|
||||
return `file:///${fullPath.replace(/\\/g, '/')}`;
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
{view === 'thumbs' ? (
|
||||
<div class="file-tree-thumbs not-content" data-pagefind-ignore>
|
||||
<div class={`thumbnail-grid thumb-${thumbSize}`}>
|
||||
{thumbnailFiles.length > 0 ? thumbnailFiles.map((file) => (
|
||||
<div class="thumb-item">
|
||||
{linkFiles && file.fileUrl ? (
|
||||
<a href={file.fileUrl} target={file.isMarkdown ? "_self" : "_blank"} rel={file.isMarkdown ? "" : "noopener noreferrer"}>
|
||||
<div class="thumb-content">
|
||||
{file.isImage ? (
|
||||
<div class="thumb-image">
|
||||
<Img
|
||||
src={file.imagePath}
|
||||
alt={file.fileName}
|
||||
width={thumbSize === 'small' ? 80 : thumbSize === 'large' ? 160 : 120}
|
||||
format="avif"
|
||||
placeholder="blurred"
|
||||
objectFit="cover"
|
||||
loading="lazy"
|
||||
attributes={{
|
||||
img: {
|
||||
class: "thumbnail-img aspect-square rounded",
|
||||
style: "width: 100%; height: 100%; object-fit: cover;"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div class="thumb-icon">
|
||||
<svg class="file-icon" width="48" height="48" viewBox="0 0 24 24" fill="currentColor" set:html={file.iconName && Icons[file.iconName] ? Icons[file.iconName] : Icons['seti:default'] || '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z M14 2v6h6"/>'} />
|
||||
</div>
|
||||
)}
|
||||
<div class="thumb-name">{file.fileName}</div>
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<div class="thumb-content">
|
||||
{file.isImage ? (
|
||||
<div class="thumb-image">
|
||||
<Img
|
||||
src={file.imagePath}
|
||||
alt={file.fileName}
|
||||
width={thumbSize === 'small' ? 80 : thumbSize === 'large' ? 160 : 120}
|
||||
format="avif"
|
||||
placeholder="blurred"
|
||||
objectFit="cover"
|
||||
loading="lazy"
|
||||
attributes={{
|
||||
img: {
|
||||
class: "thumbnail-img aspect-square rounded",
|
||||
style: "width: 100%; height: 100%; object-fit: cover;"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div class="thumb-icon">
|
||||
<svg class="file-icon" width="48" height="48" viewBox="0 0 24 24" fill="currentColor" set:html={file.iconName && Icons[file.iconName] ? Icons[file.iconName] : Icons['seti:default'] || '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z M14 2v6h6"/>'} />
|
||||
</div>
|
||||
)}
|
||||
<div class="thumb-name">{file.fileName}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)) : (
|
||||
<div class="no-files">
|
||||
No files found matching pattern: <code>{globPattern}</code><br/>
|
||||
<small>Searched in: <code>{basePath.replace(/\\/g, '/')}</code></small>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<starlight-file-tree set:html={html} class="not-content" data-pagefind-ignore />
|
||||
)}
|
||||
|
||||
<style>
|
||||
@layer starlight.components {
|
||||
starlight-file-tree {
|
||||
--x-space: 1.5rem;
|
||||
--y-space: 0.125rem;
|
||||
--y-pad: 0;
|
||||
|
||||
display: block;
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
padding: 1rem;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
font-size: var(--sl-text-xs);
|
||||
font-family: var(--__sl-);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Override any global list styling that might interfere */
|
||||
starlight-file-tree :global(ul) {
|
||||
list-style: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(li) {
|
||||
list-style: none !important;
|
||||
}
|
||||
|
||||
/* File link styling - subtle, modern approach following best practices */
|
||||
starlight-file-tree :global(a) {
|
||||
color: var(--sl-color-text);
|
||||
text-decoration: none;
|
||||
transition: all 0.15s ease;
|
||||
border-radius: 3px;
|
||||
padding: 1px 3px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Subtle hover effect with background highlight instead of aggressive blue */
|
||||
starlight-file-tree :global(a:hover) {
|
||||
color: var(--sl-color-text);
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Add subtle underline on hover using ::after for more control */
|
||||
starlight-file-tree :global(a:hover::after) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
height: 1px;
|
||||
background-color: var(--sl-color-gray-2);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(a:visited) {
|
||||
color: var(--sl-color-gray-1);
|
||||
}
|
||||
|
||||
starlight-file-tree :global(a:focus) {
|
||||
outline: 2px solid var(--sl-color-text-accent);
|
||||
outline-offset: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.directory > details) {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
padding-inline-start: var(--x-space);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.directory > details > summary) {
|
||||
margin-inline-start: calc(-1 * var(--x-space));
|
||||
border: 0;
|
||||
padding: var(--y-pad) 0.625rem;
|
||||
font-weight: normal;
|
||||
color: var(--sl-color-white);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.directory > details > summary::marker),
|
||||
starlight-file-tree :global(.directory > details > summary::-webkit-details-marker) {
|
||||
color: var(--sl-color-gray-3);
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.directory > details > summary:hover),
|
||||
starlight-file-tree :global(.directory > details > summary:hover .tree-icon) {
|
||||
cursor: pointer;
|
||||
color: var(--sl-color-text-accent);
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.directory > details > summary:hover ~ ul) {
|
||||
border-color: var(--sl-color-gray-4);
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.directory > details > summary:hover .highlight .tree-icon) {
|
||||
color: var(--sl-color-text-invert);
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(ul) {
|
||||
margin-inline-start: 0.5rem;
|
||||
border-inline-start: 1px solid var(--sl-color-gray-5);
|
||||
padding: 0;
|
||||
padding-inline-start: 0.125rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
starlight-file-tree > :global(ul) {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(li) {
|
||||
margin: var(--y-space) 0;
|
||||
padding: var(--y-pad) 0;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.file) {
|
||||
margin-inline-start: calc(var(--x-space) - 0.125rem);
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
/* Style debug info in tree view too */
|
||||
starlight-file-tree :global(code) {
|
||||
background: var(--sl-color-gray-4);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
starlight-file-tree :global(small) {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
color: var(--sl-color-gray-2);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.tree-entry) {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
max-width: calc(100% - 1rem);
|
||||
}
|
||||
|
||||
@media (min-width: 30em) {
|
||||
starlight-file-tree :global(.tree-entry) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.tree-entry > :first-child) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.empty) {
|
||||
color: var(--sl-color-gray-3);
|
||||
padding-inline-start: 0.375rem;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.comment) {
|
||||
color: var(--sl-color-gray-3);
|
||||
padding-inline-start: 1.625rem;
|
||||
max-width: 24rem;
|
||||
min-width: 12rem;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.highlight) {
|
||||
display: inline-block;
|
||||
border-radius: 0.25rem;
|
||||
padding-inline-end: 0.5rem;
|
||||
color: var(--sl-color-text-invert);
|
||||
background-color: var(--sl-color-text-accent);
|
||||
}
|
||||
|
||||
starlight-file-tree :global(svg) {
|
||||
display: inline;
|
||||
fill: var(--sl-color-gray-3);
|
||||
vertical-align: middle;
|
||||
margin-inline: 0.25rem 0.375rem;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
||||
starlight-file-tree :global(.highlight svg.tree-icon) {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* Thumbnail View Styles */
|
||||
.file-tree-thumbs {
|
||||
--thumb-small: 80px;
|
||||
--thumb-medium: 120px;
|
||||
--thumb-large: 160px;
|
||||
|
||||
display: block;
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
padding: 1rem;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
font-size: var(--sl-text-xs);
|
||||
font-family: var(--__sl-);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumbnail-grid) {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--thumb-medium), 1fr));
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumbnail-grid.thumb-small) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--thumb-small), 1fr));
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumbnail-grid.thumb-large) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--thumb-large), 1fr));
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-item) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--sl-color-gray-4);
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem;
|
||||
background: var(--sl-color-gray-5);
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Subtle clickable indicator */
|
||||
.file-tree-thumbs :global(.thumb-item:has(a)) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-item:has(a)::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--sl-color-text-accent);
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-item:hover::before) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-item:hover) {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Thumbnail link styling - subtle and modern */
|
||||
.file-tree-thumbs :global(.thumb-item a) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-item a:hover) {
|
||||
transform: none; /* Override the item hover transform */
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-item a:focus) {
|
||||
outline: 2px solid var(--sl-color-text-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-image) {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background: var(--sl-color-gray-4);
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-image img),
|
||||
.file-tree-thumbs :global(.thumb-image .thumbnail-img) {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Imagetools specific styling */
|
||||
.file-tree-thumbs :global(.astro-imagetools-img) {
|
||||
border-radius: 4px;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-icon) {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--sl-color-gray-2);
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-icon.thumb-folder) {
|
||||
color: #FFD700; /* Gold color for folders */
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.file-icon) {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-name) {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--sl-color-gray-1);
|
||||
word-break: break-word;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Subtle highlight for thumbnail names on hover */
|
||||
.file-tree-thumbs :global(.thumb-item:hover .thumb-name) {
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.no-files),
|
||||
.file-tree-thumbs :global(.error) {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
color: var(--sl-color-gray-3);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.no-files code),
|
||||
.file-tree-thumbs :global(.error code) {
|
||||
background: var(--sl-color-gray-4);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.no-files small),
|
||||
.file-tree-thumbs :global(.error small) {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
color: var(--sl-color-gray-2);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.file-tree-thumbs :global(.thumbnail-grid) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.file-tree-thumbs :global(.thumb-image),
|
||||
.file-tree-thumbs :global(.thumb-icon) {
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
203
packages/polymech/src/components/Icons.ts
Normal file
203
packages/polymech/src/components/Icons.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { FileIcons } from './file-tree-icons.js';
|
||||
|
||||
export const BuiltInIcons = {
|
||||
'up-caret':
|
||||
'<path d="m17 13.41-4.29-4.24a.999.999 0 0 0-1.42 0l-4.24 4.24a1 1 0 1 0 1.41 1.42L12 11.29l3.54 3.54a1 1 0 0 0 1.41 0 1 1 0 0 0 .05-1.42Z"/>',
|
||||
'down-caret':
|
||||
'<path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/>',
|
||||
'right-caret':
|
||||
'<path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/>',
|
||||
'left-caret':
|
||||
'<path d="m11.29 12 3.54-3.54a1 1 0 0 0 0-1.41 1 1 0 0 0-1.42 0l-4.24 4.24a1 1 0 0 0 0 1.42L13.41 17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29 1 1 0 0 0 0-1.41Z"/>',
|
||||
'up-arrow':
|
||||
'<path d="m17.71 11.29-5-5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-5 5a1 1 0 0 0 1.42 1.42L11 9.41V17a1 1 0 0 0 2 0V9.41l3.29 3.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42Z"/>',
|
||||
'down-arrow':
|
||||
'<path d="M17.71 11.29a1 1 0 0 0-1.42 0L13 14.59V7a1 1 0 0 0-2 0v7.59l-3.29-3.3a1 1 0 0 0-1.42 1.42l5 5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l5-5a1 1 0 0 0 0-1.42Z"/>',
|
||||
'right-arrow':
|
||||
'<path d="M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z"/>',
|
||||
'left-arrow':
|
||||
'<path d="M17 11H9.41l3.3-3.29a1.004 1.004 0 1 0-1.42-1.42l-5 5a1 1 0 0 0-.21.33 1 1 0 0 0 0 .76 1 1 0 0 0 .21.33l5 5a1.002 1.002 0 0 0 1.639-.325 1 1 0 0 0-.219-1.095L9.41 13H17a1 1 0 0 0 0-2Z"/>',
|
||||
bars: '<path d="M3 8h18a1 1 0 1 0 0-2H3a1 1 0 0 0 0 2Zm18 8H3a1 1 0 0 0 0 2h18a1 1 0 0 0 0-2Zm0-5H3a1 1 0 0 0 0 2h18a1 1 0 0 0 0-2Z"/>',
|
||||
translate:
|
||||
'<path fill-rule="evenodd" d="M8.516 3a.94.94 0 0 0-.941.94v1.15H2.94a.94.94 0 1 0 0 1.882h7.362a7.422 7.422 0 0 1-1.787 3.958 7.42 7.42 0 0 1-1.422-2.425.94.94 0 1 0-1.774.627 9.303 9.303 0 0 0 1.785 3.043 7.422 7.422 0 0 1-4.164 1.278.94.94 0 1 0 0 1.881 9.303 9.303 0 0 0 5.575-1.855 9.303 9.303 0 0 0 4.11 1.74l-.763 1.525a.968.968 0 0 0-.016.034l-1.385 2.77a.94.94 0 1 0 1.683.841l1.133-2.267h5.806l1.134 2.267a.94.94 0 0 0 1.683-.841l-1.385-2.769a.95.95 0 0 0-.018-.036l-3.476-6.951a.94.94 0 0 0-1.682 0l-1.82 3.639a7.423 7.423 0 0 1-3.593-1.256 9.303 9.303 0 0 0 2.27-5.203h1.894a.94.94 0 0 0 0-1.881H9.456V3.94A.94.94 0 0 0 8.516 3Zm6.426 11.794a1.068 1.068 0 0 1-.02.039l-.703 1.407h3.924l-1.962-3.924-1.24 2.478Z" clip-rule="evenodd"/>',
|
||||
pencil:
|
||||
'<path d="M22 7.24a1 1 0 0 0-.29-.71l-4.24-4.24a1 1 0 0 0-1.1-.22 1 1 0 0 0-.32.22l-2.83 2.83L2.29 16.05a1 1 0 0 0-.29.71V21a1 1 0 0 0 1 1h4.24a1 1 0 0 0 .76-.29l10.87-10.93L21.71 8c.1-.1.17-.2.22-.33a1 1 0 0 0 0-.24v-.14l.07-.05ZM6.83 20H4v-2.83l9.93-9.93 2.83 2.83L6.83 20ZM18.17 8.66l-2.83-2.83 1.42-1.41 2.82 2.82-1.41 1.42Z"/>',
|
||||
pen: '<path d="M21 12a1 1 0 0 0-1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h6a1 1 0 1 0 0-2H5a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-6a1 1 0 0 0-1-1Zm-15 .76V17a1 1 0 0 0 1 1h4.24a1 1 0 0 0 .71-.29l6.92-6.93L21.71 8a1 1 0 0 0 0-1.42l-4.24-4.29a1 1 0 0 0-1.42 0l-2.82 2.83-6.94 6.93a1 1 0 0 0-.29.71Zm10.76-8.35 2.83 2.83-1.42 1.42-2.83-2.83 1.42-1.42ZM8 13.17l5.93-5.93 2.83 2.83L10.83 16H8v-2.83Z"/>',
|
||||
document:
|
||||
'<path d="M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3.06a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z"/>',
|
||||
'add-document':
|
||||
'<path d="M20 8.94a1.3 1.3 0 0 0-.06-.27v-.09c-.05-.1-.11-.2-.19-.28l-6-6a1.07 1.07 0 0 0-.28-.19h-.09a.88.88 0 0 0-.33-.11H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V8.94Zm-6-3.53L16.59 8H15a1 1 0 0 1-1-1V5.41ZM18 19a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-4-5h-1v-1a1 1 0 0 0-2 0v1h-1a1 1 0 0 0 0 2h1v1a1 1 0 0 0 2 0v-1h1a1 1 0 0 0 0-2Z"/>',
|
||||
setting:
|
||||
'<path d="m21.32 9.55-1.89-.63.89-1.78A1 1 0 0 0 20.13 6L18 3.87a1 1 0 0 0-1.15-.19l-1.78.89-.63-1.89A1 1 0 0 0 13.5 2h-3a1 1 0 0 0-.95.68l-.63 1.89-1.78-.89A1 1 0 0 0 6 3.87L3.87 6a1 1 0 0 0-.19 1.15l.89 1.78-1.89.63a1 1 0 0 0-.68.94v3a1 1 0 0 0 .68.95l1.89.63-.89 1.78A1 1 0 0 0 3.87 18L6 20.13a1 1 0 0 0 1.15.19l1.78-.89.63 1.89a1 1 0 0 0 .95.68h3a1 1 0 0 0 .95-.68l.63-1.89 1.78.89a1 1 0 0 0 1.13-.19L20.13 18a1 1 0 0 0 .19-1.15l-.89-1.78 1.89-.63a1 1 0 0 0 .68-.94v-3a1 1 0 0 0-.68-.95ZM20 12.78l-1.2.4A2 2 0 0 0 17.64 16l.57 1.14-1.1 1.1-1.11-.6a2 2 0 0 0-2.79 1.16l-.4 1.2h-1.59l-.4-1.2A2 2 0 0 0 8 17.64l-1.14.57-1.1-1.1.6-1.11a2 2 0 0 0-1.16-2.82l-1.2-.4v-1.56l1.2-.4A2 2 0 0 0 6.36 8l-.57-1.11 1.1-1.1L8 6.36a2 2 0 0 0 2.82-1.16l.4-1.2h1.56l.4 1.2A2 2 0 0 0 16 6.36l1.14-.57 1.1 1.1-.6 1.11a2 2 0 0 0 1.16 2.79l1.2.4v1.59ZM12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4Z"/>',
|
||||
external:
|
||||
'<path d="M19.33 10.18a1 1 0 0 1-.77 0 1 1 0 0 1-.62-.93l.01-1.83-8.2 8.2a1 1 0 0 1-1.41-1.42l8.2-8.2H14.7a1 1 0 0 1 0-2h4.25a1 1 0 0 1 1 1v4.25a1 1 0 0 1-.62.93Z"/><path d="M11 4a1 1 0 1 1 0 2H7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-4a1 1 0 1 1 2 0v4a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3h4Z"/>',
|
||||
download:
|
||||
'<path d="M8.29 13.29a1 1 0 0 0 0 1.42l3 3a1 1 0 0 0 1.42 0l3-3a1 1 0 0 0-1.42-1.42L13 14.59V3a1 1 0 0 0-2 0v11.59l-1.29-1.3a1 1 0 0 0-1.42 0ZM18 9h-2a1 1 0 0 0 0 2h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1h2a1 1 0 0 0 0-2H6a3 3 0 0 0-3 3v7a3 3 0 0 0 3 3h12a3 3 0 0 0 3-3v-7a3 3 0 0 0-3-3Z"/>',
|
||||
'cloud-download':
|
||||
'<path d="M14.29 17.29 13 18.59V13a1 1 0 0 0-2 0v5.59l-1.29-1.3a1 1 0 0 0-1.42 1.42l3 3a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l3-3a1 1 0 0 0-1.42-1.42Zm4.13-11.07A7 7 0 0 0 5.06 8.11 4 4 0 0 0 6 16a1 1 0 0 0 0-2 2 2 0 0 1 0-4 1 1 0 0 0 1-1 5 5 0 0 1 9.73-1.61 1 1 0 0 0 .78.67 3 3 0 0 1 .24 5.84 1 1 0 1 0 .5 1.94 5 5 0 0 0 .17-9.62Z"/>',
|
||||
moon: '<path d="M21.64 13a1 1 0 0 0-1.05-.14 8.049 8.049 0 0 1-3.37.73 8.15 8.15 0 0 1-8.14-8.1 8.59 8.59 0 0 1 .25-2A1 1 0 0 0 8 2.36a10.14 10.14 0 1 0 14 11.69 1 1 0 0 0-.36-1.05Zm-9.5 6.69A8.14 8.14 0 0 1 7.08 5.22v.27a10.15 10.15 0 0 0 10.14 10.14 9.784 9.784 0 0 0 2.1-.22 8.11 8.11 0 0 1-7.18 4.32v-.04Z"/>',
|
||||
sun: '<path d="M5 12a1 1 0 0 0-1-1H3a1 1 0 0 0 0 2h1a1 1 0 0 0 1-1Zm.64 5-.71.71a1 1 0 0 0 0 1.41 1 1 0 0 0 1.41 0l.71-.71A1 1 0 0 0 5.64 17ZM12 5a1 1 0 0 0 1-1V3a1 1 0 0 0-2 0v1a1 1 0 0 0 1 1Zm5.66 2.34a1 1 0 0 0 .7-.29l.71-.71a1 1 0 1 0-1.41-1.41l-.66.71a1 1 0 0 0 0 1.41 1 1 0 0 0 .66.29Zm-12-.29a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-.71-.71a1.004 1.004 0 1 0-1.43 1.41l.73.71ZM21 11h-1a1 1 0 0 0 0 2h1a1 1 0 0 0 0-2Zm-2.64 6A1 1 0 0 0 17 18.36l.71.71a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-.76-.66ZM12 6.5a5.5 5.5 0 1 0 5.5 5.5A5.51 5.51 0 0 0 12 6.5Zm0 9a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7Zm0 3.5a1 1 0 0 0-1 1v1a1 1 0 0 0 2 0v-1a1 1 0 0 0-1-1Z"/>',
|
||||
laptop:
|
||||
'<path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/>',
|
||||
'open-book':
|
||||
'<path d="M21.17 2.06A13.1 13.1 0 0 0 19 1.87a12.94 12.94 0 0 0-7 2.05 12.94 12.94 0 0 0-7-2 13.1 13.1 0 0 0-2.17.19 1 1 0 0 0-.83 1v12a1 1 0 0 0 1.17 1 10.9 10.9 0 0 1 8.25 1.91l.12.07h.11a.91.91 0 0 0 .7 0h.11l.12-.07A10.899 10.899 0 0 1 20.83 16 1 1 0 0 0 22 15V3a1 1 0 0 0-.83-.94ZM11 15.35a12.87 12.87 0 0 0-6-1.48H4v-10c.333-.02.667-.02 1 0a10.86 10.86 0 0 1 6 1.8v9.68Zm9-1.44h-1a12.87 12.87 0 0 0-6 1.48V5.67a10.86 10.86 0 0 1 6-1.8c.333-.02.667-.02 1 0v10.04Zm1.17 4.15a13.098 13.098 0 0 0-2.17-.19 12.94 12.94 0 0 0-7 2.05 12.94 12.94 0 0 0-7-2.05c-.727.003-1.453.066-2.17.19A1 1 0 0 0 2 19.21a1 1 0 0 0 1.17.79 10.9 10.9 0 0 1 8.25 1.91 1 1 0 0 0 1.16 0A10.9 10.9 0 0 1 20.83 20a1 1 0 0 0 1.17-.79 1 1 0 0 0-.83-1.15Z"/>',
|
||||
information:
|
||||
'<path d="M12 11a1 1 0 0 0-1 1v4a1 1 0 0 0 2 0v-4a1 1 0 0 0-1-1Zm.38-3.92a1 1 0 0 0-.76 0 1 1 0 0 0-.33.21 1.15 1.15 0 0 0-.21.33 1 1 0 0 0 .21 1.09c.097.088.209.16.33.21A1 1 0 0 0 13 8a1.05 1.05 0 0 0-.29-.71 1 1 0 0 0-.33-.21ZM12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm0 18a8 8 0 1 1 0-16.001A8 8 0 0 1 12 20Z"/>',
|
||||
magnifier:
|
||||
'<path d="M21.71 20.29 18 16.61A9 9 0 1 0 16.61 18l3.68 3.68a.999.999 0 0 0 1.42 0 1 1 0 0 0 0-1.39ZM11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14Z"/>',
|
||||
'forward-slash':
|
||||
'<path d="M17 2H7a5 5 0 0 0-5 5v10a5 5 0 0 0 5 5h10a5 5 0 0 0 5-5V7a5 5 0 0 0-5-5Zm3 15a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v10Z"/><path d="M15.293 6.707a1 1 0 1 1 1.414 1.414l-8.485 8.486a1 1 0 0 1-1.414-1.415l8.485-8.485Z"/>',
|
||||
close:
|
||||
'<path d="m13.41 12 6.3-6.29a1.004 1.004 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1.004 1.004 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 0 1.42.998.998 0 0 0 1.42 0l6.29-6.3 6.29 6.3a.999.999 0 0 0 1.42 0 1 1 0 0 0 0-1.42L13.41 12Z"/>',
|
||||
error:
|
||||
'<path d="M12 7a1 1 0 0 0-1 1v4a1 1 0 0 0 2 0V8a1 1 0 0 0-1-1Zm0 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm9.71-7.44-5.27-5.27a1.05 1.05 0 0 0-.71-.29H8.27a1.05 1.05 0 0 0-.71.29L2.29 7.56a1.05 1.05 0 0 0-.29.71v7.46c.004.265.107.518.29.71l5.27 5.27c.192.183.445.286.71.29h7.46a1.05 1.05 0 0 0 .71-.29l5.27-5.27a1.05 1.05 0 0 0 .29-.71V8.27a1.05 1.05 0 0 0-.29-.71ZM20 15.31 15.31 20H8.69L4 15.31V8.69L8.69 4h6.62L20 8.69v6.62Z"/>',
|
||||
warning:
|
||||
'<path d="M12 16a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm10.67 1.47-8.05-14a3 3 0 0 0-5.24 0l-8 14A3 3 0 0 0 3.94 22h16.12a3 3 0 0 0 2.61-4.53Zm-1.73 2a1 1 0 0 1-.88.51H3.94a1 1 0 0 1-.88-.51 1 1 0 0 1 0-1l8-14a1 1 0 0 1 1.78 0l8.05 14a1 1 0 0 1 .05 1.02v-.02ZM12 8a1 1 0 0 0-1 1v4a1 1 0 0 0 2 0V9a1 1 0 0 0-1-1Z"/>',
|
||||
'approve-check-circle':
|
||||
'<path d="m14.72 8.79-4.29 4.3-1.65-1.65a1 1 0 1 0-1.41 1.41l2.35 2.36a1 1 0 0 0 1.41 0l5-5a1.002 1.002 0 1 0-1.41-1.42ZM12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm0 18a8 8 0 1 1 0-16.001A8 8 0 0 1 12 20Z"/>',
|
||||
'approve-check':
|
||||
'<path d="M18.71 7.21a1 1 0 0 0-1.42 0l-7.45 7.46-3.13-3.14A1.02 1.02 0 1 0 5.29 13l3.84 3.84a1.001 1.001 0 0 0 1.42 0l8.16-8.16a1 1 0 0 0 0-1.47Z"/>',
|
||||
rocket:
|
||||
'<path fill-rule="evenodd" d="M1.44 8.855v-.001l3.527-3.516c.34-.344.802-.541 1.285-.548h6.649l.947-.947c3.07-3.07 6.207-3.072 7.62-2.868a1.821 1.821 0 0 1 1.557 1.557c.204 1.413.203 4.55-2.868 7.62l-.946.946v6.649a1.845 1.845 0 0 1-.549 1.286l-3.516 3.528a1.844 1.844 0 0 1-3.11-.944l-.858-4.275-4.52-4.52-2.31-.463-1.964-.394A1.847 1.847 0 0 1 .98 10.693a1.843 1.843 0 0 1 .46-1.838Zm5.379 2.017-3.873-.776L6.32 6.733h4.638l-4.14 4.14Zm8.403-5.655c2.459-2.46 4.856-2.463 5.89-2.33.134 1.035.13 3.432-2.329 5.891l-6.71 6.71-3.561-3.56 6.71-6.711Zm-1.318 15.837-.776-3.873 4.14-4.14v4.639l-3.364 3.374Z" clip-rule="evenodd"/><path d="M9.318 18.345a.972.972 0 0 0-1.86-.561c-.482 1.435-1.687 2.204-2.934 2.619a8.22 8.22 0 0 1-1.23.302c.062-.365.157-.79.303-1.229.415-1.247 1.184-2.452 2.62-2.935a.971.971 0 1 0-.62-1.842c-.12.04-.236.084-.35.13-2.02.828-3.012 2.588-3.493 4.033a10.383 10.383 0 0 0-.51 2.845l-.001.016v.063c0 .536.434.972.97.972H2.24a7.21 7.21 0 0 0 .878-.065c.527-.063 1.248-.19 2.02-.447 1.445-.48 3.205-1.472 4.033-3.494a5.828 5.828 0 0 0 .147-.407Z"/>',
|
||||
star: '<path d="M22 9.67a1 1 0 0 0-.86-.67l-5.69-.83L12.9 3a1 1 0 0 0-1.8 0L8.55 8.16 2.86 9a1 1 0 0 0-.81.68 1 1 0 0 0 .25 1l4.13 4-1 5.68a1 1 0 0 0 1.45 1.07L12 18.76l5.1 2.68c.14.08.3.12.46.12a1 1 0 0 0 .99-1.19l-1-5.68 4.13-4A1 1 0 0 0 22 9.67Zm-6.15 4a1 1 0 0 0-.29.89l.72 4.19-3.76-2a1 1 0 0 0-.94 0l-3.76 2 .72-4.19a1 1 0 0 0-.29-.89l-3-3 4.21-.61a1 1 0 0 0 .76-.55L12 5.7l1.88 3.82a1 1 0 0 0 .76.55l4.21.61-3 2.99Z"/>',
|
||||
puzzle:
|
||||
'<path d="M17 22H5a3 3 0 0 1-3-3V9a3 3 0 0 1 3-3h1a4 4 0 0 1 7.3-2.18c.448.64.692 1.4.7 2.18h3a1 1 0 0 1 1 1v3a4 4 0 0 1 2.18 7.3A3.86 3.86 0 0 1 18 18v3a1 1 0 0 1-1 1ZM5 8a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11v-3.18a1 1 0 0 1 1.33-.95 1.77 1.77 0 0 0 1.74-.23 2 2 0 0 0 .93-1.37 2 2 0 0 0-.48-1.59 1.89 1.89 0 0 0-2.17-.55 1 1 0 0 1-1.33-.95V8h-3.2a1 1 0 0 1-1-1.33 1.77 1.77 0 0 0-.23-1.74 1.939 1.939 0 0 0-3-.43A2 2 0 0 0 8 6c.002.23.046.456.13.67A1 1 0 0 1 7.18 8H5Z"/>',
|
||||
'list-format':
|
||||
'<path d="M3.71 16.29a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21 1 1 0 0 0-.21.33 1 1 0 0 0 .21 1.09c.097.088.209.16.33.21a.94.94 0 0 0 .76 0 1.15 1.15 0 0 0 .33-.21 1 1 0 0 0 .21-1.09 1 1 0 0 0-.21-.33ZM7 8h14a1 1 0 1 0 0-2H7a1 1 0 0 0 0 2Zm-3.29 3.29a1 1 0 0 0-1.09-.21 1.15 1.15 0 0 0-.33.21 1 1 0 0 0-.21.33.94.94 0 0 0 0 .76c.05.121.122.233.21.33.097.088.209.16.33.21a.94.94 0 0 0 .76 0 1.15 1.15 0 0 0 .33-.21 1.15 1.15 0 0 0 .21-.33.94.94 0 0 0 0-.76 1 1 0 0 0-.21-.33ZM21 11H7a1 1 0 0 0 0 2h14a1 1 0 0 0 0-2ZM3.71 6.29a1 1 0 0 0-.33-.21 1 1 0 0 0-1.09.21 1.15 1.15 0 0 0-.21.33.94.94 0 0 0 0 .76c.05.121.122.233.21.33.097.088.209.16.33.21a1 1 0 0 0 1.09-.21 1.15 1.15 0 0 0 .21-.33.94.94 0 0 0 0-.76 1.15 1.15 0 0 0-.21-.33ZM21 16H7a1 1 0 0 0 0 2h14a1 1 0 0 0 0-2Z"/>',
|
||||
random:
|
||||
'<path d="M8.7 10a1 1 0 0 0 1.41 0 1 1 0 0 0 0-1.41l-6.27-6.3a1 1 0 0 0-1.42 1.42ZM21 14a1 1 0 0 0-1 1v3.59L15.44 14A1 1 0 0 0 14 15.44L18.59 20H15a1 1 0 0 0 0 2h6a1 1 0 0 0 .38-.08 1 1 0 0 0 .54-.54A1 1 0 0 0 22 21v-6a1 1 0 0 0-1-1Zm.92-11.38a1 1 0 0 0-.54-.54A1 1 0 0 0 21 2h-6a1 1 0 0 0 0 2h3.59L2.29 20.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0L20 5.41V9a1 1 0 0 0 2 0V3a1 1 0 0 0-.08-.38Z"/>',
|
||||
comment:
|
||||
'<path d="M12 2A10 10 0 0 0 2 12a9.9 9.9 0 0 0 2.3 6.3l-2 2a1 1 0 0 0-.3 1.1 1 1 0 0 0 1 .6h9a10 10 0 0 0 0-20m0 18H5.4l1-1a1 1 0 0 0 0-1.3A8 8 0 1 1 12 20"/>',
|
||||
'comment-alt':
|
||||
'<path d="M19 2H5a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h11.6l3.7 3.7a1 1 0 0 0 .7.3.8.8 0 0 0 .4 0 1 1 0 0 0 .6-1V5a3 3 0 0 0-3-3m1 16.6-2.3-2.3a1 1 0 0 0-.7-.3H5a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1Z"/>',
|
||||
heart:
|
||||
'<path d="M20.16 5A6.29 6.29 0 0 0 12 4.36a6.27 6.27 0 0 0-8.16 9.48l6.21 6.22a2.78 2.78 0 0 0 3.9 0l6.21-6.22a6.27 6.27 0 0 0 0-8.84m-1.41 7.46-6.21 6.21a.76.76 0 0 1-1.08 0l-6.21-6.24a4.29 4.29 0 0 1 0-6 4.27 4.27 0 0 1 6 0 1 1 0 0 0 1.42 0 4.27 4.27 0 0 1 6 0 4.29 4.29 0 0 1 .08 6Z"/>',
|
||||
github:
|
||||
'<path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.08 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"/>',
|
||||
gitlab:
|
||||
'<path d="m22.63 9.8-.03-.09-3-7.81a.78.78 0 0 0-.76-.5.8.8 0 0 0-.46.18.8.8 0 0 0-.26.4L16.1 8.17H7.9l-2-6.19a.79.79 0 0 0-1.5-.08l-3 7.81-.02.08a5.56 5.56 0 0 0 1.84 6.43h.01l.03.02 4.56 3.42 2.26 1.7 1.37 1.05a.92.92 0 0 0 1.12 0l1.38-1.04 2.25-1.71 4.6-3.44a5.56 5.56 0 0 0 1.84-6.43Z"/>',
|
||||
bitbucket:
|
||||
'<path d="M1 1.5a.8.8 0 0 0-.7.9l3.2 19.3c0 .5.5.8 1 .8h15.2c.4 0 .7-.2.8-.6l3.2-19.5a.7.7 0 0 0-.8-.9H1zm13.4 14H9.6l-1.3-7h7.3l-1.2 7z"/>',
|
||||
codePen:
|
||||
'<path d="M23.5 7.5 12.5.2a1 1 0 0 0-1 0L.4 7.5a1 1 0 0 0-.5.8v7.4c0 .3.2.6.5.8l11 7.3c.3.3.7.3 1 0l11-7.3c.3-.2.5-.5.5-.8V8.3a1 1 0 0 0-.5-.8zM13 3l8.1 5.3-3.6 2.5-4.5-3V3zm-2 0v4.8l-4.5 3-3.6-2.5 8-5.3zm-9 7.3L4.7 12l-2.5 1.7v-3.4zM11 21l-8.1-5.3 3.6-2.5 4.5 3V21zm1-6.6L8.4 12 12 9.6l3.6 2.4-3.6 2.4zm1 6.6v-4.8l4.5-3 3.6 2.5-8 5.3zm9-7.3L19.3 12l2.5-1.7v3.4z"/>',
|
||||
farcaster:
|
||||
'<path d="M6.187 3.733h11.627v16.533h-1.707v-7.573h-.017a4.107 4.107 0 0 0-8.18 0h-.017v7.573H6.186z"/><path d="m3.093 6.08.693 2.347h.587v9.493a.533.533 0 0 0-.533.533v.64h-.107a.533.533 0 0 0-.533.533v.64h5.973v-.64a.533.533 0 0 0-.533-.533h-.107v-.64A.533.533 0 0 0 8 17.92h-.64V6.08zM16.213 17.92a.533.533 0 0 0-.533.533v.64h-.107a.533.533 0 0 0-.533.533v.64h5.973v-.64a.533.533 0 0 0-.533-.533h-.107v-.64a.533.533 0 0 0-.533-.533V8.427h.587l.693-2.347h-4.267v11.84z"/>',
|
||||
discord:
|
||||
'<path d="M20.32 4.37a19.8 19.8 0 0 0-4.93-1.51 13.78 13.78 0 0 0-.64 1.28 18.27 18.27 0 0 0-5.5 0 12.64 12.64 0 0 0-.64-1.28h-.05A19.74 19.74 0 0 0 3.64 4.4 20.26 20.26 0 0 0 .11 18.09l.02.02a19.9 19.9 0 0 0 6.04 3.03l.04-.02a14.24 14.24 0 0 0 1.23-2.03.08.08 0 0 0-.05-.07 13.1 13.1 0 0 1-1.9-.92.08.08 0 0 1 .02-.1 10.2 10.2 0 0 0 .41-.31h.04a14.2 14.2 0 0 0 12.1 0l.04.01a9.63 9.63 0 0 0 .4.32.08.08 0 0 1-.03.1 12.29 12.29 0 0 1-1.9.91.08.08 0 0 0-.02.1 15.97 15.97 0 0 0 1.27 2.01h.04a19.84 19.84 0 0 0 6.03-3.05v-.03a20.12 20.12 0 0 0-3.57-13.69ZM8.02 15.33c-1.18 0-2.16-1.08-2.16-2.42 0-1.33.96-2.42 2.16-2.42 1.21 0 2.18 1.1 2.16 2.42 0 1.34-.96 2.42-2.16 2.42Zm7.97 0c-1.18 0-2.15-1.08-2.15-2.42 0-1.33.95-2.42 2.15-2.42 1.22 0 2.18 1.1 2.16 2.42 0 1.34-.94 2.42-2.16 2.42Z"/>',
|
||||
gitter:
|
||||
'<path d="M6.11 15.12H3.75V0h2.36v15.12zm4.71-11.55H8.46V24h2.36V3.57zm4.72 0h-2.36V24h2.36V3.57zm4.71 0h-2.36v11.57h2.36V3.56z"/>',
|
||||
twitter:
|
||||
'<path d="M24 4.4a10 10 0 0 1-2.83.78 5.05 5.05 0 0 0 2.17-2.79 9.7 9.7 0 0 1-3.13 1.23 4.89 4.89 0 0 0-5.94-1.03 5 5 0 0 0-2.17 2.38 5.15 5.15 0 0 0-.3 3.25c-1.95-.1-3.86-.63-5.61-1.53a14.04 14.04 0 0 1-4.52-3.74 5.2 5.2 0 0 0-.09 4.91c.39.74.94 1.35 1.61 1.82a4.77 4.77 0 0 1-2.23-.63v.06c0 1.16.4 2.29 1.12 3.18a4.9 4.9 0 0 0 2.84 1.74c-.73.22-1.5.26-2.24.12a4.89 4.89 0 0 0 4.59 3.49A9.78 9.78 0 0 1 0 19.73 13.65 13.65 0 0 0 7.55 22a13.63 13.63 0 0 0 9.96-4.16A14.26 14.26 0 0 0 21.6 7.65V7c.94-.72 1.75-1.6 2.4-2.6Z"/>',
|
||||
'x.com':
|
||||
'<path d="M 18.242188 2.25 L 21.554688 2.25 L 14.324219 10.507812 L 22.828125 21.75 L 16.171875 21.75 L 10.953125 14.933594 L 4.992188 21.75 L 1.679688 21.75 L 9.40625 12.914062 L 1.257812 2.25 L 8.082031 2.25 L 12.792969 8.480469 Z M 17.082031 19.773438 L 18.914062 19.773438 L 7.082031 4.125 L 5.113281 4.125 Z M 17.082031 19.773438 "/>',
|
||||
mastodon:
|
||||
'<path d="M16.45 17.77c2.77-.33 5.18-2.03 5.49-3.58.47-2.45.44-5.97.44-5.97 0-4.77-3.15-6.17-3.15-6.17-1.58-.72-4.3-1.03-7.13-1.05h-.07c-2.83.02-5.55.33-7.13 1.05 0 0-3.14 1.4-3.14 6.17v.91c-.01.88-.02 1.86 0 2.88.12 4.67.87 9.27 5.2 10.4 2 .53 3.72.64 5.1.57 2.51-.14 3.92-.9 3.92-.9l-.08-1.8s-1.8.56-3.8.5c-2-.08-4.1-.22-4.43-2.66a4.97 4.97 0 0 1-.04-.68s1.96.48 4.44.59c1.51.07 2.94-.09 4.38-.26Zm2.22-3.4h-2.3v-5.6c0-1.19-.5-1.79-1.5-1.79-1.1 0-1.66.71-1.66 2.12v3.07h-2.3V9.1c0-1.4-.55-2.12-1.65-2.12-1 0-1.5.6-1.5 1.78v5.61h-2.3V8.6c0-1.18.3-2.12.9-2.81a3.17 3.17 0 0 1 2.47-1.05c1.18 0 2.07.45 2.66 1.35l.57.96.58-.96a2.97 2.97 0 0 1 2.66-1.35c1.01 0 1.83.36 2.46 1.05.6.7.9 1.63.9 2.81v5.78Z"/>',
|
||||
codeberg:
|
||||
'<path d="M12 .5a12 12 0 0 0-12 12 12 12 0 0 0 1.8 6.4l10-13a.2.1 0 0 1 .4 0l10 13a12 12 0 0 0 1.8-6.4 12 12 0 0 0-12-12zm.3 6.5 4.4 16.5a12 12 0 0 0 5.2-4.2z"/>',
|
||||
youtube:
|
||||
'<path d="M23.5 6.2A3 3 0 0 0 21.4 4c-1.9-.5-9.4-.5-9.4-.5s-7.5 0-9.4.5A3 3 0 0 0 .5 6.3C0 8 0 12 0 12s0 4 .5 5.8A3 3 0 0 0 2.6 20c1.9.6 9.4.6 9.4.6s7.5 0 9.4-.6a3 3 0 0 0 2.1-2c.5-2 .5-5.9.5-5.9s0-4-.5-5.8zm-14 9.4V8.4l6.3 3.6-6.3 3.6z"/>',
|
||||
threads:
|
||||
'<path d="m17.73 11.2-.29-.13c-.17-3.13-1.88-4.92-4.75-4.94h-.04c-1.72 0-3.14.73-4.02 2.06l1.58 1.09a2.8 2.8 0 0 1 2.47-1.21c.94 0 1.66.28 2.12.81.33.4.56.93.67 1.61-.84-.14-1.74-.18-2.71-.13-2.73.16-4.49 1.75-4.37 3.97a3.41 3.41 0 0 0 1.57 2.71c.81.54 1.85.8 2.93.74a4.32 4.32 0 0 0 3.33-1.62 6 6 0 0 0 1.14-2.97 3.5 3.5 0 0 1 1.46 1.6 4 4 0 0 1-.98 4.4c-1.3 1.3-2.86 1.85-5.21 1.87-2.62-.02-4.6-.86-5.88-2.5-1.2-1.52-1.82-3.73-1.85-6.56.03-2.83.65-5.04 1.85-6.57 1.29-1.63 3.26-2.47 5.88-2.49 2.63.02 4.64.86 5.97 2.5.66.8 1.15 1.82 1.48 3l1.85-.5c-.4-1.44-1.02-2.7-1.86-3.73-1.71-2.1-4.21-3.19-7.44-3.21h-.01c-3.22.02-5.7 1.1-7.35 3.22C3.79 6.1 3.03 8.72 3 11.99V12c.03 3.29.79 5.9 2.27 7.78 1.66 2.12 4.13 3.2 7.35 3.22h.01c2.86-.02 4.88-.77 6.54-2.43a5.95 5.95 0 0 0 1.4-6.56 5.62 5.62 0 0 0-2.84-2.81Zm-4.94 4.64c-1.2.07-2.44-.47-2.5-1.62-.05-.85.6-1.8 2.57-1.92l.67-.02c.71 0 1.38.07 1.99.2-.23 2.84-1.56 3.3-2.73 3.36Z"/>',
|
||||
linkedin:
|
||||
'<path d="M20.47 2H3.53a1.45 1.45 0 0 0-1.47 1.43v17.14A1.45 1.45 0 0 0 3.53 22h16.94a1.45 1.45 0 0 0 1.47-1.43V3.43A1.45 1.45 0 0 0 20.47 2ZM8.09 18.74h-3v-9h3v9ZM6.59 8.48a1.56 1.56 0 0 1 0-3.12 1.57 1.57 0 1 1 0 3.12Zm12.32 10.26h-3v-4.83c0-1.21-.43-2-1.52-2A1.65 1.65 0 0 0 12.85 13a2 2 0 0 0-.1.73v5h-3v-9h3V11a3 3 0 0 1 2.71-1.5c2 0 3.45 1.29 3.45 4.06v5.18Z"/>',
|
||||
twitch:
|
||||
'<path d="M2.5 1 1 4.8v15.4h5.5V23h3.1l3-2.8H17l6-5.7V1H2.6ZM21 13.5l-3.4 3.3H12l-3 2.8v-2.8H4.5V3H21v10.5Zm-3.4-6.8v5.8h-2V6.7h2Zm-5.5 0v5.8h-2V6.7h2Z"/>',
|
||||
azureDevOps:
|
||||
'<path d="M17,4v9.74l-4,3.28-6.2-2.26V17L3.29,12.41l10.23.8V4.44Zm-3.41.49L7.85,1V3.29L2.58,4.84,1,6.87v4.61l2.26,1V6.57Z"/>',
|
||||
microsoftTeams:
|
||||
'<path d="M13.78 7.2a3.63 3.63 0 1 0-4.3-3.68h1.78a2.52 2.52 0 0 1 2.52 2.53V7.2zM7.34 18.8h3.92a2.52 2.52 0 0 0 2.52-2.52V8.37h4.17c.58.01 1.04.5 1.03 1.07v6.45a6.3 6.3 0 0 1-6.14 6.43 6.3 6.3 0 0 1-5.5-3.52zm16.1-14.06a2.51 2.51 0 1 1-5.02 0 2.51 2.51 0 0 1 5.02 0zm-3.36 14.24h-.17c.4-1 .59-2.05.57-3.11V9.46c0-.38-.07-.75-.23-1.09h2.69c.58 0 1.06.48 1.06 1.06v5.65a3.9 3.9 0 0 1-3.9 3.9h-.02z"/><path d="M1.02 5.02h10.24c.56 0 1.02.46 1.02 1.03v10.23a1.02 1.02 0 0 1-1.02 1.02H1.02A1.02 1.02 0 0 1 0 16.28V6.04c0-.56.46-1.02 1.02-1.02zm7.81 3.9V7.84H3.45v1.08h2.03v5.57h1.3V8.92h2.05z"/>',
|
||||
instagram:
|
||||
'<path d="M17.3 5.5a1.2 1.2 0 1 0 1.2 1.2 1.2 1.2 0 0 0-1.2-1.2ZM22 7.9a7.6 7.6 0 0 0-.4-2.5 5 5 0 0 0-1.2-1.7 4.7 4.7 0 0 0-1.8-1.2 7.3 7.3 0 0 0-2.4-.4L12 2H7.9a7.3 7.3 0 0 0-2.5.5 4.8 4.8 0 0 0-1.7 1.2 4.7 4.7 0 0 0-1.2 1.8 7.3 7.3 0 0 0-.4 2.4L2 12v4.1a7.3 7.3 0 0 0 .5 2.4 4.7 4.7 0 0 0 1.2 1.8 4.8 4.8 0 0 0 1.8 1.2 7.3 7.3 0 0 0 2.4.4l4.1.1h4.1a7.3 7.3 0 0 0 2.4-.5 4.7 4.7 0 0 0 1.8-1.2 4.8 4.8 0 0 0 1.2-1.7 7.6 7.6 0 0 0 .4-2.5L22 12V7.9ZM20.1 16a5.6 5.6 0 0 1-.3 1.9A3 3 0 0 1 19 19a3.2 3.2 0 0 1-1.1.8 5.6 5.6 0 0 1-1.9.3H8a5.7 5.7 0 0 1-1.9-.3A3.3 3.3 0 0 1 5 19a3 3 0 0 1-.7-1.1 5.5 5.5 0 0 1-.4-1.9l-.1-4V8a5.5 5.5 0 0 1 .4-1.9A3 3 0 0 1 5 5a3.1 3.1 0 0 1 1.1-.8A5.7 5.7 0 0 1 8 3.9l4-.1h4a5.6 5.6 0 0 1 1.9.4A3 3 0 0 1 19 5a3 3 0 0 1 .7 1.1A5.6 5.6 0 0 1 20 8l.1 4v4ZM12 6.9a5.1 5.1 0 1 0 5.1 5.1A5.1 5.1 0 0 0 12 6.9Zm0 8.4a3.3 3.3 0 1 1 3.3-3.3 3.3 3.3 0 0 1-3.3 3.3Z"/>',
|
||||
stackOverflow:
|
||||
'<path d="M15.72 0 14 1.28l6.4 8.58 1.7-1.26L15.73 0zm-3.94 3.42-1.36 1.64 8.22 6.85 1.37-1.64-8.23-6.85zM8.64 7.88l-.91 1.94 9.7 4.52.9-1.94-9.7-4.52zm-1.86 4.86-.44 2.1 10.48 2.2.44-2.1-10.47-2.2zM1.9 15.47V24h19.19v-8.53h-2.13v6.4H4.02v-6.4H1.9zm4.26 2.13v2.13h10.66V17.6H6.15Z"/>',
|
||||
telegram:
|
||||
'<path d="M22.265 2.428a2.048 2.048 0 0 0-2.078-.324L2.266 9.339a2.043 2.043 0 0 0 .104 3.818l3.625 1.261 2.02 6.682a.998.998 0 0 0 .119.252c.008.012.019.02.027.033a.988.988 0 0 0 .211.215.972.972 0 0 0 .07.05.986.986 0 0 0 .31.136l.013.001.006.003a1.022 1.022 0 0 0 .203.02l.018-.003a.993.993 0 0 0 .301-.052c.023-.008.042-.02.064-.03a.993.993 0 0 0 .205-.114 250.76 250.76 0 0 1 .152-.129l2.702-2.983 4.03 3.122a2.023 2.023 0 0 0 1.241.427 2.054 2.054 0 0 0 2.008-1.633l3.263-16.017a2.03 2.03 0 0 0-.693-1.97ZM9.37 14.736a.994.994 0 0 0-.272.506l-.31 1.504-.784-2.593 4.065-2.117Zm8.302 5.304-4.763-3.69a1.001 1.001 0 0 0-1.353.12l-.866.955.306-1.487 7.083-7.083a1 1 0 0 0-1.169-1.593L6.745 12.554 3.02 11.191 20.999 4Z"/>',
|
||||
rss: '<path d="M2.88 16.88a3 3 0 0 0 0 4.24 3 3 0 0 0 4.24 0 3 3 0 0 0-4.24-4.24Zm2.83 2.83a1 1 0 0 1-1.42-1.42 1 1 0 0 1 1.42 0 1 1 0 0 1 0 1.42ZM5 12a1 1 0 0 0 0 2 5 5 0 0 1 5 5 1 1 0 0 0 2 0 7 7 0 0 0-7-7Zm0-4a1 1 0 0 0 0 2 9 9 0 0 1 9 9 1 1 0 0 0 2 0 11.08 11.08 0 0 0-3.22-7.78A11.08 11.08 0 0 0 5 8Zm10.61.39A15.11 15.11 0 0 0 5 4a1 1 0 0 0 0 2 13 13 0 0 1 13 13 1 1 0 0 0 2 0 15.11 15.11 0 0 0-4.39-10.61Z"/>',
|
||||
facebook:
|
||||
'<path d="M20.9 2H3.1A1.1 1.1 0 0 0 2 3.1v17.8A1.1 1.1 0 0 0 3.1 22h9.58v-7.75h-2.6v-3h2.6V9a3.64 3.64 0 0 1 3.88-4 20.26 20.26 0 0 1 2.33.12v2.7H17.3c-1.26 0-1.5.6-1.5 1.47v1.93h3l-.39 3H15.8V22h5.1a1.1 1.1 0 0 0 1.1-1.1V3.1A1.1 1.1 0 0 0 20.9 2Z"/>',
|
||||
email:
|
||||
'<path d="M19 4H5a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V7a3 3 0 0 0-3-3Zm-.41 2-5.88 5.88a1 1 0 0 1-1.42 0L5.41 6ZM20 17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7.41l5.88 5.88a3 3 0 0 0 4.24 0L20 7.41Z"/>',
|
||||
phone:
|
||||
'<path d="M19.44 13c-.22 0-.45-.07-.67-.12a9.44 9.44 0 0 1-1.31-.39 2 2 0 0 0-2.48 1l-.22.45a12.18 12.18 0 0 1-2.66-2 12.18 12.18 0 0 1-2-2.66l.42-.28a2 2 0 0 0 1-2.48 10.33 10.33 0 0 1-.39-1.31c-.05-.22-.09-.45-.12-.68a3 3 0 0 0-3-2.49h-3a3 3 0 0 0-3 3.41 19 19 0 0 0 16.52 16.46h.38a3 3 0 0 0 2-.76 3 3 0 0 0 1-2.25v-3a3 3 0 0 0-2.47-2.9Zm.5 6a1 1 0 0 1-.34.75 1.05 1.05 0 0 1-.82.25A17 17 0 0 1 4.07 5.22a1.09 1.09 0 0 1 .25-.82 1 1 0 0 1 .75-.34h3a1 1 0 0 1 1 .79q.06.41.15.81a11.12 11.12 0 0 0 .46 1.55l-1.4.65a1 1 0 0 0-.49 1.33 14.49 14.49 0 0 0 7 7 1 1 0 0 0 .76 0 1 1 0 0 0 .57-.52l.62-1.4a13.69 13.69 0 0 0 1.58.46q.4.09.81.15a1 1 0 0 1 .79 1Z"/>',
|
||||
reddit:
|
||||
'<path d="M14.41 16.87a3.38 3.38 0 0 1-2.37.63 3.37 3.37 0 0 1-2.36-.63 1 1 0 0 0-1.42 1.41 5.11 5.11 0 0 0 3.78 1.22 5.12 5.12 0 0 0 3.78-1.22 1 1 0 1 0-1.41-1.41ZM9.2 15a1 1 0 1 0-1-1 1 1 0 0 0 1 1Zm6-2a1 1 0 1 0 1 1 1 1 0 0 0-1-1Zm7.8-1.22a3.77 3.77 0 0 0-6.8-2.26 16.5 16.5 0 0 0-3.04-.48l.85-5.7 2.09.7a3 3 0 0 0 6-.06v-.02a3.03 3.03 0 0 0-3-2.96 2.98 2.98 0 0 0-2.34 1.16l-3.24-1.1a1 1 0 0 0-1.3.8l-1.09 7.17a16.66 16.66 0 0 0-3.34.49 3.77 3.77 0 0 0-6.22 4.23A4.86 4.86 0 0 0 1 16c0 3.92 4.83 7 11 7s11-3.08 11-7a4.86 4.86 0 0 0-.57-2.25 3.78 3.78 0 0 0 .57-1.97ZM19.1 3a1 1 0 1 1-1 1 1.02 1.02 0 0 1 1-1ZM4.77 10a1.76 1.76 0 0 1 .88.25A9.98 9.98 0 0 0 3 11.92v-.14A1.78 1.78 0 0 1 4.78 10ZM12 21c-4.88 0-9-2.29-9-5s4.12-5 9-5 9 2.29 9 5-4.12 5-9 5Zm8.99-9.08a9.98 9.98 0 0 0-2.65-1.67 1.76 1.76 0 0 1 .88-.25A1.78 1.78 0 0 1 21 11.78l-.01.14Z"/>',
|
||||
patreon:
|
||||
'<path d="M22.04 7.6c0-2.8-2.19-5.1-4.75-5.93a15.19 15.19 0 0 0-10.44.55C3.16 3.96 2 7.78 1.95 11.58c-.02 3.12.3 11.36 4.94 11.42 3.45.04 3.97-4.4 5.56-6.55 1.14-1.52 2.6-1.95 4.4-2.4 3.1-.76 5.2-3.2 5.2-6.44Z"/>',
|
||||
signal:
|
||||
'<path d="m9.12.35.27 1.09a10.9 10.9 0 0 0-3.015 1.248l-.578-.964A12 12 0 0 1 9.12.35m5.76 0-.27 1.09a10.9 10.9 0 0 1 3.015 1.248l.581-.964A12 12 0 0 0 14.88.35M1.725 5.797A12 12 0 0 0 .351 9.119l1.09.27A10.9 10.9 0 0 1 2.69 6.374zm-.6 6.202a11 11 0 0 1 .122-1.63l-1.112-.168a12 12 0 0 0 0 3.596l1.112-.169A11 11 0 0 1 1.125 12zm17.078 10.275-.578-.964a10.9 10.9 0 0 1-3.011 1.247l.27 1.091a12 12 0 0 0 3.319-1.374M22.875 12a11 11 0 0 1-.122 1.63l1.112.168a12 12 0 0 0 0-3.596l-1.112.169a11 11 0 0 1 .122 1.63zm.774 2.88-1.09-.27a10.9 10.9 0 0 1-1.248 3.015l.964.581a12 12 0 0 0 1.374-3.326m-10.02 7.875a11 11 0 0 1-3.258 0l-.17 1.112a12 12 0 0 0 3.597 0zm7.125-4.303a11 11 0 0 1-2.304 2.302l.668.906a12 12 0 0 0 2.542-2.535zM18.45 3.245a11 11 0 0 1 2.304 2.304l.906-.675a12 12 0 0 0-2.535-2.535zM3.246 5.549A11 11 0 0 1 5.55 3.245l-.675-.906A12 12 0 0 0 2.34 4.874zm19.029.248-.964.577a10.9 10.9 0 0 1 1.247 3.011l1.091-.27a12 12 0 0 0-1.374-3.318M10.371 1.246a11 11 0 0 1 3.258 0L13.8.134a12 12 0 0 0-3.597 0zM3.823 21.957 1.5 22.5l.542-2.323-1.095-.257-.542 2.323a1.125 1.125 0 0 0 1.352 1.352l2.321-.532zm-2.642-3.041 1.095.255.375-1.61a10.8 10.8 0 0 1-1.21-2.952l-1.09.27a12 12 0 0 0 1.106 2.852zm5.25 2.437-1.61.375.255 1.095 1.185-.275a12 12 0 0 0 2.851 1.106l.27-1.091a10.8 10.8 0 0 1-2.943-1.217zM12 2.25a9.75 9.75 0 0 0-8.25 14.938l-.938 4 4-.938A9.75 9.75 0 1 0 12 2.25"/>',
|
||||
slack:
|
||||
'<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52Zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313ZM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834Zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312Zm10.122 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834Zm-1.268 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312Zm-2.523 10.122a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52Zm0-1.268a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313Z"/>',
|
||||
matrix:
|
||||
'<path d="M22.5 1.5v21h-2.25V24H24V0h-3.75v1.5h2.25ZM7.46 7.95V9.1h.04a3.02 3.02 0 0 1 2.61-1.39c.54 0 1.03.1 1.48.32.44.2.78.58 1.01 1.1.26-.37.6-.7 1.03-.99.44-.28.95-.43 1.55-.43.45 0 .87.06 1.26.17.38.11.71.29.99.53.27.24.49.56.64.95.15.4.23.86.23 1.42v5.72h-2.34v-4.85c0-.29-.01-.56-.04-.8a1.73 1.73 0 0 0-.18-.67 1.1 1.1 0 0 0-.44-.45 1.6 1.6 0 0 0-.78-.16c-.33 0-.6.06-.8.19-.2.12-.37.29-.48.5a2 2 0 0 0-.23.69c-.04.26-.06.52-.06.78v4.77H10.6v-4.8l-.01-.75a2.29 2.29 0 0 0-.14-.69c-.08-.2-.23-.38-.42-.5a1.5 1.5 0 0 0-.85-.2c-.15.01-.3.04-.44.08-.19.06-.37.15-.52.28-.18.14-.32.34-.44.6-.12.26-.18.6-.18 1.02v4.96H5.25V7.94h2.21ZM1.5 1.5v21h2.25V24H0V0h3.75v1.5H1.5Z"/>',
|
||||
hackerOne:
|
||||
'<path d="M7.2 0Q6.5 0 6 .3a1 1 0 0 0-.4.8v21.8q0 .4.4.7.5.4 1.2.4t1.2-.3q.5-.4.5-.8V1c0-.3-.2-.6-.5-.8Q7.9 0 7.2 0m9.5 8.7q-.7 0-1.1.3L11 11.7c-.2.2-.3.5-.2.9q0 .6.5 1c.3.4.7.7 1 .7q.7.2 1-.1l1.7-1v9.7q0 .4.5.7c.3.3.7.4 1.1.4q.7 0 1.2-.3c.4-.3.5-.5.5-.8V9.7q0-.4-.5-.7-.4-.3-1.2-.3"/>',
|
||||
openCollective:
|
||||
'<path d="M21.86 5.17a11.94 11.94 0 0 1 0 13.66l-3.1-3.1a7.68 7.68 0 0 0 0-7.46l3.1-3.1Zm-3.03-3.03-3.1 3.1a7.71 7.71 0 1 0 0 13.51l3.1 3.11a12 12 0 1 1 0-19.73Z"/><path d="M21.86 5.17a11.94 11.94 0 0 1 0 13.66l-3.1-3.1a7.68 7.68 0 0 0 0-7.46l3.1-3.1Z"/>',
|
||||
blueSky:
|
||||
'<path d="M12 10.8c-1-2.1-4-6-6.8-8C2.6 1 1.6 1.3.9 1.6.1 1.9 0 3 0 3.8c0 .7.4 5.6.6 6.4C1.4 13 4.3 14 7 13.6h.4H7c-4 .6-7.4 2-2.8 7 5 5.3 6.8-1 7.8-4.2 1 3.2 2 9.3 7.7 4.3 4.3-4.3 1.2-6.5-2.7-7a9 9 0 0 1-.4-.1h.4c2.7.3 5.6-.6 6.4-3.4.2-.8.6-5.7.6-6.4 0-.7-.1-1.9-.9-2.2-.7-.3-1.7-.7-4.3 1.2-2.8 2-5.7 5.9-6.8 8"/>',
|
||||
discourse:
|
||||
'<path d="M12.102 0h-.081C5.462 0 .13 5.252.001 11.779v.012L.007 24l12.097-.01c6.582-.055 11.897-5.404 11.897-11.995S18.686.056 12.109 0h-.005zM12 18.857h-.015a6.778 6.778 0 0 1-2.94-.666l.041.018-4.345 1.077 1.227-4.018a6.78 6.78 0 0 1-.83-3.27A6.86 6.86 0 1 1 12 18.857z"/>',
|
||||
zulip:
|
||||
'<path d="M21 19c0 1.7-1.2 3-2.7 3H5.7C4.2 22 3 20.7 3 19a3 3 0 0 1 1.2-2.4l6.7-6c0-.1.3 0 .2.2l-2.5 5s0 .2.2.2h9.5c1.5 0 2.7 1.4 2.7 3Zm0-14a3 3 0 0 1-1.2 2.4l-6.7 6c0 .1-.2 0-.2-.2l2.5-5s0-.2-.2-.2H5.7C4.2 8 3 6.6 3 5c0-1.7 1.2-3 2.7-3h12.6C19.8 2 21 3.3 21 5Z"/>',
|
||||
pinterest:
|
||||
'<path d="M12 0a12 12 0 0 0-4.4 23.1c0-.9-.2-2.4 0-3.4l1.5-6s-.4-.7-.4-1.7c0-1.7 1-3 2.2-3 1 0 1.5.8 1.5 1.7 0 1-.6 2.6-1 4-.3 1.2.6 2.2 1.8 2.2 2.1 0 3.8-2.2 3.8-5.5 0-2.8-2-4.9-5-4.9a5.2 5.2 0 0 0-5.4 5.2c0 1 .3 2.2.8 2.8.1.1.2.2.1.3l-.3 1.4c0 .2-.2.3-.4.2-1.5-.7-2.4-3-2.4-4.7C4.4 8 7 4.5 12.3 4.5c4.1 0 7.4 3 7.4 6.9 0 4.1-2.6 7.4-6.3 7.4-1.2 0-2.3-.6-2.7-1.3l-.8 2.8c-.2 1-1 2.4-1.5 3.2A12 12 0 1 0 12 0z"/>',
|
||||
tiktok:
|
||||
'<path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/>',
|
||||
astro:
|
||||
'<path d="M7.233 15.856c-.456 1.5-.132 3.586.948 4.57v-.036l.036-.096c.132-.636.648-1.032 1.309-1.008.612.012.96.336 1.044 1.044.036.264.036.528.048.803v.084c0 .6.168 1.176.504 1.68.3.48.72.851 1.284 1.103l-.024-.048-.024-.096c-.42-1.26-.12-2.135.984-2.879l.336-.227.745-.492a3.647 3.647 0 0 0 1.536-2.603c.06-.456 0-.9-.132-1.331l-.18.12c-1.668.887-3.577 1.2-5.425.84-1.117-.169-2.197-.48-3-1.416l.011-.012ZM2 15.592s3.205-1.559 6.421-1.559l2.437-7.508c.084-.36.348-.6.648-.6.3 0 .552.24.648.612l2.425 7.496c3.816 0 6.421 1.56 6.421 1.56L15.539.72c-.144-.444-.42-.72-.768-.72H8.24c-.348 0-.6.276-.768.72L2 15.592Z"/>',
|
||||
alpine: '<path d="m18.7 6 5.3 5.3-5.3 5.3-5.4-5.3L18.7 6zM5.3 6l11 11H5.8L0 11.2 5.3 6z"/>',
|
||||
pnpm: '<path d="M0 0v7.5h7.5V0H0Zm8.25 0v7.5h7.498V0H8.25Zm8.25 0v7.5H24V0h-7.5ZM8.25 8.25v7.5h7.498v-7.5H8.25Zm8.25 0v7.5H24v-7.5h-7.5ZM0 16.5V24h7.5v-7.5H0Zm8.25 0V24h7.498v-7.5H8.25Zm8.25 0V24H24v-7.5h-7.5Z"/>',
|
||||
biome:
|
||||
'<path d="m12 2-5.346 9.259a12.065 12.065 0 0 1 6.326-.22l1.807.427-1.7 7.208-1.81-.427c-2.223-.524-4.36.644-5.263 2.507l-1.672-.809c1.276-2.636 4.284-4.232 7.363-3.505l.848-3.593A10.213 10.213 0 0 0 0 22.785h24L12 2Z"/>',
|
||||
bun: '<path d="M11.966 22.132c6.609 0 11.966-4.326 11.966-9.661 0-3.308-2.051-6.23-5.204-7.963-1.283-.713-2.291-1.353-3.13-1.885C14.018 1.619 13.043 1 11.966 1c-1.094 0-2.327.783-3.955 1.816a49.78 49.78 0 0 1-2.808 1.692C2.051 6.241 0 9.163 0 12.471c0 5.335 5.357 9.661 11.966 9.661Zm-1.397-17.83a5.885 5.885 0 0 0 .497-2.403c0-.144.201-.186.229-.028.656 2.775-.9 4.15-2.051 4.61-.124.048-.199-.12-.103-.208a5.747 5.747 0 0 0 1.428-1.971Zm2.052-.102a5.795 5.795 0 0 0-.78-2.3v-.015c-.068-.123.086-.263.185-.172 1.956 2.105 1.303 4.055.554 5.037-.082.102-.229-.003-.188-.126a5.837 5.837 0 0 0 .229-2.424Zm1.771-.559a5.709 5.709 0 0 0-1.607-1.801v-.014c-.112-.085-.024-.274.113-.218 2.588 1.084 2.766 3.171 2.452 4.395a.116.116 0 0 1-.13.09.11.11 0 0 1-.071-.045.118.118 0 0 1-.022-.083 5.863 5.863 0 0 0-.735-2.324ZM9.32 4.2c-.616.544-1.279.758-2.058.997-.116 0-.194-.078-.155-.18 1.747-.907 2.369-1.645 2.99-2.771 0 0 .155-.117.188.085 0 .303-.348 1.325-.965 1.869Zm4.931 11.205a2.95 2.95 0 0 1-.935 1.549 2.16 2.16 0 0 1-1.282.618 2.167 2.167 0 0 1-1.323-.618 2.95 2.95 0 0 1-.923-1.549.243.243 0 0 1 .064-.197.23.23 0 0 1 .192-.069h3.954a.227.227 0 0 1 .244.16c.01.035.014.07.009.106Zm-5.443-2.17a1.85 1.85 0 0 1-2.377-.244 1.969 1.969 0 0 1-.233-2.44c.207-.318.502-.565.846-.711a1.84 1.84 0 0 1 2.053.42c.264.27.443.616.515.99a1.98 1.98 0 0 1-.108 1.118c-.142.35-.384.653-.696.867Zm8.471.005a1.85 1.85 0 0 1-2.374-.252 1.956 1.956 0 0 1-.546-1.362c0-.383.11-.758.319-1.076.207-.318.502-.566.847-.711a1.84 1.84 0 0 1 1.09-.108c.366.076.702.261.965.533s.44.617.512.993a1.98 1.98 0 0 1-.113 1.118 1.922 1.922 0 0 1-.7.865Z"/>',
|
||||
mdx: '<path d="m15.494 12.406-3.169 3.169-3.25-3.169.894-.894 1.706 1.707V8.588h1.219V13.3l1.706-1.706.894.812Zm-13.65-.65 2.193 2.194 2.276-2.194v3.575H7.53v-6.58l-3.494 3.493L.625 8.75v6.581h1.219v-3.575ZM22.4 15.25l-2.519-2.519-2.518 2.519-.813-.894 2.519-2.518-2.6-2.6.893-.813 2.52 2.6 2.6-2.6.893.813-2.6 2.6 2.519 2.518-.894.894Z"/>',
|
||||
apple:
|
||||
'<path d="M14.94 5.19A4.38 4.38 0 0 0 16 2a4.44 4.44 0 0 0-3 1.52 4.17 4.17 0 0 0-1 3.09 3.69 3.69 0 0 0 2.94-1.42Zm2.52 7.44a4.51 4.51 0 0 1 2.16-3.81 4.66 4.66 0 0 0-3.66-2c-1.56-.16-3 .91-3.83.91s-2-.89-3.3-.87a4.92 4.92 0 0 0-4.14 2.53C2.93 12.45 4.24 17 6 19.47c.8 1.21 1.8 2.58 3.12 2.53s1.75-.82 3.28-.82 2 .82 3.3.79 2.22-1.24 3.06-2.45a11 11 0 0 0 1.38-2.85 4.41 4.41 0 0 1-2.68-4.04Z"/>',
|
||||
linux:
|
||||
'<path d="M19.7 17.6c-.1-.2-.2-.4-.2-.6 0-.4-.2-.7-.5-1-.1-.1-.3-.2-.4-.2.6-1.8-.3-3.6-1.3-4.9-.8-1.2-2-2.1-1.9-3.7 0-1.9.2-5.4-3.3-5.1-3.6.2-2.6 3.9-2.7 5.2 0 1.1-.5 2.2-1.3 3.1-.2.2-.4.5-.5.7-1 1.2-1.5 2.8-1.5 4.3-.2.2-.4.4-.5.6-.1.1-.2.2-.2.3-.1.1-.3.2-.5.3-.4.1-.7.3-.9.7-.1.3-.2.7-.1 1.1.1.2.1.4 0 .7-.2.4-.2.9 0 1.4.3.4.8.5 1.5.6.5 0 1.1.2 1.6.4.5.3 1.1.5 1.7.5.3 0 .7-.1 1-.2.3-.2.5-.4.6-.7.4 0 1-.2 1.7-.2.6 0 1.2.2 2 .1 0 .1 0 .2.1.3.2.5.7.9 1.3 1h.2c.8-.1 1.6-.5 2.1-1.1.4-.4.9-.7 1.4-.9.6-.3 1-.5 1.1-1 .1-.7-.1-1.1-.5-1.7zM12.8 4.8c.6.1 1.1.6 1 1.2 0 .3-.1.6-.3.9h-.1c-.2-.1-.3-.1-.4-.2.1-.1.1-.3.2-.5 0-.4-.2-.7-.4-.7-.3 0-.5.3-.5.7v.1c-.1-.1-.3-.1-.4-.2V6c-.1-.5.3-1.1.9-1.2zm-.3 2c.1.1.3.2.4.2.1 0 .3.1.4.2.2.1.4.2.4.5s-.3.6-.9.8c-.2.1-.3.1-.4.2-.3.2-.6.3-1 .3-.3 0-.6-.2-.8-.4-.1-.1-.2-.2-.4-.3-.1-.1-.3-.3-.4-.6 0-.1.1-.2.2-.3.3-.2.4-.3.5-.4l.1-.1c.2-.3.6-.5 1-.5.3.1.6.2.9.4zM10.4 5c.4 0 .7.4.8 1.1v.2c-.1 0-.3.1-.4.2v-.2c0-.3-.2-.6-.4-.5-.2 0-.3.3-.3.6 0 .2.1.3.2.4 0 0-.1.1-.2.1-.2-.2-.4-.5-.4-.8 0-.6.3-1.1.7-1.1zm-1 16.1c-.7.3-1.6.2-2.2-.2-.6-.3-1.1-.4-1.8-.4-.5-.1-1-.1-1.1-.3-.1-.2-.1-.5.1-1 .1-.3.1-.6 0-.9-.1-.3-.1-.5 0-.8.1-.3.3-.4.6-.5.3-.1.5-.2.7-.4.1-.1.2-.2.3-.4.3-.4.5-.6.8-.6.6.1 1.1 1 1.5 1.9.2.3.4.7.7 1 .4.5.9 1.2.9 1.6 0 .5-.2.8-.5 1zm4.9-2.2c0 .1 0 .1-.1.2-1.2.9-2.8 1-4.1.3l-.6-.9c.9-.1.7-1.3-1.2-2.5-2-1.3-.6-3.7.1-4.8.1-.1.1 0-.3.8-.3.6-.9 2.1-.1 3.2 0-.8.2-1.6.5-2.4.7-1.3 1.2-2.8 1.5-4.3.1.1.1.1.2.1.1.1.2.2.3.2.2.3.6.4.9.4h.1c.4 0 .8-.1 1.1-.4.1-.1.2-.2.4-.2.3-.1.6-.3.9-.6.4 1.3.8 2.5 1.4 3.6.4.8.7 1.6.9 2.5.3 0 .7.1 1 .3.8.4 1.1.7 1 1.2H18c0-.3-.2-.6-.9-.9-.7-.3-1.3-.3-1.5.4-.1 0-.2.1-.3.1-.8.4-.8 1.5-.9 2.6.1.4 0 .7-.1 1.1zm4.6.6c-.6.2-1.1.6-1.5 1.1-.4.6-1.1 1-1.9.9-.4 0-.8-.3-.9-.7-.1-.6-.1-1.2.2-1.8.1-.4.2-.7.3-1.1.1-1.2.1-1.9.6-2.2 0 .5.3.8.7 1 .5 0 1-.1 1.4-.5h.2c.3 0 .5 0 .7.2.2.2.3.5.3.7 0 .3.2.6.3.9.5.5.5.8.5.9-.1.2-.5.4-.9.6zm-9-12c-.1 0-.1 0-.1.1 0 0 0 .1.1.1s.1.1.1.1c.3.4.8.6 1.4.7.5-.1 1-.2 1.5-.6l.6-.3c.1 0 .1-.1.1-.1 0-.1 0-.1-.1-.1-.2.1-.5.2-.7.3-.4.3-.9.5-1.4.5-.5 0-.9-.3-1.2-.6-.1 0-.2-.1-.3-.1z"/>',
|
||||
homebrew:
|
||||
'<path d="M7.94 0a.21.21 0 0 0-.2.16c-.32 1.1.17 2.15.83 2.93.15.18.31.35.48.5a2.04 2.04 0 0 0-.67.02c-1.18.24-2.2.99-2.74 2.53a3.9 3.9 0 0 0-.2 1.47 1.56 1.56 0 0 0-1.16 1.5 1.59 1.59 0 0 0 1.23 1.55l.03 12.04c0 .2.1.38.26.48a.21.21 0 0 0 .01 0c.54.32 2.05.82 5.21.82 3.24 0 4.7-.68 5.18-1.04a.57.57 0 0 0 .22-.45v-1.6a.14.14 0 0 1 .14-.14h1.32a1.83 1.83 0 0 0 1.83-1.82v-5.8a1.83 1.83 0 0 0-1.82-1.83h-1.33a.14.14 0 0 1-.14-.15v-.57a1.57 1.57 0 0 0 1.36-1.56c0-.81-.63-1.49-1.42-1.56a4.34 4.34 0 0 0-.74-2.58 3.1 3.1 0 0 0-2.28-1.32c-.5-.02-.84.12-1.13.25-.21.1-.42.18-.67.22 0-1.28.95-1.98.95-1.98a.21.21 0 0 0 .05-.3s-.09-.12-.21-.26c-.12-.13-.27-.3-.47-.38a.21.21 0 0 0-.08-.01.21.21 0 0 0-.14.05 4.3 4.3 0 0 0-.88 1.1 3.42 3.42 0 0 0-.13.28 3.5 3.5 0 0 0-.38-.85A4.44 4.44 0 0 0 8.02.02.21.21 0 0 0 7.94 0zm.15.52c.85.38 1.43.83 1.8 1.4.27.45.42.97.48 1.6a3.07 3.07 0 0 0-.01.45 6.9 6.9 0 0 1-.17-.05 5.49 5.49 0 0 1-1.3-1.1c-.54-.66-.93-1.46-.8-2.3m3.71 1.1c.07.05.14.1.21.18l.06.07a2.97 2.97 0 0 0-.95 2.45.21.21 0 0 0 .22.2c.47-.02.78-.17 1.06-.3.27-.13.5-.23.93-.21.87.02 1.64.71 1.94 1.13.3.45.65 1 .66 2.36a1.66 1.66 0 0 0-.41.14 1.94 1.94 0 0 0-1.77-1.16 1.94 1.94 0 0 0-1.87 1.45 1.78 1.78 0 0 0-1.36-.64c-.48 0-.9.2-1.23.52a1.87 1.87 0 0 0-1.85-1.63c-.65 0-1.22.34-1.55.84a3.1 3.1 0 0 1 .16-.73c.5-1.44 1.35-2.05 2.42-2.26.36-.07.66 0 .99.1.32.1.67.26 1.09.34a.21.21 0 0 0 .25-.25c-.11-.67.07-1.26.34-1.74a3.71 3.71 0 0 1 .66-.86m-4.36 5A1.44 1.44 0 0 1 8.8 8.53a.21.21 0 0 0 .17.28.21.21 0 0 0 .24-.15 1.37 1.37 0 0 1 2.62 0 .21.21 0 0 0 .41-.1 1.5 1.5 0 0 1 1.5-1.66c.69 0 1.26.44 1.45 1.05a.21.21 0 0 0 .26.15l.15-.04a.21.21 0 0 0 .05-.02 1.14 1.14 0 0 1 1.7 1 1.14 1.14 0 0 1-.98 1.12 2.21 2.21 0 0 0-.49.13 10.65 10.65 0 0 1-1.18.36.21.21 0 0 0-.16.2 1.28 1.28 0 0 1-.14.47 2.07 2.07 0 0 0-.24 1.11v.15a.44.44 0 0 1-.16.36.67.67 0 0 1-.43.14.59.59 0 0 1-.59-.59.8.8 0 0 0-.38-.68 1.28 1.28 0 0 1-.53-.64.21.21 0 0 0-.21-.14 19.47 19.47 0 0 1-5.37-.6 9 9 0 0 0-.84-.2 1.16 1.16 0 0 1-.94-1.13c0-.62.5-1.11 1.1-1.14a.21.21 0 0 0 .21-.17A1.44 1.44 0 0 1 7.44 6.6m8.55 4.1v.46c0 .32.26.57.57.57h1.33a1.4 1.4 0 0 1 1.4 1.4v5.8a1.4 1.4 0 0 1-1.4 1.4h-1.32a.57.57 0 0 0-.58.57v1.6c0 .05-.02.08-.05.11-.35.26-1.75.95-4.92.95-3.1 0-4.59-.52-4.99-.75a.14.14 0 0 1-.06-.12l-.03-11.95.43.1.39.1v10.37c0 .13.07.25.18.31.45.22 1.77.74 4.07.74 2.32 0 3.6-.63 4.02-.89a.36.36 0 0 0 .17-.3v-10.2l.79-.26m-8 .9a.5.5 0 0 1 .5.48v8.58a.5.5 0 0 1-.49.5.5.5 0 0 1-.5-.5V12.1a.5.5 0 0 1 .5-.49zm8.66 1.13a.66.66 0 0 0-.66.66v5.21a.66.66 0 0 0 .66.66h1.14a.66.66 0 0 0 .66-.66v-5.2a.66.66 0 0 0-.66-.67zm0 .43h1.14a.23.23 0 0 1 .23.23v5.21a.23.23 0 0 1-.23.23h-1.14a.23.23 0 0 1-.23-.23v-5.2a.23.23 0 0 1 .23-.24"/>',
|
||||
nix: '<path d="M7.4 1.6H6l-.7 1.1L7 5.5H3.7L2.4 7.8h11.7l-1.3-2.3H9.6l-2.2-4zm6.1 0h-2.7l5.9 10.1L18 9.4l-1.6-2.8 2.3-3.8-.7-1.2h-1.3L15 4.3l-1.6-2.7zm7 4.2-6 10.1h2.8l1.6-2.8h4.4L24 12l-.7-1.2h-3.1l1.6-2.7-1.4-2.3zM9.4 8H6.6L5 10.8H.7L0 12l.7 1.2h3.1l-1.6 2.7 1.4 2.3zm-2.2 4.2L6 14.6l1.6 2.8-2.3 3.8.7 1.2h1.3L9 19.7l1.6 2.7h2.7zm2.6 3.9 1.3 2.3h3.2l2.2 3.9H18l.7-1.2-1.6-2.7h3.2l1.3-2.3z"/>',
|
||||
starlight:
|
||||
'<path fill-rule="evenodd" d="M15.19 6.75 12 0 8.81 6.75l-.19.38-1.68-1.88a1.2 1.2 0 1 0-1.69 1.69L7.13 8.8h-.38L0 12l6.75 3.19h.38l-1.88 1.87a1.2 1.2 0 1 0 1.69 1.69l1.68-1.88.2.38L12 24l3.19-6.75v-.38l1.69 1.88a1.2 1.2 0 1 0 1.68-1.69l-1.68-1.68.37-.2L24 12l-6.75-3.19-.38-.19 1.7-1.68a1.2 1.2 0 1 0-1.7-1.69L15.2 7.13v-.38ZM12 7.13l-.38.93a8.18 8.18 0 0 1-3.56 3.56l-.94.38.94.38a8.18 8.18 0 0 1 3.56 3.56l.38.94.38-.94a8.18 8.18 0 0 1 3.56-3.56l.94-.38-.94-.38a8.18 8.18 0 0 1-3.56-3.56L12 7.12Z"/><path d="M22.12 3.56a1.2 1.2 0 1 0-1.68-1.69l-.57.57a1.2 1.2 0 0 0 1.7 1.68l.55-.56Zm-18 .75c-.37.38-1.12.38-1.68 0l-.57-.56a1.2 1.2 0 0 1 1.7-1.69l.55.56c.57.38.57 1.13 0 1.7Zm0 15.38c-.37-.38-1.12-.38-1.68 0l-.57.56a1.2 1.2 0 1 0 1.7 1.69l.55-.57c.57-.37.57-1.12 0-1.68Zm18 .75a1.2 1.2 0 1 1-1.68 1.68l-.57-.56a1.2 1.2 0 0 1 1.7-1.69l.55.57Z"/>',
|
||||
pkl: '<path fill-rule="evenodd" d="M18.7 1.8 18 5a9 9 0 0 1 2 2.4l3.2.2c.4 1 .6 2 .7 3.1L21 12.2a9 9 0 0 1-.7 3l1.9 2.7c-.6 1-1.2 1.7-2 2.5l-3-1.3c-.9.6-1.9 1-2.9 1.4l-.9 3c-1 .2-2.1.2-3.2 0l-.8-3c-1-.4-2-.8-2.9-1.5l-3 1.3a12 12 0 0 1-2-2.5l2-2.6a9 9 0 0 1-.8-3L0 10.5c.1-1.1.3-2.1.7-3.1L4 7.3A9 9 0 0 1 6 5l-.6-3.2c1-.6 2-1 3-1.3l2 2.4c1.1-.2 2.2-.2 3.2 0L15.8.4c1 .3 2 .8 2.9 1.4Zm1 9.8c0 4.2-3.3 7.5-7.5 7.5a7.5 7.5 0 0 1-7.6-7.5c0-4.1 3.4-7.5 7.6-7.5 4.2 0 7.6 3.4 7.6 7.5Z"></path><path fill-opacity=".5" d="M11.4 10.8c-6.6-2.7-3.6-5.5.4-5.5 4.3 0 7.8 2.5 1.2 5.5a2 2 0 0 1-1.6 0Zm.4 1.9c1 7-3 5.8-5 2.5-2.1-3.7-1.7-8 4.2-3.9a2 2 0 0 1 .8 1.4Zm6.2 1.7c2-3.3 1-7.3-4.7-3a2 2 0 0 0-.8 1.4c-.7 7.1 3.3 5.4 5.5 1.7Z"/>',
|
||||
node: '<path d="M12 23.96c-.34 0-.66-.1-.96-.25l-3.03-1.73c-.45-.25-.22-.33-.09-.38.62-.2.73-.24 1.37-.6.07-.04.16-.02.23.03l2.32 1.34c.1.05.2.05.27 0l9.1-5.08a.27.27 0 0 0 .13-.24V6.9c0-.11-.05-.2-.14-.24L12.11 1.6c-.09-.05-.2-.05-.27 0L2.75 6.66c-.09.04-.13.15-.13.24v10.14c0 .1.04.2.13.25l2.49 1.38c1.34.66 2.18-.1 2.18-.88V7.78c0-.13.12-.26.28-.26h1.16c.13 0 .27.1.27.26v10.01c0 1.74-.98 2.75-2.69 2.75-.52 0-.93 0-2.1-.55l-2.38-1.32a1.85 1.85 0 0 1-.96-1.6V6.92c0-.66.36-1.28.96-1.6l9.08-5.1a2.1 2.1 0 0 1 1.92 0l9.08 5.08c.6.33.96.95.96 1.61v10.15c0 .66-.36 1.27-.96 1.6l-9.08 5.09a2.4 2.4 0 0 1-.96.2m2.8-6.98c-3.98 0-4.8-1.76-4.8-3.26 0-.13.11-.26.27-.26h1.18c.14 0 .25.08.25.22.19 1.16.71 1.73 3.12 1.73 1.92 0 2.74-.41 2.74-1.4 0-.58-.23-1-3.21-1.28-2.49-.24-4.04-.77-4.04-2.68 0-1.79 1.55-2.84 4.15-2.84 2.91 0 4.35.96 4.53 3.08a.35.35 0 0 1-.07.2.27.27 0 0 1-.18.08h-1.18a.27.27 0 0 1-.25-.2c-.28-1.2-.98-1.6-2.85-1.6-2.1 0-2.35.7-2.35 1.23 0 .64.3.84 3.12 1.19 2.8.35 4.13.86 4.13 2.75-.03 1.94-1.67 3.04-4.56 3.04"/>',
|
||||
cloudflare:
|
||||
'<path d="M16.5 16.84c.16-.5.1-.97-.15-1.3-.22-.33-.6-.5-1.06-.53l-8.66-.11a.15.15 0 0 1-.13-.08.21.21 0 0 1-.02-.15.25.25 0 0 1 .2-.15l8.74-.12a3.13 3.13 0 0 0 2.55-1.91l.5-1.3a.25.25 0 0 0 .01-.17 5.68 5.68 0 0 0-10.93-.59 2.58 2.58 0 0 0-3.35.24 2.55 2.55 0 0 0-.67 2.44 3.64 3.64 0 0 0-3.5 4.17.18.18 0 0 0 .17.15h15.98a.22.22 0 0 0 .21-.16l.12-.43Zm2.77-5.56-.24.01c-.06 0-.1.05-.13.1l-.34 1.18c-.15.5-.1.97.16 1.31.22.32.6.5 1.06.52l1.84.12c.06 0 .1.02.14.07.02.04.03.1.02.15a.23.23 0 0 1-.2.15l-1.93.12a3.11 3.11 0 0 0-2.55 1.91l-.14.36c-.03.07.02.14.1.14h6.6a.18.18 0 0 0 .16-.12 4.74 4.74 0 0 0-4.56-6v-.02Z"/>',
|
||||
vercel: '<path d="m12 1l12 21H0z"/>',
|
||||
netlify:
|
||||
'<path d="M6.49 19.04h-.23L5.13 17.9v-.23l1.73-1.71h1.2l.15.15v1.2L6.5 19.04ZM5.13 6.31V6.1l1.13-1.13h.23L8.2 6.68v1.2l-.15.15h-1.2zm9.96 9.09h-1.65l-.14-.13v-3.83c0-.68-.27-1.2-1.1-1.23c-.42 0-.9 0-1.43.02l-.07.08v4.96l-.14.14H8.9l-.13-.14V8.73l.13-.14h3.7a2.6 2.6 0 0 1 2.61 2.6v4.08l-.13.14Zm-8.37-2.44H.14L0 12.82v-1.64l.14-.14h6.58l.14.14v1.64zm17.14 0h-6.58l-.14-.14v-1.64l.14-.14h6.58l.14.14v1.64zM11.05 6.55V1.64l.14-.14h1.65l.14.14v4.9l-.14.14h-1.65zm0 15.81v-4.9l.14-.14h1.65l.14.13v4.91l-.14.14h-1.65z"/>',
|
||||
deno: '<path d="M12 0a12 12 0 1 1 0 24 12 12 0 0 1 0-24m-.47 6.8c-3.49 0-6.2 2.19-6.2 4.92 0 2.58 2.5 4.23 6.37 4.15h.12l.42-.02-.1.28v.03l.09.22v.03l.02.04.02.07.02.04.01.05.02.05.02.07.02.08.02.06.02.08.02.09.02.09.03.1.01.06.03.1.02.1.03.15.02.07.02.11.03.12.02.12.04.17.02.15.04.2.02.1.03.15.03.15.04.22.04.23.04.23.04.24.04.24.04.26.04.26.04.2.05.34.02.14.06.36.04.3.04.22.04.31.03.16a10.76 10.76 0 0 0 6.53-3.41l.05-.06-.24-.89-.64-2.37-.39-1.47-.35-1.3-.21-.78-.14-.5-.08-.3-.07-.26-.03-.11-.02-.07-.01-.03v-.03a6.04 6.04 0 0 0-2.05-2.97 6.75 6.75 0 0 0-4.25-1.35M8.47 19.3a.59.59 0 0 0-.72.4v.01l-.53 1.96q.5.24 1.01.43l.08.03.57-2.11V20a.59.59 0 0 0-.41-.7m3.26-1.43a.59.59 0 0 0-.71.4v.01l-.8 2.96v.01a.59.59 0 0 0 1.12.3l.8-2.96v-.02l.02-.06v-.02l-.02-.1-.02-.14-.02-.08a.58.58 0 0 0-.37-.3Zm-5.55-3.04a1 1 0 0 0-.04.09v.02l-.8 2.95v.02a.59.59 0 0 0 1.13.3v-.01l.72-2.68a5.3 5.3 0 0 1-1.01-.7Zm-1.9-3.4a.59.59 0 0 0-.72.4v.02l-.8 2.95v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7m17.87-.68a.59.59 0 0 0-.72.4v.02l-.8 2.95v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7M2.55 6.81a10.7 10.7 0 0 0-1.26 3.93.59.59 0 0 0 1-.22v-.02l.8-2.95v-.01a.59.59 0 0 0-.55-.73m17.59.02a.59.59 0 0 0-.72.4v.01l-.8 2.96v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7Zm-7.85 1.93a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5M6.01 4.03a.59.59 0 0 0-.71.4v.02L4.5 7.4v.01a.59.59 0 0 0 1.12.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7Zm10.24.56a.59.59 0 0 0-.71.4V5L15 7q.52.26.99.6l.05.04.62-2.32V5.3a.59.59 0 0 0-.41-.7m-5.21-3.34a11 11 0 0 0-1.12.16l-.07.01L9.1 4.2v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.6.6 0 0 0 0-.27m7.34 2.04-.16.58v.02a.59.59 0 0 0 1.13.3V4.2l.02-.07a11 11 0 0 0-.92-.77zm-4.64-1.94-.28 1.05a.59.59 0 0 0 1.13.31v-.01l.3-1.1q-.52-.15-1.06-.24z"/>',
|
||||
jsr: '<path d="M3.7 5.54v3.7H0v7.38h7.38v1.84h12.93v-3.7H24V7.39h-7.38V5.54Zm1.84 1.85h1.85v7.38H1.84v-3.7h1.84v1.85h1.85zm3.7 0h5.53v1.84h-3.7v1.85h3.7v5.53H9.23v-1.84h3.7v-1.85h-3.7Zm7.38 1.84h5.53v3.7h-1.84v-1.85h-1.85v5.54h-1.84z"/>',
|
||||
nostr:
|
||||
'<path d="M21.6 10.6v9.7a.7.7 0 0 1-.6.6h-8a.7.7 0 0 1-.6-.6v-1.8c0-2.2.3-4.4.8-5.3a2 2 0 0 1 1.3-1c1-.4 2.9-.2 3.7-.2 0 0 2.3 0 2.3-1.3 0-1-1-1-1-1-1.2 0-2 0-2.6-.2-1-.4-1-1.1-1-1.3 0-2.7-4-3-7.5-2.4-3.9.7 0 6.2 0 13.5v1a.7.7 0 0 1-.7.6H3.8a.7.7 0 0 1-.6-.6V3.5a.7.7 0 0 1 .6-.6h3.7a.7.7 0 0 1 .7.6c0 .6.6.9 1 .6 1.3-1 3-1.5 5-1.5 4.2 0 7.4 2.5 7.4 8zm-7-2a1.4 1.4 0 1 0-2.9 0 1.4 1.4 0 0 0 2.9 0z"/>',
|
||||
backstage:
|
||||
'<path d="M18.4 9.3a4.4 4.4 0 0 0 .7-.5l.1-.1a4.5 4.5 0 0 0 .8-1.1l.3-1c0-1-.6-2-2-3L12.7.4 6 6.6l-4.3 4 6 3.7a6.1 6.1 0 0 0 3 .9c1.5 0 2.8-.5 3.7-1.4 1-1 1.4-2.3.8-3.4a2.8 2.8 0 0 0-.4-.5l1 .1a4.6 4.6 0 0 0 1.8-.3 4.5 4.5 0 0 0 .7-.3zm-5.5 3.3c-1 1-2.8 1.2-4.2.4l-4.1-2.5 3.7-3.6 4.2 2.6c1.5.9 1.4 2.1.4 3.1zm.5-4.5-4-2.3L13 2.4l3.8 2.3c1.4.9 1.6 2 .6 3a3.3 3.3 0 0 1-4 .4zM15 18.5c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4v1.4l6 3.6a6.1 6.1 0 0 0 3 .9c1.5 0 2.8-.5 3.7-1.4.7-.7 1.1-1.5 1.1-2.3l-.6.6zm0-2.1c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4V15l6 3.6a6.1 6.1 0 0 0 3 .9c1.5 0 2.8-.5 3.7-1.4.7-.7 1.1-1.5 1.1-2.3v-.1l-.6.7zm0-2.1c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4v1.3l6 3.6a6.1 6.1 0 0 0 3 1c1.5 0 2.8-.6 3.7-1.5.7-.7 1.1-1.5 1.1-2.3l-.6.7zm4.6 1.4a5.2 5.2 0 0 1-3.3 1.4v1.5a4.5 4.5 0 0 0 2.8-1.3c.8-.7 1.2-1.5 1.2-2.3v-.1l-.7.8zm-4.6 5c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4v1.3l6 3.6a6.1 6.1 0 0 0 3 1c1.5 0 2.8-.5 3.7-1.5.7-.6 1.1-1.5 1.1-2.2V20l-.6.7zM19.7 9l-.1.2-1.2.8a5.2 5.2 0 0 1-1.5.5 5.2 5.2 0 0 1-.8 0l.1.4V12a4.6 4.6 0 0 0 1.5-.3A4.4 4.4 0 0 0 19 11l.1-.1a4.5 4.5 0 0 0 .8-1.1 2.6 2.6 0 0 0 .3-1.1v-.1l-.2.1-.4.6zm0 4.3a6 6 0 0 1-.1.1 5.2 5.2 0 0 1-3.3 1.5v1.4a4.5 4.5 0 0 0 2.8-1.2l1-1.2.2-1v-.1l-.2.2a4.8 4.8 0 0 1-.4.5zm0-2.1-.1.1a4.8 4.8 0 0 1-.6.5 5.2 5.2 0 0 1-2.7 1v1.4A4.5 4.5 0 0 0 19 13h.1a4.2 4.2 0 0 0 .8-1.2l.3-.9v-.3a4 4 0 0 1-.2.2l-.4.5z"/>',
|
||||
confluence:
|
||||
'<path d="M.85 18.07.1 19.32a.76.76 0 0 0-.1.28.76.76 0 0 0 .02.28.75.75 0 0 0 .33.46l4.97 3.07a.76.76 0 0 0 .86-.03.75.75 0 0 0 .2-.23l.73-1.23c1.97-3.23 3.97-2.83 7.54-1.14l4.93 2.34a.76.76 0 0 0 .6.03.76.76 0 0 0 .43-.4l2.36-5.35a.75.75 0 0 0 .02-.57.77.77 0 0 0-.38-.43c-1.04-.49-3.1-1.45-4.96-2.36C10.9 10.8 5.2 11 .85 18.07Z"/><path d="m23.15 5.94.75-1.25a.77.77 0 0 0 .08-.57.76.76 0 0 0-.13-.27.76.76 0 0 0-.22-.2L18.67.6a.76.76 0 0 0-.29-.1.77.77 0 0 0-.57.13.77.77 0 0 0-.2.23l-.73 1.22c-1.98 3.25-3.96 2.86-7.53 1.16L4.42.89a.78.78 0 0 0-.59-.03.76.76 0 0 0-.26.15.76.76 0 0 0-.18.24L1.02 6.61a.77.77 0 0 0-.02.57c.04.1.09.18.15.25a.76.76 0 0 0 .24.18c1.04.5 3.11 1.45 4.96 2.36 6.73 3.26 12.44 3.04 16.8-4.03z"/>',
|
||||
jira: '<path d="M7.75 16.3H5.62C2.4 16.3.09 14.31.09 11.43h11.47c.6 0 .98.42.98 1.02V24c-2.87 0-4.79-2.32-4.79-5.56Zm5.67-5.74h-2.14c-3.21 0-5.52-1.94-5.52-4.82h11.47c.6 0 1.01.38 1.01.98v11.54c-2.87 0-4.82-2.32-4.82-5.56zm5.7-5.7h-2.14c-3.21 0-5.52-1.97-5.52-4.86h11.47c.6 0 .98.42.98.99v11.54c-2.87 0-4.8-2.32-4.8-5.56z"/>',
|
||||
storybook:
|
||||
'<path d="m20.35 0-1.32.08.1 2.78a.18.18 0 0 1-.3.14l-.9-.7-1.05.8a.18.18 0 0 1-.25-.03.18.18 0 0 1-.04-.12l.12-2.72-13.21.82A1.2 1.2 0 0 0 2.37 2.3l.74 19.82a1.2 1.2 0 0 0 1.15 1.16l16.11.72h.06c.66 0 1.2-.54 1.2-1.2V1.12A1.2 1.2 0 0 0 20.35 0zm-7.99 4.08c3.14 0 4.86 1.68 4.86 4.88-.42.33-3.59.56-3.59.09.07-1.8-.73-1.87-1.18-1.87-.42 0-1.13.12-1.13 1.08 0 2.37 6.1 2.24 6.1 7.02 0 2.69-2.18 4.17-4.97 4.17-2.87 0-5.38-1.16-5.1-5.2.11-.47 3.77-.35 3.77 0-.05 1.67.33 2.16 1.29 2.16.73 0 1.07-.4 1.07-1.09 0-2.43-6.02-2.51-6.02-6.97 0-2.56 1.76-4.27 4.9-4.27z"/>',
|
||||
vscode:
|
||||
'<path d="M23.15 2.59 18.2.2a1.5 1.5 0 0 0-1.7.29L7.04 9.13 2.93 6a1 1 0 0 0-1.28.06L.33 7.26a1 1 0 0 0 0 1.48L3.9 12 .32 15.26a1 1 0 0 0 0 1.48l1.33 1.2a1 1 0 0 0 1.28.06l4.12-3.13 9.46 8.63c.44.45 1.13.57 1.7.29l4.94-2.38c.52-.25.85-.77.85-1.35V3.94c0-.58-.33-1.1-.85-1.36ZM18 17.45 10.82 12 18 6.55v10.9Z"/>',
|
||||
jetbrains:
|
||||
'<path fill-rule="evenodd" d="M2.05 24h9.33c1.17 0 2.28-.52 3.11-1.47l5.22-5.97A5.45 5.45 0 0 0 21 13.01V2.34C21 1.05 20.08 0 18.95 0H9.62C8.45 0 7.34.52 6.5 1.47L1.29 7.43A5.45 5.45 0 0 0 0 11v10.66C0 22.95.92 24 2.05 24ZM3.47 6.5l3.72-4.25A3.2 3.2 0 0 1 9.62 1.1h9.33c.6 0 1.08.56 1.08 1.23V13a4.3 4.3 0 0 1-1 2.77l-3.73 4.25V6.51H3.47Zm-.17.2v13.52h11.83l-1.32 1.51a3.22 3.22 0 0 1-2.43 1.15H2.05c-.6 0-1.08-.56-1.08-1.24V11a4.3 4.3 0 0 1 1-2.77L3.3 6.71Zm6.6 10.43H4.8v1.37h5.1v-1.37Z"/>',
|
||||
zed: '<path d="M2.25 1.5a.75.75 0 0 0-.75.75v16.5H0V2.25C0 1.01 1 0 2.25 0h20.1c1 0 1.5 1.21.79 1.92L10.76 14.3h3.49v-1.55h1.5v1.92c0 .62-.5 1.13-1.13 1.13H9.27L6.7 18.37h11.69V9h1.5v9.38c0 .82-.68 1.5-1.5 1.5H5.18L2.57 22.5h19.19c.41 0 .75-.34.75-.75V5.25H24v16.5c0 1.24-1 2.25-2.25 2.25H1.65c-1 0-1.5-1.21-.79-1.92L13.19 9.75H9.75v1.5h-1.5V9.37c0-.62.5-1.12 1.12-1.12h5.32l2.62-2.62H5.63V15h-1.5V5.63c0-.83.67-1.5 1.5-1.5H18.8l2.63-2.63H2.25Z"/>',
|
||||
vim: '<path d="m19.83 16.57.45-.49h1.25l.29.4-1.19 3.84h.46l-.07.2h-1.67l1.05-3.34h-1.89l-1 3.18h.4l-.08.16h-1.5l1.04-3.33H15.4l-1 3.13h.41l-.06.2h-1.56l1.42-4.18h-.55l.09-.26h1.54l.49.5h.85l.46-.51h1l.45.5h.9ZM6 20.27H4.4l-.25-.14V3.65H2.98l-.1-.1v-1.1l.14-.14h6.91l.2.2v1.04L10 3.7H8.99v8.14l8.25-8.14H15.3l-.17-.18V2.44l.12-.1h7.02l.12.13v1l-9.46 9.7H12.5a.24.24 0 0 0-.11.06l-.32.28a.25.25 0 0 0-.07.1l-.28.78-5.74 5.88Zm7.45-6.75.14.14-.26.87-.2.2h-.91l-.17-.16.29-.82.27-.23h.84Zm-3.47 7.04 1.48-4.22h-.47l.28-.29h1.56l-1.47 4.27h.59l-.08.24H9.97ZM23.25 12h-.03l-4.05-4.05 4.04-4.14V2.12l-.61-.6H14.9l-.61.56v.98L12 .78V.75L12 .77l-.01-.02v.03l-1.21 1.2-.5-.5H2.65l-.6.65V3.9l.58.58h.67v4.97L.78 12H.75l.02.01-.02.01h.03l2.53 2.54v6.06l.85.5h2.18l1.74-1.8 3.9 3.91v.03l.02-.02.01.02v-.03l2.35-2.35h.46c.1 0 .2-.07.23-.17l.14-.4.01-.07c0-.06-.01-.11-.04-.15l1.37-1.37-.58 1.84v.07c0 .11.06.2.17.24l.07.01h1.7c.11 0 .2-.06.24-.15l.15-.37a.25.25 0 0 0 0-.2.25.25 0 0 0-.23-.15h-.07l.79-2.47h1.15l-.95 3.02-.01.07c0 .11.07.2.17.24l.08.01h1.88c.1 0 .2-.07.23-.16l.15-.4a.25.25 0 0 0-.15-.32.24.24 0 0 0-.08-.02h-.14l1.06-3.44.02-.08c0-.05-.02-.1-.05-.14l-.36-.48a.25.25 0 0 0-.2-.1h-1.34a.25.25 0 0 0-.18.08l-.37.41h-.59l-.04-.04 4.17-4.17h.03-.02l.02-.02Z"/>',
|
||||
figma:
|
||||
'<path d="M5.77 8.28A4.44 4.44 0 0 1 8.19.1h7.6a4.44 4.44 0 0 1 2.43 8.17 4.44 4.44 0 0 1-2.42 8.16h-.08a4.42 4.42 0 0 1-3-1.17v4.14a4.5 4.5 0 0 1-4.51 4.48 4.46 4.46 0 0 1-2.44-8.17 4.44 4.44 0 0 1 0-7.44ZM12.7 12a3 3 0 0 0 3 3h.09a3 3 0 1 0 0-6h-.08a3 3 0 0 0-3 3Zm-1.43-3H8.19a3 3 0 0 0-.01 6h3.1V9Zm-3.09 7.44h-.01a3 3 0 1 0 .03 6.01 3.06 3.06 0 0 0 3.07-3.04v-2.97H8.19Zm3.09-8.88V1.55H8.19a3 3 0 1 0 0 6.01h3.09Zm4.52 0a3 3 0 1 0 0-6.01H12.7v6.01h3.09Z"/>',
|
||||
sketch:
|
||||
'<path d="m.29 8.99 4.8-6.53a.6.6 0 0 1 .42-.24l6.42-.71a.6.6 0 0 1 .14 0l6.42.71a.6.6 0 0 1 .42.24L23.7 9a.6.6 0 0 1-.02.75L12.34 22.86a.45.45 0 0 1-.68 0L.31 9.74a.6.6 0 0 1-.02-.75Zm13.36-5.55a.15.15 0 0 0-.21.2l3.04 3.75a.3.3 0 0 1-.23.49h-8.5a.3.3 0 0 1-.23-.5l3.05-3.74a.15.15 0 0 0-.22-.2L5.8 7.72a.3.3 0 0 1-.5-.24l.21-3.04a.15.15 0 0 0-.3-.05l-.99 3.48a.75.75 0 0 1-.48.5l-2.19.71a.15.15 0 0 0 .05.3h2.1a.75.75 0 0 1 .64.34l5.56 8.74a.23.23 0 0 0 .39-.22L6.3 10.02a.45.45 0 0 1 .4-.64h10.57a.45.45 0 0 1 .4.64l-3.98 8.22a.22.22 0 0 0 .4.22l5.55-8.74a.75.75 0 0 1 .64-.34h2.04a.15.15 0 0 0 .04-.3l-2.12-.7a.75.75 0 0 1-.48-.51l-1-3.48a.15.15 0 0 0-.17-.1.15.15 0 0 0-.12.15l.22 3.04a.3.3 0 0 1-.51.24l-4.54-4.28Z"/>',
|
||||
npm: '<path d="M1.76 0h20.48a1.76 1.76 0 0 1 1.76 1.76v20.48a1.76 1.76 0 0 1-1.76 1.76H1.76A1.76 1.76 0 0 1 0 22.24V1.76A1.76 1.76 0 0 1 1.76 0zM5.11 19.16h6.93V8.8h3.47v10.36h3.47V5.34H5.13v13.82z"></path>',
|
||||
sourcehut:
|
||||
'<path d="M12 20a8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8 8 8 0 0 1-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2"/>',
|
||||
substack:
|
||||
'<path d="M22.5 8.2h-21V5.4h21v2.8zm-21 2.6V24L12 18.1 22.5 24V10.8h-21zM22.5 0h-21v2.8h21V0z"/>',
|
||||
};
|
||||
|
||||
export const Icons = {
|
||||
...BuiltInIcons,
|
||||
...FileIcons,
|
||||
};
|
||||
|
||||
export type StarlightIcon = keyof typeof Icons;
|
||||
@ -6,33 +6,10 @@ import { I18N_SOURCE_LANGUAGE, IMAGE_SETTINGS } from "config/config.js";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { glob } from 'glob';
|
||||
import { globBase, globParent, pathInfoEx, pathInfo } from "@polymech/commons";
|
||||
import { globBase, pathInfo } from "@polymech/commons";
|
||||
import { gallery } from "@/base/media.js";
|
||||
import ExifReader from 'exifreader';
|
||||
// Path resolution function (simplified version)
|
||||
function resolveImagePath(src: string, entryPath?: string, astroUrl?: URL): string {
|
||||
// External URLs and absolute paths
|
||||
if (src.startsWith('/') || src.startsWith('http')) {
|
||||
return src;
|
||||
}
|
||||
|
||||
// Relative paths
|
||||
if (src.startsWith('.') && entryPath) {
|
||||
const contentDir = entryPath.substring(0, entryPath.lastIndexOf('/'));
|
||||
const basePath = path.join(process.cwd(), 'src', 'content', contentDir);
|
||||
|
||||
try {
|
||||
const absolutePath = path.resolve(basePath, src);
|
||||
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
|
||||
return path.relative(process.cwd(), absolutePath).replace(/\\/g, '/');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[MasonryGallery] Error resolving path for ${src}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
import { resolveImagePath } from "../utils/path-resolution.js";
|
||||
|
||||
// Extract date from EXIF data or file stats
|
||||
async function extractImageDate(filePath: string): Promise<{ year: number, dateTaken: Date }> {
|
||||
@ -164,27 +141,15 @@ if (globPattern) {
|
||||
}
|
||||
}
|
||||
|
||||
// Use entryPath if provided, otherwise derive from URL
|
||||
let finalContentPath;
|
||||
if (entryPath) {
|
||||
// entryPath is like "resources/internal/Masonry", we want the directory part
|
||||
const lastSlashIndex = entryPath.lastIndexOf('/');
|
||||
finalContentPath = lastSlashIndex > -1 ? entryPath.substring(0, lastSlashIndex) : entryPath;
|
||||
} else {
|
||||
finalContentPath = contentPath;
|
||||
}
|
||||
// Use entryPath if provided, otherwise derive from URL (same as RelativeGallery)
|
||||
const finalContentPath = entryPath ? entryPath.substring(0, entryPath.lastIndexOf('/')) : contentPath;
|
||||
const contentDir = path.join(process.cwd(), 'src', 'content', finalContentPath);
|
||||
|
||||
console.log(`[MasonryGallery] Searching for files with pattern: ${globPattern}`);
|
||||
console.log(`[MasonryGallery] Content directory: ${contentDir}`);
|
||||
|
||||
let matchedFiles = glob.sync(globPattern, { cwd: contentDir, absolute: true });
|
||||
console.log(`[MasonryGallery] Found ${matchedFiles.length} files:`, matchedFiles);
|
||||
|
||||
if (matchedFiles.length === 0) {
|
||||
const pathInfo2 = pathInfo(globPattern, false, path.join(contentDir, globBase(globPattern).base));
|
||||
matchedFiles = pathInfo2.FILES;
|
||||
console.log(`[MasonryGallery] Fallback pathInfo found ${matchedFiles.length} files:`, matchedFiles);
|
||||
}
|
||||
|
||||
// Process matched files
|
||||
@ -196,11 +161,6 @@ if (globPattern) {
|
||||
// Convert to a path that resolveImagePath can handle (like "./gallery/image.jpg")
|
||||
const relativeSrc = `./${relativeFromContentDir.replace(/\\/g, '/')}`;
|
||||
|
||||
console.log(`[MasonryGallery] Processing file: ${filePath}`);
|
||||
console.log(`[MasonryGallery] Content dir: ${contentDir}`);
|
||||
console.log(`[MasonryGallery] Relative from content: ${relativeFromContentDir}`);
|
||||
console.log(`[MasonryGallery] Relative src: ${relativeSrc}`);
|
||||
|
||||
// Extract date information
|
||||
const { year, dateTaken } = await extractImageDate(filePath);
|
||||
|
||||
@ -247,12 +207,12 @@ if (globPattern) {
|
||||
|
||||
// Apply maxItems constraint and resolve image paths
|
||||
const resolvedImages = allImages.slice(0, maxItems).map(image => {
|
||||
const originalSrc = image.src;
|
||||
const resolvedSrc = resolveImagePath(image.src, entryPath, Astro.url);
|
||||
console.log(`[MasonryGallery] Path resolution: "${originalSrc}" -> "${resolvedSrc}"`);
|
||||
// Ensure the path is absolute (starts with /) for proper browser resolution
|
||||
const absoluteSrc = resolvedSrc.startsWith('/') ? resolvedSrc : `/${resolvedSrc}`;
|
||||
return {
|
||||
...image,
|
||||
src: resolvedSrc
|
||||
src: absoluteSrc
|
||||
};
|
||||
});
|
||||
|
||||
@ -279,15 +239,57 @@ sortedYears.forEach(year => {
|
||||
});
|
||||
});
|
||||
|
||||
// Create flat array with year info for lightbox
|
||||
const finalImages = resolvedImages;
|
||||
// Create flat array with proper year/date sorting for lightbox
|
||||
const finalImages = sortedYears.flatMap(year => imagesByYear[year]);
|
||||
|
||||
console.log('[MasonryGallery] Sample final image paths:', finalImages.slice(0, 3).map(img => ({ src: img.src, title: img.title })));
|
||||
|
||||
|
||||
console.log(`[MasonryGallery] Images grouped by years:`, sortedYears, imagesByYear);
|
||||
console.log(`[MasonryGallery] Final images array:`, finalImages.length, finalImages);
|
||||
---
|
||||
|
||||
<div
|
||||
x-data={`{ open: false, currentIndex: 0, total: ${finalImages.length}, lightboxLoaded: false, touchStartX: 0, touchEndX: 0, minSwipeDistance: 50, isSwiping: false, images: ${JSON.stringify(finalImages)}, openLightbox(index) { this.currentIndex = index; this.preloadAndOpen(); }, handleSwipe() { if (!this.isSwiping) return; const swipeDistance = this.touchEndX - this.touchStartX; if (Math.abs(swipeDistance) >= this.minSwipeDistance) { if (swipeDistance > 0 && this.currentIndex > 0) { this.currentIndex--; this.preloadAndOpen(); } else if (swipeDistance < 0 && this.currentIndex < this.total - 1) { this.currentIndex++; this.preloadAndOpen(); } } this.isSwiping = false; }, preloadAndOpen() { if (this.isSwiping) return; this.lightboxLoaded = false; let img = new Image(); img.src = this.images[this.currentIndex].src; img.onload = () => { this.lightboxLoaded = true; this.open = true; }; } }`}
|
||||
x-data={`{
|
||||
open: false,
|
||||
currentIndex: 0,
|
||||
total: ${finalImages.length},
|
||||
lightboxLoaded: false,
|
||||
touchStartX: 0,
|
||||
touchEndX: 0,
|
||||
minSwipeDistance: 50,
|
||||
isSwiping: false,
|
||||
images: ${JSON.stringify(finalImages)},
|
||||
openLightbox(index) {
|
||||
this.currentIndex = index;
|
||||
this.preloadAndOpen();
|
||||
},
|
||||
handleSwipe() {
|
||||
if (!this.isSwiping) return;
|
||||
const swipeDistance = this.touchEndX - this.touchStartX;
|
||||
if (Math.abs(swipeDistance) >= this.minSwipeDistance) {
|
||||
if (swipeDistance > 0 && this.currentIndex > 0) {
|
||||
this.currentIndex--;
|
||||
this.preloadAndOpen();
|
||||
} else if (swipeDistance < 0 && this.currentIndex < this.total - 1) {
|
||||
this.currentIndex++;
|
||||
this.preloadAndOpen();
|
||||
}
|
||||
}
|
||||
this.isSwiping = false;
|
||||
},
|
||||
preloadAndOpen() {
|
||||
if (this.isSwiping) return;
|
||||
this.lightboxLoaded = false;
|
||||
let img = new Image();
|
||||
const currentImage = this.images[this.currentIndex];
|
||||
|
||||
// Use the original resolved src - imagetools will handle the optimization
|
||||
img.src = currentImage.src;
|
||||
img.onload = () => {
|
||||
this.lightboxLoaded = true;
|
||||
this.open = true;
|
||||
};
|
||||
}
|
||||
}`}
|
||||
@keydown.escape.window="open = false"
|
||||
@keydown.window="if(open){ if($event.key === 'ArrowRight' && currentIndex < total - 1){ currentIndex++; preloadAndOpen(); } else if($event.key === 'ArrowLeft' && currentIndex > 0){ currentIndex--; preloadAndOpen(); } }"
|
||||
class="masonry-gallery"
|
||||
@ -299,7 +301,7 @@ console.log(`[MasonryGallery] Final images array:`, finalImages.length, finalIma
|
||||
const startIndex = finalImages.findIndex(img => img.year === year);
|
||||
|
||||
return (
|
||||
<div key={year} class="year-group">
|
||||
<div class="year-group">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 sticky top-4 bg-white/90 backdrop-blur-sm py-2 px-4 rounded-lg shadow-sm z-10">
|
||||
{year}
|
||||
<span class="text-sm font-normal text-gray-500 ml-2">
|
||||
@ -358,6 +360,10 @@ console.log(`[MasonryGallery] Final images array:`, finalImages.length, finalIma
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -491,4 +497,19 @@ console.log(`[MasonryGallery] Final images array:`, finalImages.length, finalIma
|
||||
.masonry-gallery [x-show="open"] {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Year group styling */
|
||||
.year-group {
|
||||
scroll-margin-top: 6rem;
|
||||
}
|
||||
|
||||
/* Sticky year headers */
|
||||
.year-group h2 {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
z-index: 20;
|
||||
margin-bottom: 1.5rem;
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
||||
@ -1,160 +0,0 @@
|
||||
# MasonryGallery Component
|
||||
|
||||
A responsive masonry gallery component with lightbox functionality, glob pattern support, and configurable constraints.
|
||||
|
||||
## Features
|
||||
|
||||
- **Masonry Layout**: CSS Grid-based masonry layout with no fixed height
|
||||
- **Lightbox**: Full lightbox functionality inherited from GalleryK with swipe support
|
||||
- **Glob Support**: Auto-load images using glob patterns (relative to content directory)
|
||||
- **Constraints**: Configurable max items, max width, and max height
|
||||
- **Responsive**: Adapts to different screen sizes
|
||||
- **Metadata**: Supports companion JSON and Markdown files for image metadata
|
||||
|
||||
## Props
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
images?: Image[]; // Manual image array
|
||||
glob?: string; // Glob pattern for auto-loading (e.g., "*.jpg", "**/*.{jpg,png}")
|
||||
maxItems?: number; // Maximum number of images (default: 50)
|
||||
maxWidth?: string; // Max width per image (default: "300px")
|
||||
maxHeight?: string; // Max height per image (default: "400px")
|
||||
entryPath?: string; // Content entry path for resolving relative images
|
||||
gallerySettings?: { // Gallery display settings
|
||||
SIZES_REGULAR?: string;
|
||||
SIZES_THUMB?: string;
|
||||
SIZES_LARGE?: string;
|
||||
SHOW_TITLE?: boolean;
|
||||
SHOW_DESCRIPTION?: boolean;
|
||||
};
|
||||
lightboxSettings?: { // Lightbox display settings
|
||||
SIZES_REGULAR?: string;
|
||||
SIZES_THUMB?: string;
|
||||
SIZES_LARGE?: string;
|
||||
SHOW_TITLE?: boolean;
|
||||
SHOW_DESCRIPTION?: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage with Manual Images
|
||||
|
||||
```astro
|
||||
---
|
||||
import MasonryGallery from "./MasonryGallery.astro";
|
||||
|
||||
const images = [
|
||||
{ src: "/images/photo1.jpg", alt: "Photo 1", title: "Beautiful Landscape" },
|
||||
{ src: "/images/photo2.jpg", alt: "Photo 2", title: "City Skyline" },
|
||||
// ... more images
|
||||
];
|
||||
---
|
||||
|
||||
<MasonryGallery images={images} maxItems={20} />
|
||||
```
|
||||
|
||||
### Using Glob Patterns
|
||||
|
||||
```astro
|
||||
---
|
||||
import MasonryGallery from "./MasonryGallery.astro";
|
||||
---
|
||||
|
||||
<!-- Load all JPG images from current content directory -->
|
||||
<MasonryGallery
|
||||
glob="*.jpg"
|
||||
maxItems={30}
|
||||
maxWidth="400px"
|
||||
maxHeight="500px"
|
||||
/>
|
||||
|
||||
<!-- Load images from subdirectories -->
|
||||
<MasonryGallery
|
||||
glob="gallery/**/*.{jpg,png,webp}"
|
||||
maxItems={50}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Custom Settings
|
||||
|
||||
```astro
|
||||
---
|
||||
import MasonryGallery from "./MasonryGallery.astro";
|
||||
|
||||
const gallerySettings = {
|
||||
SHOW_TITLE: true,
|
||||
SHOW_DESCRIPTION: true,
|
||||
SIZES_REGULAR: "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
};
|
||||
|
||||
const lightboxSettings = {
|
||||
SHOW_TITLE: true,
|
||||
SHOW_DESCRIPTION: false,
|
||||
SIZES_LARGE: "90vw"
|
||||
};
|
||||
---
|
||||
|
||||
<MasonryGallery
|
||||
glob="**/*.jpg"
|
||||
maxItems={25}
|
||||
maxWidth="350px"
|
||||
maxHeight="450px"
|
||||
gallerySettings={gallerySettings}
|
||||
lightboxSettings={lightboxSettings}
|
||||
/>
|
||||
```
|
||||
|
||||
## Metadata Support
|
||||
|
||||
The component supports companion files for rich metadata:
|
||||
|
||||
### JSON Metadata (`image.json`)
|
||||
```json
|
||||
{
|
||||
"alt": "Beautiful mountain landscape at sunset",
|
||||
"title": "Mountain Sunset",
|
||||
"description": "A breathtaking view of mountains during golden hour"
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown Metadata (`image.md`)
|
||||
```markdown
|
||||
A detailed description of the image that can include **markdown formatting**.
|
||||
|
||||
This will be used as the description if no JSON description is provided.
|
||||
```
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
- **Desktop**: Minimum 250px columns with auto-fill
|
||||
- **Tablet**: Minimum 200px columns
|
||||
- **Mobile**: Minimum 150px columns
|
||||
|
||||
## Keyboard Controls (in Lightbox)
|
||||
|
||||
- **Escape**: Close lightbox
|
||||
- **Arrow Left**: Previous image
|
||||
- **Arrow Right**: Next image
|
||||
|
||||
## Touch Controls (in Lightbox)
|
||||
|
||||
- **Swipe Left**: Next image
|
||||
- **Swipe Right**: Previous image
|
||||
|
||||
## CSS Classes
|
||||
|
||||
The component uses the following CSS classes for styling:
|
||||
|
||||
- `.masonry-gallery`: Main container
|
||||
- `.masonry-container`: Grid container
|
||||
- `.masonry-item`: Individual image container
|
||||
- `.line-clamp-2`: Text truncation utility
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Modern browsers with CSS Grid support
|
||||
- Graceful fallback for browsers without masonry support
|
||||
- Progressive enhancement for touch devices
|
||||
178
packages/polymech/src/components/RelativeGallery.astro
Normal file
178
packages/polymech/src/components/RelativeGallery.astro
Normal file
@ -0,0 +1,178 @@
|
||||
---
|
||||
import LGallery from "./GalleryK.astro";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { glob } from 'glob';
|
||||
import { globBase, pathInfo } from "@polymech/commons";
|
||||
import { GalleryImage } from "@/base/images.js";
|
||||
import { gallery } from "@/base/media.js";
|
||||
import { resolveImagePath } from '@/utils/path-resolution';
|
||||
|
||||
export interface Props {
|
||||
images?: GalleryImage[];
|
||||
glob?: string; // New glob pattern support
|
||||
gallerySettings?: any;
|
||||
lightboxSettings?: any;
|
||||
entryPath?: string;
|
||||
}
|
||||
|
||||
const { images, glob: globPattern, entryPath, ...props } = Astro.props;
|
||||
|
||||
// Get current content directory dynamically from URL
|
||||
const currentUrl = Astro.url.pathname;
|
||||
const pathSegments = currentUrl.split('/').filter(Boolean);
|
||||
|
||||
// Handle locale-aware URLs (e.g., /en/resources/test/ vs /resources/test/)
|
||||
// Check if first segment is a locale (2-character language code)
|
||||
const isLocaleFirst = pathSegments.length > 0 && pathSegments[0].length === 2 && /^[a-z]{2}$/.test(pathSegments[0]);
|
||||
|
||||
// Determine content subdirectory (e.g., 'resources', 'blog', etc.)
|
||||
let contentSubdir = 'resources'; // fallback
|
||||
if (pathSegments.length >= 1) {
|
||||
contentSubdir = isLocaleFirst && pathSegments.length > 1
|
||||
? pathSegments[1] // Skip locale: /en/resources/test/ -> "resources"
|
||||
: pathSegments[0]; // No locale: /resources/test/ -> "resources"
|
||||
}
|
||||
|
||||
// Get the nested content directory (e.g., 'cassandra' from /resources/cassandra/home/)
|
||||
// We need to distinguish between:
|
||||
// 1. /resources/test -> root-level file test.mdx -> contentPath = "resources"
|
||||
// 2. /es/resources/test -> root-level file test.mdx with locale -> contentPath = "resources"
|
||||
// 3. /resources/cassandra/home -> nested file cassandra/home.mdx -> contentPath = "resources/cassandra"
|
||||
// 4. /es/resources/cassandra/home -> nested file with locale -> contentPath = "resources/cassandra"
|
||||
let contentPath = contentSubdir;
|
||||
|
||||
// Determine the minimum segments needed for nested directories
|
||||
// Without locale: resources/folder/file = 3 segments
|
||||
// With locale: es/resources/folder/file = 4 segments
|
||||
const minNestedSegments = isLocaleFirst ? 4 : 3;
|
||||
|
||||
if (pathSegments.length >= minNestedSegments) {
|
||||
// Only treat as nested if we have enough segments for actual subdirectory
|
||||
const nestedDirIndex = isLocaleFirst ? 2 : 1; // Account for locale
|
||||
if (pathSegments.length > nestedDirIndex) {
|
||||
const nestedDir = pathSegments[nestedDirIndex];
|
||||
contentPath = `${contentSubdir}/${nestedDir}`;
|
||||
}
|
||||
}
|
||||
|
||||
const contentDir = path.join(process.cwd(), 'src', 'content', contentPath);
|
||||
|
||||
let allImages: GalleryImage[] = [];
|
||||
|
||||
// Handle glob patterns using the gallery function from media.ts
|
||||
if (globPattern) {
|
||||
try {
|
||||
// For content collections, we need to create a mock product structure
|
||||
// that the gallery function can work with
|
||||
const mockProductPath = `content/${contentSubdir}`;
|
||||
|
||||
// Since gallery expects a specific directory structure, let's use our custom approach
|
||||
// but leverage the media processing logic where possible
|
||||
let matchedFiles = glob.sync(globPattern, { cwd: contentDir, absolute: true });
|
||||
|
||||
if (matchedFiles.length === 0) {
|
||||
const pathInfo2 = pathInfo(globPattern, false, path.join(contentDir, globBase(globPattern).base));
|
||||
matchedFiles = pathInfo2.FILES;
|
||||
}
|
||||
|
||||
// Process each file to get rich metadata (similar to media.ts approach)
|
||||
const globImages: GalleryImage[] = await Promise.all(
|
||||
matchedFiles.map(async (filePath) => {
|
||||
const relativePath = path.relative(process.cwd(), filePath);
|
||||
const fileName = path.basename(filePath, path.extname(filePath));
|
||||
const webPath = `/${relativePath.replace(/\\/g, '/')}`;
|
||||
|
||||
// Create basic image structure
|
||||
const image: GalleryImage = {
|
||||
name: fileName,
|
||||
url: webPath,
|
||||
src: webPath,
|
||||
thumb: webPath,
|
||||
responsive: webPath,
|
||||
alt: `Image: ${fileName}`,
|
||||
title: fileName.replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
||||
description: `Auto-loaded from ${globPattern}`,
|
||||
keywords: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
gps: { lon: 0, lat: 0 },
|
||||
meta: {
|
||||
format: path.extname(filePath).toLowerCase().slice(1),
|
||||
width: 0,
|
||||
height: 0,
|
||||
space: '',
|
||||
channels: 0,
|
||||
depth: '',
|
||||
density: 0,
|
||||
chromaSubsampling: '',
|
||||
isProgressive: false,
|
||||
resolutionUnit: '',
|
||||
hasProfile: false,
|
||||
hasAlpha: false,
|
||||
orientation: 0,
|
||||
exif: {} as any,
|
||||
json: {
|
||||
alt: `Image: ${fileName}`,
|
||||
keywords: "",
|
||||
title: "",
|
||||
description: ""
|
||||
},
|
||||
markdown: ''
|
||||
}
|
||||
};
|
||||
|
||||
// Try to load companion markdown and JSON files (like media.ts does)
|
||||
const baseDir = path.dirname(filePath);
|
||||
const metaJsonPath = path.join(baseDir, `${fileName}.json`);
|
||||
const metaMarkdownPath = path.join(baseDir, `${fileName}.md`);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(metaJsonPath)) {
|
||||
const metaJson = JSON.parse(fs.readFileSync(metaJsonPath, 'utf8'));
|
||||
image.meta!.json = metaJson;
|
||||
image.alt = metaJson.alt || image.alt;
|
||||
image.title = metaJson.title || image.title;
|
||||
image.description = metaJson.description || image.description;
|
||||
}
|
||||
|
||||
if (fs.existsSync(metaMarkdownPath)) {
|
||||
const markdown = fs.readFileSync(metaMarkdownPath, 'utf8');
|
||||
image.meta!.markdown = markdown;
|
||||
image.description = markdown || image.description;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Error loading metadata for ${fileName}:`, e);
|
||||
}
|
||||
|
||||
return image;
|
||||
})
|
||||
);
|
||||
|
||||
allImages = [...globImages];
|
||||
} catch (error) {
|
||||
console.warn('Glob pattern failed:', error);
|
||||
allImages = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Add manual images if provided
|
||||
if (images) {
|
||||
allImages = [...allImages, ...images];
|
||||
}
|
||||
|
||||
// Resolve relative paths in all image sources and convert to LGallery format
|
||||
const resolvedImages = allImages.map(image => {
|
||||
const resolvedSrc = resolveImagePath(image.src, entryPath, Astro.url);
|
||||
|
||||
// Convert GalleryImage to LGallery's expected Image interface
|
||||
return {
|
||||
src: resolvedSrc,
|
||||
alt: image.alt || '',
|
||||
title: image.title,
|
||||
description: image.description
|
||||
};
|
||||
});
|
||||
---
|
||||
|
||||
<LGallery images={resolvedImages} {...props} />
|
||||
96
packages/polymech/src/components/RelativeImage.astro
Normal file
96
packages/polymech/src/components/RelativeImage.astro
Normal file
@ -0,0 +1,96 @@
|
||||
---
|
||||
import { Img } from "imagetools/components";
|
||||
import { resolveImagePath } from '@/utils/path-resolution';
|
||||
|
||||
export interface Props {
|
||||
src: string;
|
||||
alt: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
class?: string;
|
||||
entryPath?: string;
|
||||
lightbox?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const {
|
||||
src,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
placeholder,
|
||||
objectFit,
|
||||
loading,
|
||||
sizes,
|
||||
class: className,
|
||||
entryPath,
|
||||
lightbox = true,
|
||||
...rest
|
||||
} = Astro.props;
|
||||
|
||||
const resolvedSrc = resolveImagePath(src, entryPath, Astro.url);
|
||||
const imgProps = { alt, width, height, format, placeholder, objectFit, loading, sizes };
|
||||
Object.keys(imgProps).forEach(key => imgProps[key] === undefined && delete imgProps[key]);
|
||||
---
|
||||
<button
|
||||
class:list={["w-full h-full", { 'lightbox-enabled': lightbox }]}
|
||||
>
|
||||
<Img src={resolvedSrc} {...imgProps} attributes={{
|
||||
img: {
|
||||
...(className && { class: className })
|
||||
}
|
||||
}} />
|
||||
</button>
|
||||
|
||||
<script define:vars={{ lightbox }}>
|
||||
const button = document.currentScript.previousElementSibling;
|
||||
|
||||
function getHighestResSrc(img) {
|
||||
let highestResSrc = img.src;
|
||||
const srcset = img.getAttribute('srcset');
|
||||
|
||||
if (srcset) {
|
||||
let maxWidth = 0;
|
||||
srcset.split(',').forEach(s => {
|
||||
const parts = s.trim().split(/\s+/);
|
||||
const url = parts[0];
|
||||
const widthStr = parts[1];
|
||||
if (widthStr && widthStr.endsWith('w')) {
|
||||
const width = parseInt(widthStr.slice(0, -1), 10);
|
||||
if (width > maxWidth) {
|
||||
maxWidth = width;
|
||||
highestResSrc = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return highestResSrc;
|
||||
}
|
||||
|
||||
button.addEventListener('click', (event) => {
|
||||
if (!lightbox) return;
|
||||
|
||||
const triggerImage = event.currentTarget.querySelector('img');
|
||||
if (!triggerImage) return;
|
||||
|
||||
const allImages = Array.from(document.querySelectorAll('.lightbox-enabled img')).map(img => ({
|
||||
src: getHighestResSrc(img),
|
||||
alt: img.getAttribute('alt')
|
||||
}));
|
||||
|
||||
const triggerSrc = getHighestResSrc(triggerImage);
|
||||
const currentIndex = allImages.findIndex(img => img.src === triggerSrc);
|
||||
|
||||
if (currentIndex === -1) return;
|
||||
|
||||
const lightboxEvent = new CustomEvent('open-lightbox', {
|
||||
detail: {
|
||||
images: allImages,
|
||||
currentIndex: currentIndex
|
||||
}
|
||||
});
|
||||
|
||||
window.dispatchEvent(lightboxEvent);
|
||||
});
|
||||
</script>
|
||||
47
packages/polymech/src/components/RelativePicture.astro
Normal file
47
packages/polymech/src/components/RelativePicture.astro
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
import { Picture } from "imagetools/components";
|
||||
|
||||
export interface Props {
|
||||
src: string;
|
||||
alt: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
class?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const { src, class: className, ...props } = Astro.props;
|
||||
|
||||
// Get current content directory dynamically from URL
|
||||
const currentUrl = Astro.url.pathname;
|
||||
const pathSegments = currentUrl.split('/').filter(Boolean);
|
||||
|
||||
// Handle locale-aware URLs (e.g., /en/resources/test/ vs /resources/test/)
|
||||
// Check if first segment is a locale (2-character language code)
|
||||
const isLocaleFirst = pathSegments.length > 0 && pathSegments[0].length === 2 && /^[a-z]{2}$/.test(pathSegments[0]);
|
||||
|
||||
// Determine content subdirectory (e.g., 'resources', 'blog', etc.)
|
||||
let contentSubdir = 'resources'; // fallback
|
||||
if (pathSegments.length >= 1) {
|
||||
contentSubdir = isLocaleFirst && pathSegments.length > 1
|
||||
? pathSegments[1] // Skip locale: /en/resources/test/ -> "resources"
|
||||
: pathSegments[0]; // No locale: /resources/test/ -> "resources"
|
||||
}
|
||||
|
||||
// Dynamic path resolver for relative paths in content collections
|
||||
let resolvedSrc = src;
|
||||
|
||||
if (src.startsWith('./')) {
|
||||
// Convert ./image.jpg to /src/content/{dynamic-subdir}/image.jpg for imagetools
|
||||
resolvedSrc = src.replace('./', `/src/content/${contentSubdir}/`);
|
||||
} else if (src.startsWith('../')) {
|
||||
// Handle parent directory references
|
||||
resolvedSrc = src.replace('../', '/src/content/');
|
||||
}
|
||||
---
|
||||
|
||||
<Picture src={resolvedSrc} {...props} attributes={{
|
||||
img: {
|
||||
class: className
|
||||
}
|
||||
}} />
|
||||
8
packages/polymech/src/components/conditional.astro
Normal file
8
packages/polymech/src/components/conditional.astro
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
import DefaultFallback from "@/components/Default.astro"
|
||||
const { expression = false, fallback = DefaultFallback } = Astro.props
|
||||
const currentLocal = Astro.currentLocale || "en"
|
||||
---
|
||||
<div data-track={currentLocal}>
|
||||
{expression ? <slot /> : <div/>}
|
||||
</div>
|
||||
782
packages/polymech/src/components/file-tree-icons.ts
Normal file
782
packages/polymech/src/components/file-tree-icons.ts
Normal file
File diff suppressed because one or more lines are too long
52
packages/polymech/src/components/readme.astro
Normal file
52
packages/polymech/src/components/readme.astro
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
import { renderMarkup } from "@/model/component.js";
|
||||
import { render, component } from "@/base/index.js";
|
||||
import { translate } from "@/base/i18n.js";
|
||||
import { I18N_SOURCE_LANGUAGE, ASSET_URL } from "config/config.js";
|
||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
||||
import { toString } from "mdast-util-to-string";
|
||||
// https://github.com/syntax-tree/mdast-util-mdxjs-esm?tab=readme-ov-file#when-to-use-this
|
||||
// https://github.com/syntax-tree/mdast-util-to-markdown?tab=readme-ov-file#list-of-extensions
|
||||
// https://chatgpt.com/c/67ba55c7-4c04-8001-b26b-bfa3a89aafb1
|
||||
import { toMarkdown } from "mdast-util-to-markdown";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Root, RootContent, Heading, Image } from "mdast";
|
||||
import { Img } from "imagetools/components";
|
||||
|
||||
interface Props {
|
||||
markdown: string;
|
||||
className?: string;
|
||||
baseImageUrl?: string;
|
||||
translate?: boolean;
|
||||
data?: any;
|
||||
}
|
||||
const {
|
||||
markdown,
|
||||
className = "",
|
||||
baseImageUrl = "",
|
||||
translate = false,
|
||||
data = {},
|
||||
...rest
|
||||
} = Astro.props as Props;
|
||||
|
||||
const processImageUrls = (content: string, data: Record<string, string>) => {
|
||||
const tree = fromMarkdown(content) as Root;
|
||||
visit(tree, "image", (node: Image) => {
|
||||
if (!node.url.startsWith("http") && !node.url.startsWith("/")) {
|
||||
node.url = ASSET_URL(node.url, data);
|
||||
}
|
||||
})
|
||||
const markup = toMarkdown(tree);
|
||||
return markup;
|
||||
}
|
||||
|
||||
const ReadmeContent = await component(
|
||||
processImageUrls(markdown, data),
|
||||
Astro.currentLocale,
|
||||
{},
|
||||
)
|
||||
---
|
||||
|
||||
<div class={className}>
|
||||
<ReadmeContent />
|
||||
</div>
|
||||
260
packages/polymech/src/components/rehype-file-tree.ts
Normal file
260
packages/polymech/src/components/rehype-file-tree.ts
Normal file
@ -0,0 +1,260 @@
|
||||
import { AstroError } from 'astro/errors';
|
||||
import type { Element, ElementContent, Text } from 'hast';
|
||||
import { type Child, h, s } from 'hastscript';
|
||||
import { select } from 'hast-util-select';
|
||||
import { fromHtml } from 'hast-util-from-html';
|
||||
import { toString } from 'hast-util-to-string';
|
||||
import { rehype } from 'rehype';
|
||||
import { CONTINUE, SKIP, visit } from 'unist-util-visit';
|
||||
import { Icons, type StarlightIcon } from './Icons.js';
|
||||
import { definitions } from './file-tree-icons.js';
|
||||
|
||||
declare module 'vfile' {
|
||||
interface DataMap {
|
||||
directoryLabel: string;
|
||||
}
|
||||
}
|
||||
|
||||
const folderIcon = makeSVGIcon(Icons['seti:folder']);
|
||||
const defaultFileIcon = makeSVGIcon(Icons['seti:default']);
|
||||
|
||||
/**
|
||||
* Process the HTML for a file tree to create the necessary markup for each file and directory
|
||||
* including icons.
|
||||
* @param html Inner HTML passed to the `<FileTree>` component.
|
||||
* @param directoryLabel The localized label for a directory.
|
||||
* @returns The processed HTML for the file tree.
|
||||
*/
|
||||
export function processFileTree(html: string, directoryLabel: string) {
|
||||
const file = fileTreeProcessor.processSync({ data: { directoryLabel }, value: html });
|
||||
|
||||
return file.toString();
|
||||
}
|
||||
|
||||
/** Rehype processor to extract file tree data and turn each entry into its associated markup. */
|
||||
const fileTreeProcessor = rehype()
|
||||
.data('settings', { fragment: true })
|
||||
.use(function fileTree() {
|
||||
return (tree: Element, file) => {
|
||||
const { directoryLabel } = file.data;
|
||||
|
||||
// validateFileTree(tree);
|
||||
|
||||
visit(tree, 'element', (node) => {
|
||||
// Strip nodes that only contain newlines.
|
||||
node.children = node.children.filter(
|
||||
(child) => child.type === 'comment' || child.type !== 'text' || !/^\n+$/.test(child.value)
|
||||
);
|
||||
|
||||
// Skip over non-list items.
|
||||
if (node.tagName !== 'li') return CONTINUE;
|
||||
|
||||
const [firstChild, ...otherChildren] = node.children;
|
||||
|
||||
// Keep track of comments associated with the current file or directory.
|
||||
const comment: Child[] = [];
|
||||
|
||||
// Handle both text nodes and link elements for file names
|
||||
let filename = '';
|
||||
let isLinked = false;
|
||||
|
||||
if (firstChild?.type === 'text') {
|
||||
// Extract text comment that follows the file name, e.g. `README.md This is a comment`
|
||||
const [filenameText, ...fragments] = firstChild.value.split(' ');
|
||||
firstChild.value = filenameText || '';
|
||||
filename = filenameText || '';
|
||||
const textComment = fragments.join(' ').trim();
|
||||
if (textComment.length > 0) {
|
||||
comment.push(fragments.join(' '));
|
||||
}
|
||||
} else if (firstChild?.type === 'element' && firstChild.tagName === 'a') {
|
||||
// Handle linked files - preserve the link
|
||||
isLinked = true;
|
||||
filename = toString(firstChild);
|
||||
}
|
||||
|
||||
// Comments may not always be entirely part of the first child text node,
|
||||
// e.g. `README.md This is an __important__ comment` where the `__important__` and `comment`
|
||||
// nodes would also be children of the list item node.
|
||||
const subTreeIndex = otherChildren.findIndex(
|
||||
(child) => child.type === 'element' && child.tagName === 'ul'
|
||||
);
|
||||
const commentNodes =
|
||||
subTreeIndex > -1 ? otherChildren.slice(0, subTreeIndex) : [...otherChildren];
|
||||
otherChildren.splice(0, subTreeIndex > -1 ? subTreeIndex : otherChildren.length);
|
||||
comment.push(...commentNodes);
|
||||
|
||||
const firstChildTextContent = filename || (firstChild ? toString(firstChild) : '');
|
||||
|
||||
// Decide a node is a directory if it ends in a `/` or contains another list.
|
||||
const isDirectory =
|
||||
/\/\s*$/.test(firstChildTextContent) ||
|
||||
otherChildren.some((child) => child.type === 'element' && child.tagName === 'ul');
|
||||
// A placeholder is a node that only contains 3 dots or an ellipsis.
|
||||
const isPlaceholder = /^\s*(\.{3}|…)\s*$/.test(firstChildTextContent);
|
||||
// A node is highlighted if its first child is bold text, e.g. `**README.md**`.
|
||||
const isHighlighted = firstChild?.type === 'element' && firstChild.tagName === 'strong';
|
||||
|
||||
// Create an icon for the file or directory (placeholder do not have icons).
|
||||
const icon = h('span', isDirectory ? folderIcon : getFileIcon(firstChildTextContent));
|
||||
if (isDirectory) {
|
||||
// Add a screen reader only label for directories before the icon so that it is announced
|
||||
// as such before reading the directory name.
|
||||
icon.children.unshift(h('span', { class: 'sr-only' }, directoryLabel));
|
||||
}
|
||||
|
||||
// Add classes and data attributes to the list item node.
|
||||
node.properties.class = isDirectory ? 'directory' : 'file';
|
||||
if (isPlaceholder) node.properties.class += ' empty';
|
||||
|
||||
// Create the tree entry node that contains the icon, file name and comment which will end up
|
||||
// as the list item's children.
|
||||
const treeEntryChildren: Child[] = [
|
||||
h('span', { class: isHighlighted ? 'highlight' : '' }, [
|
||||
isPlaceholder ? null : icon,
|
||||
firstChild,
|
||||
]),
|
||||
];
|
||||
|
||||
if (comment.length > 0) {
|
||||
treeEntryChildren.push(makeText(' '), h('span', { class: 'comment' }, ...comment));
|
||||
}
|
||||
|
||||
const treeEntry = h('span', { class: 'tree-entry' }, ...treeEntryChildren);
|
||||
|
||||
if (isDirectory) {
|
||||
const hasContents = otherChildren.length > 0;
|
||||
|
||||
node.children = [
|
||||
h('details', { open: hasContents }, [
|
||||
h('summary', treeEntry),
|
||||
...(hasContents ? otherChildren : [h('ul', h('li', '…'))]),
|
||||
]),
|
||||
];
|
||||
|
||||
// Continue down the tree.
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
node.children = [treeEntry, ...otherChildren];
|
||||
|
||||
// Files can’t contain further files or directories, so skip iterating children.
|
||||
return SKIP;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/** Make a text node with the pass string as its contents. */
|
||||
function makeText(value = ''): Text {
|
||||
return { type: 'text', value };
|
||||
}
|
||||
|
||||
/** Make a node containing an SVG icon from the passed HTML string. */
|
||||
function makeSVGIcon(svgString: string) {
|
||||
return s(
|
||||
'svg',
|
||||
{
|
||||
width: 16,
|
||||
height: 16,
|
||||
class: 'tree-icon',
|
||||
'aria-hidden': 'true',
|
||||
viewBox: '0 0 24 24',
|
||||
},
|
||||
fromHtml(svgString, { fragment: true })
|
||||
);
|
||||
}
|
||||
|
||||
/** Return the icon for a file based on its file name. */
|
||||
function getFileIcon(fileName: string) {
|
||||
const name = getFileIconName(fileName);
|
||||
if (!name) return defaultFileIcon;
|
||||
if (name in Icons) {
|
||||
const path = Icons[name as StarlightIcon];
|
||||
return makeSVGIcon(path);
|
||||
}
|
||||
return defaultFileIcon;
|
||||
}
|
||||
|
||||
/** Return the icon name for a file based on its file name. */
|
||||
function getFileIconName(fileName: string) {
|
||||
let icon: string | undefined = definitions.files[fileName];
|
||||
if (icon) return icon;
|
||||
icon = getFileIconTypeFromExtension(fileName);
|
||||
if (icon) return icon;
|
||||
for (const [partial, partialIcon] of Object.entries(definitions.partials)) {
|
||||
if (fileName.includes(partial)) return partialIcon;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an icon from a file name based on its extension.
|
||||
* Note that an extension in Seti is everything after a dot, so `README.md` would be `.md` and
|
||||
* `name.with.dots` will try to look for an icon for `.with.dots` and then `.dots` if the first one
|
||||
* is not found.
|
||||
*/
|
||||
function getFileIconTypeFromExtension(fileName: string) {
|
||||
const firstDotIndex = fileName.indexOf('.');
|
||||
if (firstDotIndex === -1) return;
|
||||
let extension = fileName.slice(firstDotIndex);
|
||||
while (extension !== '') {
|
||||
const icon = definitions.extensions[extension];
|
||||
if (icon) return icon;
|
||||
const nextDotIndex = extension.indexOf('.', 1);
|
||||
if (nextDotIndex === -1) return;
|
||||
extension = extension.slice(nextDotIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/** Validate that the user provided HTML for a file tree is valid. */
|
||||
function validateFileTree(tree: Element) {
|
||||
const rootElements = tree.children.filter(isElementNode);
|
||||
const [rootElement] = rootElements;
|
||||
|
||||
if (rootElements.length === 0) {
|
||||
throwFileTreeValidationError(
|
||||
'The `<FileTree>` component expects its content to be a single unordered list but found no child elements.'
|
||||
);
|
||||
}
|
||||
|
||||
if (rootElements.length !== 1) {
|
||||
throwFileTreeValidationError(
|
||||
`The \`<FileTree>\` component expects its content to be a single unordered list but found multiple child elements: ${rootElements
|
||||
.map((element) => `\`<${element.tagName}>\``)
|
||||
.join(' - ')}.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!rootElement || rootElement.tagName !== 'ul') {
|
||||
throwFileTreeValidationError(
|
||||
`The \`<FileTree>\` component expects its content to be an unordered list but found the following element: \`<${rootElement?.tagName}>\`.`
|
||||
);
|
||||
}
|
||||
|
||||
const listItemElement = select('li', rootElement);
|
||||
|
||||
if (!listItemElement) {
|
||||
throwFileTreeValidationError(
|
||||
'The `<FileTree>` component expects its content to be an unordered list with at least one list item.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function isElementNode(node: ElementContent): node is Element {
|
||||
return node.type === 'element';
|
||||
}
|
||||
|
||||
/** Throw a validation error for a file tree linking to the documentation. */
|
||||
function throwFileTreeValidationError(message: string): never {
|
||||
throw new AstroError(
|
||||
message,
|
||||
'To learn more about the `<FileTree>` component, see https://starlight.astro.build/components/file-tree/'
|
||||
);
|
||||
}
|
||||
|
||||
export interface Definitions {
|
||||
files: Record<string, string>;
|
||||
extensions: Record<string, string>;
|
||||
partials: Record<string, string>;
|
||||
}
|
||||
117
packages/polymech/src/utils/path-resolution.ts
Normal file
117
packages/polymech/src/utils/path-resolution.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { resolveVariables, resolve } from '@polymech/commons'
|
||||
// --- Debug Configuration ---
|
||||
const enableDebugSuccess = false;
|
||||
const enableDebugErrors = false;
|
||||
|
||||
/**
|
||||
* Checks if a given file path exists and is a file.
|
||||
* @param {string} basePath The absolute base directory to check within.
|
||||
* @param {string} relativePath The relative path of the file from the base path.
|
||||
* @returns {string | null} The project-relative path if the file exists, otherwise null.
|
||||
*/
|
||||
function checkFilePath(basePath: string, relativePath: string): string | null {
|
||||
try {
|
||||
const absolutePath = path.resolve(basePath, relativePath);
|
||||
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
|
||||
// Return path relative to project root, using forward slashes for Astro's asset handling.
|
||||
// This ensures compatibility regardless of deployment environment (e.g., subdirectories).
|
||||
return path.relative(process.cwd(), absolutePath).replace(/\\/g, '/');
|
||||
}
|
||||
} catch (e) {
|
||||
if (enableDebugErrors) {
|
||||
console.error(`[resolveImagePath] Filesystem error while checking path at "${basePath}":`, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the path of an image, handling various scenarios like relative paths in content,
|
||||
* absolute paths from the /public folder, and external URLs. It's designed to be robust
|
||||
* for Astro's content collections, especially in i18n setups.
|
||||
*
|
||||
* @param {string} src The image source path from the content (e.g., "./images/foo.jpg", "/logo.png").
|
||||
* @param {string} [entryPath] The content collection entry path (e.g., "resources/cassandra/home").
|
||||
* This is the most reliable way to resolve relative paths.
|
||||
* @param {URL} [astroUrl] The Astro page URL object, used as a fallback for path resolution.
|
||||
* @returns {string} The resolved image path, typically relative to the project root for local images.
|
||||
*/
|
||||
export function resolveImagePath(src: string, entryPath?: string, astroUrl?: URL): string {
|
||||
if (enableDebugSuccess) {
|
||||
console.debug(`[resolveImagePath] Attempting to resolve: src="${src}", entryPath="${entryPath}", url="${astroUrl?.pathname}"`);
|
||||
}
|
||||
|
||||
// --- Case 1: Absolute paths or external URLs ---
|
||||
// Handles external images (http://, https://) and images in the /public directory (/).
|
||||
// These paths don't need resolution and are returned as-is.
|
||||
if (src.startsWith('/') || src.startsWith('http')) {
|
||||
if (enableDebugSuccess) {
|
||||
console.debug(`[resolveImagePath] Path is absolute or external. Returning as-is: "${src}"`);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
// --- Case 2: Relative paths (e.g., ./images/foo.jpg or ../shared/foo.jpg) ---
|
||||
if (src.startsWith('.')) {
|
||||
let basePath: string | undefined;
|
||||
let strategy: 'entryPath' | 'URL' | undefined;
|
||||
|
||||
// --- Strategy 2.1: Use entryPath (most reliable) ---
|
||||
if (entryPath) {
|
||||
strategy = 'entryPath';
|
||||
const contentDir = entryPath.substring(0, entryPath.lastIndexOf('/'));
|
||||
basePath = path.join(process.cwd(), 'src', 'content', contentDir);
|
||||
}
|
||||
// --- Strategy 2.2: Fallback to URL parsing ---
|
||||
else if (astroUrl) {
|
||||
strategy = 'URL';
|
||||
if (enableDebugErrors) console.warn(`[resolveImagePath] [INFO-URL] No entryPath provided. Falling back to URL-based resolution for "${src}". This is less reliable.`);
|
||||
|
||||
const isFolderUrl = astroUrl.pathname.endsWith('/');
|
||||
const pathSegments = astroUrl.pathname.split('/').filter(p => p);
|
||||
const hasLocale = pathSegments.length > 0 && /^[a-z]{2}$/.test(pathSegments[0]);
|
||||
if (hasLocale) pathSegments.shift();
|
||||
|
||||
if (pathSegments.length >= 1) {
|
||||
const contentDirGuess = isFolderUrl ? pathSegments.join('/') : pathSegments.slice(0, -1).join('/');
|
||||
basePath = path.join(process.cwd(), 'src', 'content', contentDirGuess);
|
||||
}
|
||||
}
|
||||
|
||||
if (basePath && strategy) {
|
||||
// Check in the determined base directory
|
||||
let resolvedPath = checkFilePath(basePath, src);
|
||||
if (resolvedPath) {
|
||||
if (enableDebugSuccess) console.log(`[resolveImagePath] [SUCCESS-${strategy}] Resolved "${src}" to "${resolvedPath}"`);
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
// Parent Directory Check: If not found, check one level
|
||||
|
||||
if (enableDebugErrors) console.warn(`[resolveImagePath] [WARN-${strategy}] Not found in "${basePath}". Checking parent directory.`);
|
||||
const parentBasePath = path.resolve(basePath, '..');
|
||||
resolvedPath = checkFilePath(parentBasePath, src);
|
||||
if (resolvedPath) {
|
||||
if (enableDebugSuccess) console.log(`[resolveImagePath] [SUCCESS-${strategy}-Parent] Resolved "${src}" to "${resolvedPath}"`);
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
if (enableDebugErrors) {
|
||||
console.warn(`[resolveImagePath] [WARN-${strategy}] Final path check failed for "${src}". Base path checked: "${basePath}", Parent path checked: "${parentBasePath}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableDebugErrors) {
|
||||
console.error(`[resolveImagePath] [FAILURE] Could not resolve relative path "${src}". Returning original.`);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
// --- Fallback for unrecognized path formats ---
|
||||
if (enableDebugErrors) {
|
||||
console.warn(`[resolveImagePath] Path format for "${src}" is ambiguous (not absolute or relative). Returning as-is.`);
|
||||
}
|
||||
return src;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user