Further progress on registry UI.
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Add package README view support and style adjustments
This commit is contained in:
parent
0bee324519
commit
7fcdf2c383
@ -4,12 +4,27 @@ await FS.ensureDir("./data");
|
|||||||
|
|
||||||
export interface IPackage {
|
export interface IPackage {
|
||||||
name: string;
|
name: string;
|
||||||
|
author: string;
|
||||||
|
description: string;
|
||||||
versions: string[];
|
versions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new Datastore<IPackage>({
|
export interface IApiKey {
|
||||||
filename: "data/db.json",
|
user: string;
|
||||||
|
key: string;
|
||||||
|
createdAt: Date;
|
||||||
|
lastAccess?: Date;
|
||||||
|
lastIP?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = {
|
||||||
|
package: new Datastore<IPackage>({
|
||||||
|
filename: "data/packages.json",
|
||||||
autoload: true,
|
autoload: true,
|
||||||
});
|
}),
|
||||||
|
api_key: new Datastore<IApiKey>({
|
||||||
|
filename: "data/api_keys.json",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
export default db;
|
export default db;
|
||||||
|
@ -13,6 +13,8 @@ export * as FS from "https://deno.land/std@0.62.0/fs/mod.ts";
|
|||||||
|
|
||||||
export * as Compress from "https://git.stamm.me/Deno/DenReg/raw/branch/master/tar/mod.ts";
|
export * as Compress from "https://git.stamm.me/Deno/DenReg/raw/branch/master/tar/mod.ts";
|
||||||
|
|
||||||
|
export { Marked } from "https://deno.land/x/markdown/mod.ts";
|
||||||
|
|
||||||
import DS from "https://raw.githubusercontent.com/hibas123/dndb/master/mod.ts";
|
import DS from "https://raw.githubusercontent.com/hibas123/dndb/master/mod.ts";
|
||||||
|
|
||||||
/// <reference path="./types/jsx.d.ts" />
|
/// <reference path="./types/jsx.d.ts" />
|
||||||
@ -20,6 +22,6 @@ export {
|
|||||||
React,
|
React,
|
||||||
jsx,
|
jsx,
|
||||||
Fragment,
|
Fragment,
|
||||||
} from "https://deno.hibas123.de/raw/@denreg-jsx/mod.ts";
|
} from "https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts";
|
||||||
|
|
||||||
export const Datastore = DS;
|
export const Datastore = DS;
|
||||||
|
@ -9,13 +9,15 @@ app.use(LoggerMW.logger({}));
|
|||||||
app.use(CorsMW.cors({}));
|
app.use(CorsMW.cors({}));
|
||||||
|
|
||||||
import api from "./http/api.ts";
|
import api from "./http/api.ts";
|
||||||
api(app.group("api"));
|
api(app.group("/api"));
|
||||||
|
|
||||||
import raw from "./http/raw.ts";
|
import raw from "./http/raw.ts";
|
||||||
raw(app.group("raw"));
|
raw(app.group("/raw"));
|
||||||
|
|
||||||
import view from "./http/views.ts";
|
import view from "./http/views.ts";
|
||||||
view(app.group("/"));
|
view(app);
|
||||||
|
|
||||||
|
console.log(app.router.trees.GET);
|
||||||
|
|
||||||
import render from "./renderer.tsx";
|
import render from "./renderer.tsx";
|
||||||
app.renderer = {
|
app.renderer = {
|
||||||
|
@ -12,19 +12,40 @@ export default function api(g: ABC.Group) {
|
|||||||
return { version: "1" };
|
return { version: "1" };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// g.post("/getapikey", getApiKey, basicauth("api"));
|
||||||
g.post("/package/:name", uploadPackage, basicauth("api"));
|
g.post("/package/:name", uploadPackage, basicauth("api"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async function getApiKey(ctx: ABC.Context) {
|
||||||
|
// const key = v4.generate();
|
||||||
|
|
||||||
|
// await db.api_key.insert({
|
||||||
|
// user: ctx.customContext.user,
|
||||||
|
// key,
|
||||||
|
// createdAt: new Date(),
|
||||||
|
// lastAccess: undefined,
|
||||||
|
// lastIP: undefined,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// key,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
async function uploadPackage(ctx: ABC.Context) {
|
async function uploadPackage(ctx: ABC.Context) {
|
||||||
const reqId = v4.generate();
|
const reqId = v4.generate();
|
||||||
const filename = "./tmp/" + reqId + ".tar";
|
const filename = "./tmp/" + reqId + ".tar";
|
||||||
const folder = "./tmp/" + reqId;
|
const folder = "./tmp/" + reqId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const packageName = ctx.params.name;
|
const packageName = ctx.params.name.toLowerCase();
|
||||||
|
|
||||||
if (!isValidPackageName(packageName))
|
if (!isValidPackageName(packageName)) {
|
||||||
throw new Error("Invalid package name");
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Invalid package name",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Writing body to tmp file:", filename);
|
console.log("Writing body to tmp file:", filename);
|
||||||
const file = await Deno.open(filename, {
|
const file = await Deno.open(filename, {
|
||||||
@ -53,7 +74,10 @@ async function uploadPackage(ctx: ABC.Context) {
|
|||||||
console.log("Checking meta.json");
|
console.log("Checking meta.json");
|
||||||
|
|
||||||
if (!meta?.version) {
|
if (!meta?.version) {
|
||||||
throw new Error("No version available in meta.json");
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "No version available in meta.json",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageVersion = meta.version;
|
const packageVersion = meta.version;
|
||||||
@ -61,28 +85,42 @@ async function uploadPackage(ctx: ABC.Context) {
|
|||||||
console.log("Checking correct version");
|
console.log("Checking correct version");
|
||||||
|
|
||||||
if (!isValidFullVersion(packageVersion)) {
|
if (!isValidFullVersion(packageVersion)) {
|
||||||
throw new Error("Invalid version. Version must be in format: 0.0.0");
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Invalid version. Version must be in format: 0.0.0",
|
||||||
|
};
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Checking for previous uploads");
|
console.log("Checking for previous uploads");
|
||||||
|
|
||||||
let packageMeta = await db.findOne({ name: packageName });
|
let packageMeta = await db.package.findOne({ name: packageName });
|
||||||
|
|
||||||
|
console.log(meta, packageMeta);
|
||||||
|
|
||||||
if (!packageMeta) {
|
if (!packageMeta) {
|
||||||
packageMeta = {
|
packageMeta = {
|
||||||
name: packageName,
|
name: packageName,
|
||||||
|
author: meta.author,
|
||||||
|
description: meta.description,
|
||||||
versions: [],
|
versions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insert(packageMeta);
|
await db.package.insert(packageMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Check if version was uploaded before");
|
console.log("Check if version was uploaded before");
|
||||||
|
|
||||||
if (packageMeta.versions.find((e) => e === meta.version)) {
|
if (packageMeta.versions.find((e) => e === meta.version)) {
|
||||||
throw new Error("Version was already uploaded!");
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Version was already uploaded!",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packageMeta.author = meta.author;
|
||||||
|
packageMeta.description = meta.description;
|
||||||
|
|
||||||
const bucketBase = "packages/" + packageName + "/" + packageVersion + "/";
|
const bucketBase = "packages/" + packageName + "/" + packageVersion + "/";
|
||||||
|
|
||||||
console.log("Uploading files to S3");
|
console.log("Uploading files to S3");
|
||||||
@ -107,7 +145,7 @@ async function uploadPackage(ctx: ABC.Context) {
|
|||||||
console.log("Setting new live version");
|
console.log("Setting new live version");
|
||||||
|
|
||||||
//TODO: Better option, since this could error whith multiple upload to the same package
|
//TODO: Better option, since this could error whith multiple upload to the same package
|
||||||
await db.update(
|
await db.package.update(
|
||||||
{ name: packageName },
|
{ name: packageName },
|
||||||
{
|
{
|
||||||
$set: { versions: [...packageMeta.versions, packageVersion] },
|
$set: { versions: [...packageMeta.versions, packageVersion] },
|
||||||
@ -115,6 +153,9 @@ async function uploadPackage(ctx: ABC.Context) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
console.log("Finished successfully");
|
console.log("Finished successfully");
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error while processing newly uploaded package");
|
console.error("Error while processing newly uploaded package");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -127,7 +168,4 @@ async function uploadPackage(ctx: ABC.Context) {
|
|||||||
await Deno.remove(filename).catch(console.error);
|
await Deno.remove(filename).catch(console.error);
|
||||||
await Deno.remove(folder, { recursive: true }).catch(console.error);
|
await Deno.remove(folder, { recursive: true }).catch(console.error);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import { ABC } from "../deps.ts";
|
import { ABC } from "../deps.ts";
|
||||||
|
import { extractPackagePath, getFile } from "../utils.ts";
|
||||||
import { sortVersions, extractPackagePath } from "../utils.ts";
|
|
||||||
|
|
||||||
import db, { IPackage } from "../db.ts";
|
|
||||||
|
|
||||||
import bucket from "../s3.ts";
|
|
||||||
|
|
||||||
export default function raw(g: ABC.Group) {
|
export default function raw(g: ABC.Group) {
|
||||||
g.get("/:package/*path", async (ctx) => {
|
g.get("/:package/*path", async (ctx) => {
|
||||||
@ -13,41 +8,17 @@ export default function raw(g: ABC.Group) {
|
|||||||
ctx.params.package
|
ctx.params.package
|
||||||
);
|
);
|
||||||
|
|
||||||
const meta = await db.findOne({ name: packageName });
|
|
||||||
|
|
||||||
console.log(packageName, await db.findOne({ name: packageName }));
|
|
||||||
|
|
||||||
const E404 = () => {
|
const E404 = () => {
|
||||||
ctx.response.status = 404;
|
ctx.response.status = 404;
|
||||||
ctx.response.body = "Not found!";
|
ctx.response.body = "// Not found!";
|
||||||
throw new Error("Not found!");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!meta || meta.versions.length < 1) return E404();
|
const result = await getFile(
|
||||||
|
packageName,
|
||||||
const versions = meta.versions.sort(sortVersions).reverse();
|
packageVersion,
|
||||||
|
|
||||||
if (!packageVersion) {
|
|
||||||
packageVersion = versions[0];
|
|
||||||
} else {
|
|
||||||
const v = versions.filter((e) =>
|
|
||||||
e.startsWith(packageVersion as string)
|
|
||||||
);
|
|
||||||
if (v.length < 1) return E404();
|
|
||||||
packageVersion = v[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const bucketPath = (
|
|
||||||
"packages/" +
|
|
||||||
packageName +
|
|
||||||
"/" +
|
|
||||||
packageVersion +
|
|
||||||
"/" +
|
|
||||||
ctx.params.path
|
ctx.params.path
|
||||||
).replace(/@/g, "§");
|
);
|
||||||
|
if (!result) return E404();
|
||||||
console.log("Getting file from:", bucketPath);
|
return result;
|
||||||
|
|
||||||
return (await bucket.getObject(bucketPath))?.body;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { ABC } from "../deps.ts";
|
import { ABC } from "../deps.ts";
|
||||||
import { basicauth } from "../utils.ts";
|
import { basicauth, extractPackagePath } from "../utils.ts";
|
||||||
|
|
||||||
export default function views(g: ABC.Group) {
|
export default function views(g: ABC.Application) {
|
||||||
g.get(
|
g.get(
|
||||||
"/",
|
"/",
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
return ctx.render("index");
|
return ctx.render("index", {
|
||||||
|
search: ctx.queryParams["q"],
|
||||||
|
});
|
||||||
// const render = await IndexView();
|
// const render = await IndexView();
|
||||||
// console.log(render);
|
// console.log(render);
|
||||||
// ctx.response.body = render;
|
// ctx.response.body = render;
|
||||||
@ -13,4 +15,15 @@ export default function views(g: ABC.Group) {
|
|||||||
},
|
},
|
||||||
basicauth("views")
|
basicauth("views")
|
||||||
);
|
);
|
||||||
|
g.get(
|
||||||
|
"/package/:package",
|
||||||
|
async (ctx) => {
|
||||||
|
let [packageName, packageVersion] = extractPackagePath(
|
||||||
|
ctx.params.package
|
||||||
|
);
|
||||||
|
|
||||||
|
return ctx.render("package", { packageName });
|
||||||
|
},
|
||||||
|
basicauth("views")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
/// <reference path="./types/jsx.d.ts" />
|
/// <reference path="./types/jsx.d.ts" />
|
||||||
import { React, jsx } from "./deps.ts";
|
import { React, jsx } from "./deps.ts";
|
||||||
|
|
||||||
|
import { v4 } from "https://deno.land/std/uuid/mod.ts";
|
||||||
|
|
||||||
class StringReader implements Deno.Reader {
|
class StringReader implements Deno.Reader {
|
||||||
private data: Uint8Array;
|
private data: Uint8Array;
|
||||||
private offset = 0;
|
private offset = 0;
|
||||||
@ -27,11 +29,12 @@ export default async function render(
|
|||||||
name: string,
|
name: string,
|
||||||
data: any
|
data: any
|
||||||
): Promise<Deno.Reader> {
|
): Promise<Deno.Reader> {
|
||||||
|
const id = v4.generate();
|
||||||
const component: {
|
const component: {
|
||||||
default: () => JSX.IntrinsicElements | Promise<JSX.IntrinsicElements>;
|
default: () => JSX.IntrinsicElements | Promise<JSX.IntrinsicElements>;
|
||||||
} = await import(`./views/${name}.tsx`);
|
} = await import(`./views/${name}.tsx?id=${id}.tsx`);
|
||||||
|
|
||||||
const res = await (<component.default {...data} />).render();
|
const res = await (<component.default {...data} />).render();
|
||||||
console.log(res);
|
|
||||||
return new StringReader(res as string);
|
return new StringReader(res as string);
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,8 @@ export const basicauth = (realm: string) => (next: ABC.HandlerFunc) => (
|
|||||||
|
|
||||||
if (config?.user[username]?.password === passwd) {
|
if (config?.user[username]?.password === passwd) {
|
||||||
console.log("User authenticated!");
|
console.log("User authenticated!");
|
||||||
|
if (!ctx.customContext) ctx.customContext = {};
|
||||||
|
ctx.customContext.user = username;
|
||||||
return next(ctx);
|
return next(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,6 +64,7 @@ export const basicauth = (realm: string) => (next: ABC.HandlerFunc) => (
|
|||||||
|
|
||||||
export function extractPackagePath(path: string): [string, string | undefined] {
|
export function extractPackagePath(path: string): [string, string | undefined] {
|
||||||
let packageName = "";
|
let packageName = "";
|
||||||
|
path = path.toLowerCase();
|
||||||
if (path.startsWith("@")) {
|
if (path.startsWith("@")) {
|
||||||
packageName = "@";
|
packageName = "@";
|
||||||
path = path.slice(1);
|
path = path.slice(1);
|
||||||
@ -84,3 +87,48 @@ export function extractPackagePath(path: string): [string, string | undefined] {
|
|||||||
|
|
||||||
return [packageName, packageVersion];
|
return [packageName, packageVersion];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import db from "./db.ts";
|
||||||
|
|
||||||
|
import bucket from "./s3.ts";
|
||||||
|
import { S3 } from "./deps.ts";
|
||||||
|
|
||||||
|
export async function getFile(
|
||||||
|
pkgName: string,
|
||||||
|
version: string | null | undefined,
|
||||||
|
file: string
|
||||||
|
): Promise<Uint8Array | null> {
|
||||||
|
const meta = await db.package.findOne({ name: pkgName });
|
||||||
|
|
||||||
|
if (!meta || meta.versions.length < 1) return null;
|
||||||
|
|
||||||
|
const versions = meta.versions.sort(sortVersions).reverse();
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
version = versions[0];
|
||||||
|
} else {
|
||||||
|
const v = versions.filter((e) => e.startsWith(version as string));
|
||||||
|
if (v.length < 1) return null;
|
||||||
|
version = v[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucketPath = (
|
||||||
|
"packages/" +
|
||||||
|
pkgName +
|
||||||
|
"/" +
|
||||||
|
version +
|
||||||
|
"/" +
|
||||||
|
file
|
||||||
|
).replace(/@/g, "§");
|
||||||
|
|
||||||
|
console.log("Getting file from:", bucketPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = (await bucket.getObject(bucketPath))?.body;
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err.message as string;
|
||||||
|
if (msg.indexOf("404") >= 0) return null;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
/// <reference path="../types/jsx.d.ts" />
|
/// <reference path="../types/jsx.d.ts" />
|
||||||
import { React } from "../deps.ts";
|
import { React } from "../deps.ts";
|
||||||
|
|
||||||
|
const styles = new TextDecoder().decode(
|
||||||
|
Deno.readFileSync("src/views/styles.css")
|
||||||
|
);
|
||||||
|
|
||||||
export default function Base(d: any, children: any[]) {
|
export default function Base(d: any, children: any[]) {
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link
|
{/* <link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://deno.hibas123.de/raw/@hibas123-theme@2.0.2/out/base.css"
|
href="https://deno.hibas123.de/raw/@hibas123-theme@2.0.2/out/base.css"
|
||||||
|
/> */}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/papercss@1.6.1/dist/paper.min.css"
|
||||||
/>
|
/>
|
||||||
|
<style innerHTML={styles}></style>
|
||||||
</head>
|
</head>
|
||||||
<body class="light-theme">{children}</body>
|
<body class="site">{children}</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
30
registry/src/views/_default.tsx
Normal file
30
registry/src/views/_default.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/// <reference path="../types/jsx.d.ts" />
|
||||||
|
import { React, Fragment } from "../deps.ts";
|
||||||
|
|
||||||
|
export function Main(a: any, children: any) {
|
||||||
|
return (
|
||||||
|
<div style="grid-area: main">
|
||||||
|
<div class="paper">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Menu({}: any, children: any) {
|
||||||
|
return (
|
||||||
|
<div style="grid-area: menu">
|
||||||
|
<div class="paper">
|
||||||
|
<div class="row flex-right">
|
||||||
|
<button class="sm-4">Login</button>
|
||||||
|
<button class="sm-4">SignUp</button>
|
||||||
|
</div>
|
||||||
|
<h3 class="sidebar-title" style="text-align:center">
|
||||||
|
<a href="/" style="all:inherit;">
|
||||||
|
DenReg
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -2,32 +2,74 @@
|
|||||||
import { React, Fragment } from "../deps.ts";
|
import { React, Fragment } from "../deps.ts";
|
||||||
import Base from "./_base.tsx";
|
import Base from "./_base.tsx";
|
||||||
import DB, { IPackage } from "../db.ts";
|
import DB, { IPackage } from "../db.ts";
|
||||||
|
import { sortVersions } from "../utils.ts";
|
||||||
|
|
||||||
function Package({ pkg }: { pkg: IPackage }) {
|
function Package({ pkg }: { pkg: IPackage }) {
|
||||||
const { name, versions } = pkg;
|
const { name, versions, author, description } = pkg;
|
||||||
|
|
||||||
|
const sorted = versions.sort(sortVersions).reverse();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="card elv-4">
|
<div
|
||||||
<div style="font-weight: bold">{name}</div>
|
class="card margin"
|
||||||
<ul class="list">
|
onClick={"window.location.href = '/package/" + name + "'"}
|
||||||
{versions.map((version) => (
|
>
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">
|
||||||
|
{name} <span class="badge">{sorted[0]}</span>
|
||||||
|
</h4>
|
||||||
|
<h5 class="card-subtitle">By {author}</h5>
|
||||||
|
<div class="card-text">
|
||||||
|
{/* {versions.map((version) => (
|
||||||
<li>{version}</li>
|
<li>{version}</li>
|
||||||
))}
|
))} */}
|
||||||
</ul>
|
{description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function index() {
|
import { Main, Menu } from "./_default.tsx";
|
||||||
const packages = await DB.find({});
|
|
||||||
|
export default async function index({ search }: any) {
|
||||||
|
let packages: IPackage[] = [];
|
||||||
|
if (search && search !== "") {
|
||||||
|
packages = await DB.package.find({
|
||||||
|
name: RegExp(`${search}`),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
packages = await DB.package.find({});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base>
|
<Base>
|
||||||
<div class="container">
|
<Main>
|
||||||
|
<form method="GET" action="./">
|
||||||
|
<div class="form-group margin">
|
||||||
|
{/* <label for="searchInput">Search</label> */}
|
||||||
|
<div style="display:flex">
|
||||||
|
<input
|
||||||
|
placeholder="Search..."
|
||||||
|
class="input-block"
|
||||||
|
type="text"
|
||||||
|
id="searchInput"
|
||||||
|
name="q"
|
||||||
|
value={search}
|
||||||
|
/>
|
||||||
|
<button>Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
{packages.map((pkg) => (
|
{packages.map((pkg) => (
|
||||||
<Package pkg={pkg} />
|
<Package pkg={pkg} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</Main>
|
||||||
|
<Menu>
|
||||||
|
<ul>
|
||||||
|
<li>Item</li>
|
||||||
|
</ul>
|
||||||
|
</Menu>
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
70
registry/src/views/package.tsx
Normal file
70
registry/src/views/package.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/// <reference path="../types/jsx.d.ts" />
|
||||||
|
import { React, Fragment, Marked } from "../deps.ts";
|
||||||
|
import Base from "./_base.tsx";
|
||||||
|
import DB, { IPackage } from "../db.ts";
|
||||||
|
import { sortVersions, getFile } from "../utils.ts";
|
||||||
|
|
||||||
|
// function Package({ pkg }: { pkg: IPackage }) {
|
||||||
|
// const { name, versions, author } = pkg;
|
||||||
|
|
||||||
|
// const sorted = versions.sort(sortVersions);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div
|
||||||
|
// class="card margin"
|
||||||
|
// onClick={"window.location.href = '/package/" + name + "'"}
|
||||||
|
// >
|
||||||
|
// <div class="card-body">
|
||||||
|
// <h4 class="card-title">{name}</h4>
|
||||||
|
|
||||||
|
// <ul class="card-text">
|
||||||
|
// {versions.map((version) => (
|
||||||
|
// <li>{version}</li>
|
||||||
|
// ))}
|
||||||
|
// {author} {sorted[0]}
|
||||||
|
// </ul>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
import { Main, Menu } from "./_default.tsx";
|
||||||
|
|
||||||
|
export default async function index({ packageName }: any) {
|
||||||
|
const pkg = await DB.package.findOne({ name: packageName });
|
||||||
|
|
||||||
|
if (!pkg)
|
||||||
|
return (
|
||||||
|
<Base>
|
||||||
|
<h1>Not found</h1>
|
||||||
|
</Base>
|
||||||
|
);
|
||||||
|
|
||||||
|
const readmeContent = await getFile(
|
||||||
|
packageName,
|
||||||
|
undefined,
|
||||||
|
"README.md"
|
||||||
|
).then((res) => {
|
||||||
|
if (res)
|
||||||
|
return Marked.parse(new TextDecoder().decode(res)).content as string;
|
||||||
|
else return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Base>
|
||||||
|
<Main>
|
||||||
|
<h2 style="margin-bottom: 0">Package: {pkg.name}</h2>
|
||||||
|
<h4 class="text-muted" style="margin-top: 0; margin-left: .5rem">
|
||||||
|
By {pkg.author}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
{readmeContent !== undefined ? (
|
||||||
|
<div innerHTML={readmeContent} />
|
||||||
|
) : (
|
||||||
|
<div class="alert alert-warning">No README.md found!</div>
|
||||||
|
)}
|
||||||
|
</Main>
|
||||||
|
<Menu></Menu>
|
||||||
|
</Base>
|
||||||
|
);
|
||||||
|
}
|
17
registry/src/views/styles.css
Normal file
17
registry/src/views/styles.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.site {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "main menu";
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 64rem) {
|
||||||
|
.site {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"menu"
|
||||||
|
"main";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user