You've already forked woodpecker
mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-05-22 08:55:42 +02:00
Improve plugins index (#1200)
Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com>
This commit is contained in:
@@ -1,64 +1,53 @@
|
||||
import { LoadContext, Plugin, PluginContentLoadedActions } from '@docusaurus/types';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { components as OctokitComponents } from '@octokit/openapi-types';
|
||||
import path from 'path';
|
||||
import { Content, WoodpeckerPlugin, WoodpeckerPluginHeader } from './types';
|
||||
import fs from 'fs';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { Content, WoodpeckerPlugin, WoodpeckerPluginHeader, WoodpeckerPluginIndexEntry } from './types';
|
||||
import * as markdown from './markdown';
|
||||
|
||||
const octokit = new Octokit();
|
||||
|
||||
async function getDocs(repoName: string): Promise<string | undefined> {
|
||||
try {
|
||||
const docsResult = (
|
||||
await octokit.repos.getContent({
|
||||
owner: 'woodpecker-ci',
|
||||
repo: repoName,
|
||||
path: '/docs.md',
|
||||
})
|
||||
).data as OctokitComponents['schemas']['content-file'];
|
||||
|
||||
return Buffer.from(docsResult.content, 'base64').toString('ascii');
|
||||
} catch (e) {
|
||||
console.error("Can't fetch docs file for repository", repoName, e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function loadContent(): Promise<Content> {
|
||||
const repositories = (
|
||||
await octokit.rest.search.repos({
|
||||
// search for repos in woodpecker-ci org with the topic: woodpecker-plugin including forks
|
||||
q: 'org:woodpecker-ci topic:woodpecker-plugin fork:true',
|
||||
})
|
||||
).data.items;
|
||||
const file = path.join(__dirname, '..', 'plugins.json');
|
||||
|
||||
console.log(repositories.map((r) => r.name));
|
||||
const pluginsIndex = JSON.parse(fs.readFileSync(file).toString()) as { plugins: WoodpeckerPluginIndexEntry[] };
|
||||
|
||||
const plugins = (
|
||||
await Promise.all(
|
||||
repositories.map(async (repo) => {
|
||||
const docs = await getDocs(repo.name);
|
||||
if (!docs) {
|
||||
pluginsIndex.plugins.map(async (i) => {
|
||||
if (i['// todo']) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const header = markdown.getHeader<WoodpeckerPluginHeader>(docs);
|
||||
const body = markdown.getContent(docs);
|
||||
let docsContent: string;
|
||||
try {
|
||||
const response = await axios(i.docs);
|
||||
docsContent = response.data;
|
||||
} catch (e) {
|
||||
console.error("Can't fetch docs file", i.docs, (e as AxiosError).message);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const plugin: WoodpeckerPlugin = {
|
||||
name: header?.name || repo.name,
|
||||
repoName: repo.name,
|
||||
url: repo.html_url,
|
||||
icon: header?.icon,
|
||||
description: header?.description,
|
||||
docs: body,
|
||||
const docsHeader = markdown.getHeader<WoodpeckerPluginHeader>(docsContent);
|
||||
const docsBody = markdown.getContent(docsContent);
|
||||
|
||||
if (!docsHeader.name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return <WoodpeckerPlugin>{
|
||||
name: docsHeader.name || i.name,
|
||||
url: docsHeader.url,
|
||||
icon: docsHeader.icon,
|
||||
description: docsHeader.description,
|
||||
docs: docsBody,
|
||||
tags: docsHeader.tags || [],
|
||||
author: docsHeader.author,
|
||||
containerImage: docsHeader.containerImage,
|
||||
containerImageUrl: docsHeader.containerImageUrl,
|
||||
verified: i.verified || false,
|
||||
};
|
||||
|
||||
return plugin;
|
||||
}),
|
||||
)
|
||||
).filter((plugin) => plugin);
|
||||
).filter<WoodpeckerPlugin>((plugin): plugin is WoodpeckerPlugin => plugin !== undefined);
|
||||
|
||||
return {
|
||||
plugins,
|
||||
@@ -77,11 +66,11 @@ async function contentLoaded({
|
||||
const pluginsJsonPath = await createData('plugins.json', JSON.stringify(plugins));
|
||||
|
||||
await Promise.all(
|
||||
plugins.map(async (plugin) => {
|
||||
const pluginJsonPath = await createData(`plugin-${plugin.repoName}.json`, JSON.stringify(plugin));
|
||||
plugins.map(async (plugin, i) => {
|
||||
const pluginJsonPath = await createData(`plugin-${i}.json`, JSON.stringify(plugin));
|
||||
|
||||
addRoute({
|
||||
path: `/plugins/${plugin.repoName}`,
|
||||
path: `/plugins/${plugin.name}`,
|
||||
component: '@theme/WoodpeckerPlugin',
|
||||
modules: {
|
||||
plugin: pluginJsonPath,
|
||||
@@ -113,7 +102,7 @@ export default function pluginWoodpeckerPluginsIndex(context: LoadContext, optio
|
||||
return path.join(__dirname, '..', 'src', 'theme');
|
||||
},
|
||||
getPathsToWatch() {
|
||||
return [path.join(__dirname, '..', 'dist', '**', '*.{js,jsx}')];
|
||||
return [path.join(__dirname, '..', 'dist', '**', '*.{js,jsx,css}')];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { marked } from 'marked';
|
||||
import { parse as YAMLParse } from 'yaml';
|
||||
|
||||
const tokens = ['---', '---'];
|
||||
const regexHeader = new RegExp('^' + tokens[0] + '([\\s|\\S]*?)' + tokens[1]);
|
||||
@@ -6,27 +7,18 @@ const regexContent = new RegExp('^ *?\\' + tokens[0] + '[^]*?' + tokens[1] + '*'
|
||||
|
||||
export function getHeader<T = any>(data: string): T {
|
||||
const header = getRawHeader(data);
|
||||
|
||||
const tmpObj = {};
|
||||
const lines = header.trim().split('\n');
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
var arr = line.trim().split(':');
|
||||
tmpObj[arr.shift()] = arr.join(':').trim();
|
||||
});
|
||||
|
||||
return tmpObj as T;
|
||||
return YAMLParse(header) as T;
|
||||
}
|
||||
|
||||
export function getRawHeader(data: string): string {
|
||||
const header = regexHeader.exec(data);
|
||||
if (!header) {
|
||||
new Error("Can't get the header");
|
||||
throw new Error("Can't get the header");
|
||||
}
|
||||
return header[1];
|
||||
}
|
||||
|
||||
export function getContent(data): string {
|
||||
export function getContent(data: string): string {
|
||||
const content = data.replace(regexContent, '').replace(/<!--(.*?)-->/gm, '');
|
||||
if (!content) {
|
||||
throw new Error("Can't get the content");
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
|
||||
export const IconVerified = (size = 32) => (
|
||||
<div title="This plugin is verified by the Woodpecker CI maintainers">
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" style={{ color: '#0369a1', marginLeft: '1rem' }}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m8.6 22.5l-1.9-3.2l-3.6-.8l.35-3.7L1 12l2.45-2.8l-.35-3.7l3.6-.8l1.9-3.2L12 2.95l3.4-1.45l1.9 3.2l3.6.8l-.35 3.7L23 12l-2.45 2.8l.35 3.7l-3.6.8l-1.9 3.2l-3.4-1.45Zm2.35-6.95L16.6 9.9l-1.4-1.45l-4.25 4.25l-2.15-2.1L7.4 12Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const IconContainer = (size = 32) => (
|
||||
<div title="Container">
|
||||
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="m10.41.24l4.711 2.774A1.767 1.767 0 0 1 16 4.54v5.01a1.77 1.77 0 0 1-.88 1.53l-7.753 4.521l-.002.001a1.767 1.767 0 0 1-1.774 0H5.59L.873 12.85A1.762 1.762 0 0 1 0 11.327V6.292c0-.304.078-.598.22-.855l.004-.005l.01-.019c.15-.262.369-.486.64-.643L8.641.239a1.75 1.75 0 0 1 1.765 0l.002.001zM9.397 1.534a.25.25 0 0 1 .252 0l4.115 2.422l-7.152 4.148a.267.267 0 0 1-.269 0L2.227 5.716l7.17-4.182zM7.365 9.402L8.73 8.61v4.46l-1.5.875V9.473a1.77 1.77 0 0 0 .136-.071zm2.864 2.794V7.741l1.521-.882v4.45l-1.521.887zm3.021-1.762l1.115-.65h.002a.268.268 0 0 0 .133-.232V5.264l-1.25.725v4.445zm-11.621 1.12l4.1 2.393V9.474a1.77 1.77 0 0 1-.138-.072L1.5 7.029v4.298c0 .095.05.181.129.227z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const IconWebsite = (size = 32) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12s4.477 10 10 10"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13 2.05S16 6 16 12m-5 9.95S8 18 8 12c0-6 3-9.95 3-9.95M2.63 15.5H12m-9.37-7h18.74"
|
||||
/>
|
||||
<path
|
||||
d="M21.879 17.917c.494.304.463 1.043-.045 1.101l-2.567.291l-1.151 2.312c-.228.459-.933.234-1.05-.334l-1.255-6.116c-.099-.48.333-.782.75-.525l5.318 3.271Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,31 +1,70 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Layout from '@theme/Layout';
|
||||
import { WoodpeckerPlugin as WoodpeckerPluginType } from '../types';
|
||||
import { IconContainer, IconVerified, IconWebsite } from './Icons';
|
||||
|
||||
export function WoodpeckerPlugin({ plugin }: { plugin: WoodpeckerPluginType }) {
|
||||
return (
|
||||
<Layout
|
||||
title="Woodpecker CI plugins"
|
||||
description="List of Woodpecker-CI plugins"
|
||||
>
|
||||
<main className={clsx("container margin-vert--lg")}>
|
||||
<Layout title="Woodpecker CI plugins" description="List of Woodpecker-CI plugins">
|
||||
<main className="container margin-vert--lg">
|
||||
<section>
|
||||
<div className={clsx("container")}>
|
||||
<a href="/plugins"><< Back to plugin list</a>
|
||||
<div className={clsx("row")}>
|
||||
<div className={clsx("col col--10")}>
|
||||
<h1>{plugin.name}</h1>
|
||||
<p>{plugin.description}</p>
|
||||
<a href={plugin.url} target="_blank" rel="noopener noreferrer">
|
||||
{plugin.url}
|
||||
</a>
|
||||
<div className="container">
|
||||
<div className="wp-plugin-breadcrumbs">
|
||||
<a href="/plugins">Plugins</a>
|
||||
<span> / </span>
|
||||
<span>{plugin.name}</span>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col col--10">
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<h1 style={{ marginBottom: 0 }}>{plugin.name}</h1>
|
||||
{plugin.verified && IconVerified()}
|
||||
</div>
|
||||
{plugin.author && <span>by {plugin.author}</span>}
|
||||
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
{plugin.containerImage && (
|
||||
<div style={{ display: 'flex', gap: '.5rem', alignItems: 'center' }}>
|
||||
{IconContainer(20)}
|
||||
{plugin.containerImageUrl ? (
|
||||
<a href={plugin.containerImageUrl} target="_blank" rel="noopener noreferrer">
|
||||
{plugin.containerImage}
|
||||
</a>
|
||||
) : (
|
||||
<span>{plugin.containerImage}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{plugin.url && (
|
||||
<a
|
||||
href={plugin.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ display: 'flex', gap: '.5rem', alignItems: 'center' }}
|
||||
>
|
||||
<div style={{ color: 'var(--ifm-font-color-base)' }}>{IconWebsite(20)}</div> Website
|
||||
</a>
|
||||
)}
|
||||
|
||||
{plugin.tags && (
|
||||
<div className="wp-plugin-tags" style={{ marginTop: '.5rem' }}>
|
||||
{plugin.tags.map((tag, idx) => (
|
||||
<span className="badge badge--success" key={idx}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p style={{ marginTop: '2rem', marginBottom: '1rem' }}>{plugin.description}</p>
|
||||
</div>
|
||||
<div className={clsx("col col--2")}>
|
||||
<div className="col col--2">
|
||||
<img src={plugin.icon} width="150" height="150" />
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<hr style={{ margin: '1rem 0' }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: plugin.docs }} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,85 +1,95 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import Fuse from 'fuse.js';
|
||||
import Layout from '@theme/Layout';
|
||||
import './style.css';
|
||||
import { WoodpeckerPlugin } from '../types';
|
||||
import { IconVerified } from './Icons';
|
||||
|
||||
function PluginPanel({ plugin }: { plugin: WoodpeckerPlugin }) {
|
||||
const pluginUrl = `/plugins/${plugin.repoName}`;
|
||||
const pluginUrl = `/plugins/${plugin.name}`;
|
||||
|
||||
return (
|
||||
<div className={clsx('col col--6')}>
|
||||
<div className={clsx('card margin-horiz--sm margin-vert--md ')}>
|
||||
<div className={clsx('card__header row')}>
|
||||
<div className={clsx('col col--8')}>
|
||||
<a href={pluginUrl}>
|
||||
<h3>{plugin.name}</h3>
|
||||
</a>
|
||||
<p>{plugin.description}</p>
|
||||
</div>
|
||||
<a href={pluginUrl} className={clsx('col col--4 text--right')}>
|
||||
<img src={plugin.icon} width="100" height="100" />
|
||||
</a>
|
||||
<a href={pluginUrl} className="card shadow--md wp-plugin-card">
|
||||
<div className="card__header row">
|
||||
<div className="col col--2 text--left">
|
||||
<img src={plugin.icon} width="50" height="50" />
|
||||
</div>
|
||||
<div className={clsx('card__footer')}>
|
||||
<a href={pluginUrl} className={clsx('button button--secondary button--outline button--block ')}>
|
||||
Open {plugin.name}
|
||||
</a>
|
||||
<div className="col col--10">
|
||||
<h3>{plugin.name}</h3>
|
||||
<p>{plugin.description}</p>
|
||||
{plugin.tags && (
|
||||
<div className="wp-plugin-tags">
|
||||
{plugin.tags.map((tag, idx) => (
|
||||
<span className="badge badge--success" key={idx}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{plugin.verified && <div className="wp-plugin-verified">{IconVerified()}</div>}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function WoodpeckerPluginList({ plugins }: { plugins: WoodpeckerPlugin[] }) {
|
||||
const applyForIndexUrl =
|
||||
'https://github.com/woodpecker-ci/woodpecker/issues/new?labels=plugin&template=plugin_index.yml';
|
||||
'https://github.com/woodpecker-ci/woodpecker/edit/master/docs/plugins/woodpecker-plugins/plugins.json';
|
||||
|
||||
const NewPluginPanel = () => (
|
||||
<a href={applyForIndexUrl} target="_blank" rel="noopener noreferrer" className="card shadow--md wp-plugin-card">
|
||||
<div className="card__header row">
|
||||
<div className="col col--2">
|
||||
<svg width="50" height="50" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M88.2357 38.0952H61.9048V11.7643C61.9048 5.29524 56.4714 0 50 0C43.5286 0 38.0952 5.29524 38.0952 11.7643V38.0952H11.7643C5.29524 38.0952 0 43.5286 0 50C0 56.4714 5.29524 61.9048 11.7643 61.9048H38.0952V88.2357C38.0952 94.7048 43.5286 100 50 100C56.4714 100 61.9048 94.7048 61.9048 88.2357V61.9048H88.2357C94.7048 61.9048 100 56.4714 100 50C100 43.5286 94.7048 38.0952 88.2357 38.0952Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="col col--10">
|
||||
<h3>Add your own plugin</h3>
|
||||
<p>You can simply add your own plugin to this index.</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
const fuse = new Fuse(plugins, {
|
||||
keys: ['name', 'description'],
|
||||
threshold: 0.3,
|
||||
});
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const searchedPlugins = query.length >= 1 ? fuse.search(query) : plugins.map((p) => ({ item: p }));
|
||||
|
||||
return (
|
||||
<Layout title="Woodpecker CI plugins" description="List of all Woodpecker-CI plugins">
|
||||
<main className="container margin-vert--lg">
|
||||
<section>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{plugins.map((plugin, idx) => (
|
||||
<PluginPanel key={idx} plugin={plugin} />
|
||||
<div style={{ display: 'flex', flexFlow: 'column', alignItems: 'center' }}>
|
||||
<h1>Woodpecker CI plugins</h1>
|
||||
<p>This list contains plugins which you can use to easily execute usual pipeline tasks.</p>
|
||||
<a href={applyForIndexUrl} target="_blank" rel="noopener noreferrer" className="button button--primary">
|
||||
🎉 Add your plugin
|
||||
</a>
|
||||
</div>
|
||||
<div className="container" style={{ display: 'flex', flexFlow: 'column', marginTop: '4rem' }}>
|
||||
<input
|
||||
type="search"
|
||||
autoComplete="off"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.currentTarget.value)}
|
||||
placeholder="Search for a plugin ..."
|
||||
className="wp-plugin-search"
|
||||
/>
|
||||
<div className="wp-plugins-list">
|
||||
{/* {query.length == 0 && <NewPluginPanel />} */}
|
||||
{searchedPlugins.map((plugin, idx) => (
|
||||
<PluginPanel key={idx} plugin={plugin.item} />
|
||||
))}
|
||||
{/* <div className={clsx('col col--6')}>
|
||||
<div className={clsx('card margin-horiz--sm margin-vert--md ')}>
|
||||
<div className={clsx('card__header row')}>
|
||||
<div className={clsx('col col--8')}>
|
||||
<a href={applyForIndexUrl}>
|
||||
<h3>Add your own plugin</h3>
|
||||
</a>
|
||||
<p>You can simply add your own plugin to this index.</p>
|
||||
</div>
|
||||
<a
|
||||
href={applyForIndexUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={clsx('col col--4 text--right')}
|
||||
>
|
||||
<svg
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 100 100"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M88.2357 38.0952H61.9048V11.7643C61.9048 5.29524 56.4714 0 50 0C43.5286 0 38.0952 5.29524 38.0952 11.7643V38.0952H11.7643C5.29524 38.0952 0 43.5286 0 50C0 56.4714 5.29524 61.9048 11.7643 61.9048H38.0952V88.2357C38.0952 94.7048 43.5286 100 50 100C56.4714 100 61.9048 94.7048 61.9048 88.2357V61.9048H88.2357C94.7048 61.9048 100 56.4714 100 50C100 43.5286 94.7048 38.0952 88.2357 38.0952Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div className={clsx('card__footer')}>
|
||||
<a
|
||||
href={applyForIndexUrl}
|
||||
className={clsx('button button--secondary button--outline button--block ')}
|
||||
>
|
||||
Add your own plugin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
.wp-plugins-list {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.wp-plugin-card {
|
||||
display: flex;
|
||||
position: relative;
|
||||
max-width: 32rem;
|
||||
color: var(--ifm-navbar-link-color);
|
||||
text-decoration: none;
|
||||
padding: .5rem 0 1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.wp-plugin-card:hover {
|
||||
color: var(--ifm-navbar-link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.wp-plugin-card:hover h3 {
|
||||
color: var(--ifm-link-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.wp-plugin-card h3 {
|
||||
color: var(--ifm-link-color);
|
||||
}
|
||||
|
||||
.wp-plugin-verified {
|
||||
position: absolute;
|
||||
top: .75rem;
|
||||
right: 1rem;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
.wp-plugin-tags {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.wp-plugin-search {
|
||||
width: 100%;
|
||||
max-width: 32rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 1rem 1rem 2.25rem;
|
||||
font-size: 1.1rem;
|
||||
appearance: none;
|
||||
background: var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat 0.75rem 1rem / 1.1rem 1.1rem;
|
||||
border-radius: .5rem;
|
||||
border: 1px solid var(--ifm-card-background-color);
|
||||
color: var(--ifm-navbar-search-input-color);
|
||||
}
|
||||
|
||||
.wp-plugin-search::placeholder {
|
||||
color: var(--ifm-navbar-search-input-color);
|
||||
}
|
||||
|
||||
.wp-plugin-breadcrumbs {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
@@ -1,16 +1,25 @@
|
||||
export type WoodpeckerPluginHeader = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
name?: string; // name of the plugin
|
||||
description?: string; // short description of the plugin
|
||||
url?: string; // url of the plugin normally link to forge
|
||||
tags?: string[]; // tags to categorize the plugin
|
||||
author?: string; // author of the plugin
|
||||
icon?: string; // url pointing to an icon
|
||||
containerImage?: string; // name of a container image
|
||||
containerImageUrl?: string; // url to a container image registry
|
||||
};
|
||||
|
||||
export type WoodpeckerPlugin = {
|
||||
export type WoodpeckerPluginIndexEntry = {
|
||||
'// todo'?: boolean;
|
||||
name: string; // name of the plugin
|
||||
docs: string; // http url to the docs.md file
|
||||
verified?: boolean; // plugins maintained by trusted parties
|
||||
};
|
||||
|
||||
export type WoodpeckerPlugin = WoodpeckerPluginHeader & {
|
||||
name: string;
|
||||
repoName: string;
|
||||
description: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
docs: string;
|
||||
docs: string; // body of the docs .md file
|
||||
verified: boolean; // we set verified to false when not explicitly set
|
||||
};
|
||||
|
||||
export type Content = {
|
||||
|
||||
Reference in New Issue
Block a user