Adding basic file browsing support
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Fabian Stamm 2020-10-14 02:52:02 +02:00
parent 78c40e4819
commit 46d8f8b289
48 changed files with 1097 additions and 70 deletions

View File

@ -1,5 +1,8 @@
{
"deno.enable": true,
"deno.unstable": true,
"debug.javascript.usePreview": false
"debug.javascript.usePreview": false,
"deno.import_intellisense_origins": {
"https://deno.land": true
}
}

BIN
registry/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

11
registry/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/public/ms-icon-70x70.png"/><square150x150logo src="/public/ms-icon-150x150.png"/><square310x310logo src="/public/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
registry/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
registry/public/file.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-folder"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>

After

Width:  |  Height:  |  Size: 311 B

View File

@ -0,0 +1,41 @@
{
"name": "Deno package Registry",
"short_name": "DenReg",
"description": "A deno package registry, that can be self hosted easily",
"start_url": "/",
"scope": "/",
"theme_color": "#ffffff",
"display": "standalone",
"icons": [
{
"src": "/public/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png",
"density": "0.75"
},
{
"src": "/public/apple-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"density": "1.5"
},
{
"src": "/public/favicon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"density": "2.0"
},
{
"src": "/public/apple-icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"density": "3.0"
},
{
"src": "/public/android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"density": "4.0"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

2
registry/public/paper.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
await fetch("https://unpkg.com/papercss/dist/paper.min.css")
.then((res) => res.arrayBuffer())
.then((data) =>
Deno.writeFile("./public/paper.min.css", new Uint8Array(data))
)
.catch(console.error);

View File

@ -1,29 +1,33 @@
// export { MongoClient, ObjectId } from "https://deno.land/x/mongo@v0.9.1/mod.ts";
export * as S3 from "https://deno.land/x/s3@0.2.0/mod.ts";
export * as S3 from "https://deno.land/x/s3/mod.ts";
export * as Ini from "https://deno.land/x/ini/mod.ts";
export * as Ini from "https://deno.hibas123.de/raw/ini@0.0.1/mod.ts";
export * as ABC from "https://deno.land/x/abc@v1/mod.ts";
export * as CorsMW from "https://deno.land/x/abc@v1/middleware/cors.ts";
export * as LoggerMW from "https://deno.land/x/abc@v1/middleware/logger.ts";
export * as Path from "https://deno.land/std@0.63.0/path/mod.ts";
export * as FS from "https://deno.land/std@0.63.0/fs/mod.ts";
export * as Base64 from "https://deno.land/std@0.63.0/encoding/base64.ts";
export * as Hash from "https://deno.land/std@0.63.0/hash/mod.ts";
export * as Path from "https://deno.land/std@0.69.0/path/mod.ts";
export * as FS from "https://deno.land/std@0.69.0/fs/mod.ts";
export * as Base64 from "https://deno.land/std@0.69.0/encoding/base64.ts";
export * as Hash from "https://deno.land/std@0.69.0/hash/mod.ts";
export * as Colors from "https://deno.land/std@0.69.0/fmt/colors.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";
export { default as Prism } from "https://cdn.skypack.dev/prismjs";
// export { Marked } from "https://deno.land/x/markdown/mod.ts";
export { Marked } from "../../markdown/mod.ts";
import DS from "https://raw.githubusercontent.com/hibas123/dndb/master/mod.ts";
/// <reference path="./types/jsx.d.ts" />
export {
React,
jsx,
Fragment,
} from "https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts";
export { React, jsx, Fragment } from "../../jsx-html/mod.ts";
// export {
// React,
// jsx,
// Fragment,
// } from "https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts";
export const Datastore = DS;

View File

@ -1,5 +1,5 @@
/// <reference path="./types/jsx.d.ts" />
import { ABC, CorsMW, LoggerMW } from "./deps.ts";
import { ABC, CorsMW, LoggerMW, Path } from "./deps.ts";
import config from "./config.ts";
const port = config?.api?.port || 8000;
@ -8,6 +8,30 @@ const app = new ABC.Application();
app.use(LoggerMW.logger({}));
app.use(CorsMW.cors({}));
// app.static("/public", "./public");
const MIMEDB = {
".css": "text/css",
".json": "application/json",
".js": "application/javascript",
".gz": "applcation/gzip",
".ico": "image/x-icon",
".png": "image/png",
".svg": "image/svg+xml",
".xml": "application/xml",
};
app.get("/public/*path", async (ctx) => {
const filePath = Path.join("./public", ctx.params.path);
console.log(filePath);
ctx.blob(
await Deno.open(filePath, { read: true }),
(MIMEDB as any)[Path.extname(filePath)]
);
});
import api from "./http/api.ts";
api(app.group("/api"));
@ -17,7 +41,15 @@ raw(app.group("/raw"));
import view from "./http/views.ts";
view(app);
console.log(app.router.trees.GET);
// function logNode(router: Node, indent = 0) {
// trees.map((tree) => {
// console.log("Path:", tree.path);
// tree.children?.forEach((node) => logNode(node, indent + 3));
// });
// }
// const trees = Object.values(app.router) as Node[];
// trees.forEach(logNode);
import render from "./renderer.tsx";
app.renderer = {

View File

@ -1,4 +1,4 @@
import { ABC, Path, Compress, FS } from "../deps.ts";
import { ABC, Path, Compress, FS, Colors } from "../deps.ts";
import bucket from "../s3.ts";
import { isValidPackageName, basicauth, isValidFullVersion } from "../utils.ts";
@ -138,16 +138,14 @@ async function uploadPackage(ctx: ABC.Context) {
});
for await (const file of walker) {
const relative = Path.relative(folder, file.path);
const relative = Path.relative(folder, file.path).replace("\\", "/");
console.log("Normalised path:", relative.replace("\\", "/"));
const bucketPath = (bucketBase + relative).replace(/@/g, "§");
console.log("Uploading file:", file.path, bucketPath, bucketBase);
await bucket.putObject(
bucketPath,
await Deno.readAll(await Deno.open(file.path)),
{}
);
const body = await Deno.readAll(await Deno.open(file.path));
await bucket.putObject(bucketPath, body, {});
}
console.log("Setting new live version");
@ -168,7 +166,9 @@ async function uploadPackage(ctx: ABC.Context) {
success: true,
};
} catch (err) {
console.error("Error while processing newly uploaded package");
console.error(
Colors.red("Error while processing newly uploaded package")
);
console.error(err);
return {
success: false,

View File

@ -1,5 +1,11 @@
import { ABC } from "../deps.ts";
import { extractPackagePath, getFile } from "../utils.ts";
import { Path } from "../deps.ts";
import type { ABC } from "../deps.ts";
import {
extractPackagePath,
getAbsolutePackageVersion,
getFile,
} from "../utils.ts";
import db from "../db.ts";
const MAX_FIXED_CACHE_AGE = 60 * 60 * 24 * 365;
const MAX_FLOATING_CACHE_AGE = 60 * 30;
@ -11,16 +17,27 @@ export default function raw(g: ABC.Group) {
ctx.params.package
);
const pkg = await db.package.findOne({ name: packageName });
packageVersion = getAbsolutePackageVersion(pkg, packageVersion);
const E404 = () => {
ctx.response.status = 404;
ctx.response.body = "// Not found!";
};
const result = await getFile(
packageName,
packageVersion,
ctx.params.path
const filepath = ctx.params.path;
const result = await getFile(packageName, packageVersion, filepath);
if (filepath.endsWith(".js")) {
const tsFile = filepath.substr(0, filepath.length - 3) + ".d.ts";
const tsResult = await getFile(packageName, packageVersion, tsFile);
if (tsResult) {
ctx.response.headers.set(
"X-TypeScript-Types",
"./" + Path.posix.basename(tsFile)
);
}
}
if (packageVersion && result) {
ctx.response.headers.set(

View File

@ -1,8 +1,16 @@
import { ABC } from "../deps.ts";
import { basicauth, extractPackagePath, sortVersions } from "../utils.ts";
import type { ABC } from "../deps.ts";
import {
basicauth,
extractPackagePath,
getBucketFilePath,
getFile,
getAbsolutePackageVersion,
sortVersions,
} from "../utils.ts";
import { Hash } from "../deps.ts";
import { Hash, Path } from "../deps.ts";
import db, { IPackage } from "../db.ts";
import bucket from "../s3.ts";
const MAX_CACHE_AGE = 60 * 30; // 30 Minutes
@ -44,6 +52,7 @@ export default function views(g: ABC.Application) {
ctx.response.headers.set("cache-control", CACHE_CONTROL);
ctx.response.headers.set("E-Tag", etag);
});
g.get("/package/:package", async (ctx) => {
let [packageName, packageVersion] = extractPackagePath(
ctx.params.package
@ -61,4 +70,84 @@ export default function views(g: ABC.Application) {
ctx.response.headers.set("cache-control", CACHE_CONTROL);
ctx.response.headers.set("E-Tag", etag);
});
async function handleBrowse(ctx: ABC.Context) {
const E404 = () => {
ctx.response.status = 404;
ctx.response.body = "// Not found!";
};
let [packageName, packageVersion] = extractPackagePath(
ctx.params.package
);
const pkg = await db.package.findOne({ name: packageName });
packageVersion = getAbsolutePackageVersion(pkg, packageVersion);
if (!packageVersion) return E404();
const path = ctx.params.path || "";
const fileContent = await getFile(packageName, packageVersion, path);
if (fileContent) {
await ctx.render("browse_file", {
pkg,
version: packageVersion,
content: new TextDecoder().decode(fileContent.data),
ext: Path.extname(path),
path: `${packageName}@${packageVersion}/${path}`,
});
} else {
const bucketPath = await getBucketFilePath(
packageName,
packageVersion,
path
);
if (!bucketPath) return E404();
console.log(bucketPath);
const filesItr = bucket.listAllObjects({
batchSize: 100,
prefix: bucketPath,
// delimiter: "/",
});
let files: { name: string; size: number }[] = [];
let directories: Set<string> = new Set();
let readme: string | null = null;
for await (let file of filesItr) {
const relPath = Path.posix.relative(bucketPath, file.key || "");
if (relPath.indexOf("/") >= 0) {
directories.add(relPath.split("/")[0]);
} else {
files.push({ name: relPath, size: file.size || -1 });
if (relPath.toLowerCase() === "readme.md") {
let readmeCont = await getFile(
packageName,
packageVersion,
Path.posix.join(path, relPath)
);
if (readmeCont) {
readme = new TextDecoder().decode(readmeCont?.data);
}
}
}
}
await ctx.render("browse_folder", {
pkg,
version: packageVersion,
readme,
files,
directories: Array.from(directories.values()).map((e) => ({
name: e,
})),
path: `${packageName}@${packageVersion}/${path}`,
});
}
}
g.get("/browse/:package", handleBrowse);
g.get("/browse/:package/*path", handleBrowse);
}

View File

@ -5,6 +5,18 @@ if (!config.s3) {
throw new Error("Config is missing [s3] section!");
}
if (!config.s3.endpoint) {
throw new Error("Config is missing s3.endpoint!");
}
if (!config.s3.accessKey) {
throw new Error("Config is missing s3.accessKey!");
}
if (!config.s3.secretKey) {
throw new Error("Config is missing s3.secretKey!");
}
const bucket = new S3.S3Bucket({
bucket: config.s3.bucket || "deno-registry",
endpointURL: config.s3.endpoint,

3
registry/src/test.ts Normal file
View File

@ -0,0 +1,3 @@
import * as Prism from "./vendor/prism/prism.js";
console.log((window as any).Prism);

View File

@ -87,30 +87,38 @@ export function extractPackagePath(path: string): [string, string | undefined] {
return [packageName, packageVersion];
}
import db from "./db.ts";
import type { IPackage } from "./db.ts";
import bucket from "./s3.ts";
export async function getFile(
pkgName: string,
version: string | null | undefined,
file: string
): Promise<{ etag: string; data: Uint8Array } | null | undefined> {
console.log("Searching for file: %s/%s@%s", pkgName, file, version);
const meta = await db.package.findOne({ name: pkgName });
export function getAbsolutePackageVersion(
pkg?: IPackage | null,
version?: string
) {
if (!pkg || pkg.versions.length < 1) return undefined;
if (!meta || meta.versions.length < 1) return null;
const versions = meta.versions.sort(sortVersions).reverse();
const versions = pkg.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;
if (v.length < 1) return undefined;
version = v[0];
}
return version;
}
export async function getBucketFilePath(
pkgName: string,
version: string,
file: string
) {
if (file.startsWith("/")) {
file = file.substr(1);
}
const bucketPath = (
"packages/" +
pkgName +
@ -120,11 +128,24 @@ export async function getFile(
file
).replace(/@/g, "§");
return bucketPath;
}
export async function getFile(
pkgName: string,
version: string | undefined,
file: string
): Promise<{ etag: string; data: Uint8Array } | null | undefined> {
if (!version) return undefined;
const bucketPath = await getBucketFilePath(pkgName, version, file);
if (!bucketPath) return null;
console.log("Getting file from:", bucketPath);
try {
const res = await bucket.getObject(bucketPath);
if (!res) return undefined;
if (!res || res.body.byteLength === 0) return undefined;
return {
etag: res.etag,
data: res.body,

7
registry/src/vendor/prism.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import "./prism/prism.js";
const Prism = (window as any).Prism;
delete (window as any).Prism;
export default Prism;

218
registry/src/vendor/prism/prism.css vendored Normal file
View File

@ -0,0 +1,218 @@
/* PrismJS 1.22.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apl+applescript+aql+arduino+arff+asciidoc+aspnet+asm6502+autohotkey+autoit+bash+basic+batch+bbcode+birb+bison+bnf+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cil+clojure+cmake+coffeescript+concurnas+csp+crystal+css-extras+cypher+d+dart+dax+dhall+diff+django+dns-zone-file+docker+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+firestore-security-rules+flow+fortran+ftl+gml+gcode+gdscript+gedcom+gherkin+git+glsl+go+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+http+hpkp+hsts+ichigojam+icon+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keyman+kotlin+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+lolcode+lua+makefile+markdown+markup-templating+matlab+mel+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nginx+nim+nix+nsis+objectivec+ocaml+opencl+oz+parigp+parser+pascal+pascaligo+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plsql+powerquery+powershell+processing+prolog+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+q+qml+qore+r+racket+jsx+tsx+reason+regex+renpy+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+stan+iecst+stylus+swift+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+turtle+twig+typescript+typoscript+unrealscript+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+wiki+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+inline-color */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.token.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
/* This background color was intended by the author of this theme. */
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
span.inline-color-wrapper {
/*
* The background image is the following SVG inline in base 64:
*
* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2">
* <path fill="gray" d="M0 0h2v2H0z"/>
* <path fill="white" d="M0 0h1v1H0zM1 1h1v1H1z"/>
* </svg>
*
* SVG-inlining explained:
* https://stackoverflow.com/a/21626701/7595472
*/
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=");
/* This is to prevent visual glitches where one pixel from the repeating pattern could be seen. */
background-position: center;
background-size: 110%;
display: inline-block;
height: 1.333ch;
width: 1.333ch;
margin: 0 .333ch;
box-sizing: border-box;
border: 1px solid white;
outline: 1px solid rgba(0,0,0,.5);
overflow: hidden;
}
span.inline-color {
display: block;
/* To prevent visual glitches again */
height: 120%;
width: 120%;
}

239
registry/src/vendor/prism/prism.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@ const styles = new TextDecoder().decode(
Deno.readFileSync("src/views/styles.css")
);
// href="https://unpkg.com/papercss@1.6.1/dist/paper.min.css"
export default function Base(p: any, children: any[]) {
const title = p.title || "DenReg";
return (
@ -14,9 +15,86 @@ export default function Base(p: any, children: any[]) {
rel="stylesheet"
href="https://deno.hibas123.de/raw/@hibas123-theme@2.0.2/out/base.css"
/> */}
<link rel="stylesheet" href="/public/paper.min.css" />
<link
rel="apple-touch-icon"
sizes="57x57"
href="/public/apple-icon-57x57.png"
/>
<link
rel="apple-touch-icon"
sizes="60x60"
href="/public/apple-icon-60x60.png"
/>
<link
rel="apple-touch-icon"
sizes="72x72"
href="/public/apple-icon-72x72.png"
/>
<link
rel="apple-touch-icon"
sizes="76x76"
href="/public/apple-icon-76x76.png"
/>
<link
rel="apple-touch-icon"
sizes="114x114"
href="/public/apple-icon-114x114.png"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="/public/apple-icon-120x120.png"
/>
<link
rel="apple-touch-icon"
sizes="144x144"
href="/public/apple-icon-144x144.png"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="/public/apple-icon-152x152.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/public/apple-icon-180x180.png"
/>
<link
rel="icon"
type="image/png"
sizes="192x192"
href="/public/android-icon-192x192.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/public/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/public/favicon-96x96.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/public/favicon-16x16.png"
/>
<link rel="manifest" href="/public/manifest.json" />
<meta name="msapplication-TileColor" content="#ffffff" />
<meta
name="msapplication-TileImage"
content="/public/ms-icon-144x144.png"
/>
<meta name="theme-color" content="#ffffff"></meta>
<link
href="https://unpkg.com/prismjs/themes/prism.css"
rel="stylesheet"
href="https://unpkg.com/papercss@1.6.1/dist/paper.min.css"
/>
<style innerHTML={styles}></style>
<title>{title}</title>

View File

@ -0,0 +1,113 @@
/// <reference path="../types/jsx.d.ts" />
import { React, Marked } from "../deps.ts";
import type { IPackage } from "../db.ts";
import { sortVersions } from "../utils.ts";
import Prism from "../vendor/prism.ts";
interface IEntryParams {
name: string;
size?: number;
directory?: true;
}
export function Entry({ name, size, directory }: IEntryParams) {
return (
<a class="browse-list-item" href={"./" + name + (directory ? "/" : "")}>
<img src={directory ? "/public/folder.svg" : "/public/file.svg"} />
<span>{name}</span>
{size && <span class="browse-list-item-size">{size}</span>}
</a>
);
}
interface IEntryListParams {
files: { name: string; size: number }[];
directories: { name: string }[];
}
export function EntryList({ directories, files }: IEntryListParams) {
return (
<div class="card" style="padding: 1rem;">
<div>
{directories.map((e) => (
<Entry name={e.name} directory />
))}
{files.map((e) => (
<Entry name={e.name} size={e.size} />
))}
</div>
</div>
);
}
interface IRenderFileInterface {
content: string;
ext: string;
}
const languages: { [key: string]: string } = {
js: "javascript",
cjs: "javascript",
mjs: "javascript",
ts: "typescript",
c: "clike",
svelte: "html",
cs: "csharp",
hb: "handlebars",
ps: "powershell",
sh: "bash",
bat: "batch",
yml: "yaml",
};
export function RenderFile({ content, ext }: IRenderFileInterface) {
if (ext === ".md") {
content = Marked.parse(content).content;
return (
<div
class="card browse-code-block"
style="margin-top: 1rem; padding: 1rem;"
innerHTML={content}
/>
);
} else {
let lang = languages[ext.replace(".", "")] || ext.replace(".", "");
if (Prism.languages[lang]) {
content = Prism.highlight(content, Prism.languages[lang], lang);
}
return <pre innerHTML={content} />;
}
}
interface IBrowseHeaderParams {
pkg: IPackage;
version?: string;
path: string;
}
export function BrowseHeader({ pkg, version, path }: IBrowseHeaderParams) {
return (
<>
<div style="display: flex;">
<div>
<h2 style="margin-bottom: 0">Browse: {pkg.name}</h2>
<h4 class="text-muted" style="margin-top: 0; margin-left: .5rem">
By {pkg.owner}
</h4>
</div>
<div>
Version:
<select>
{pkg.versions.sort(sortVersions).map((v) => (
<option selected={version === v}>{v}</option>
))}
</select>
</div>
</div>
<div class="browse-path">{path}</div>
</>
);
}

View File

@ -0,0 +1,20 @@
/// <reference path="../types/jsx.d.ts" />
import { React, Fragment, Marked } from "../deps.ts";
import type { IPackage } from "../db.ts";
export default async function index({
pkg,
version,
}: {
pkg: IPackage;
version?: string;
}) {
return (
<>
<h2 style="margin-bottom: 0">Package: {pkg.name}</h2>
<h4 class="text-muted" style="margin-top: 0; margin-left: .5rem">
By {pkg.owner}
</h4>
</>
);
}

View File

@ -0,0 +1,38 @@
/// <reference path="../types/jsx.d.ts" />
import { React } from "../deps.ts";
import Base from "./_base.tsx";
import type { IPackage } from "../db.ts";
import { Main, Menu } from "./_default.tsx";
import { RenderFile, EntryList, BrowseHeader } from "./_browse.tsx";
export default async function index({
pkg,
version,
content,
ext,
path,
}: {
pkg: IPackage;
version?: string;
content: string;
ext: string;
path: string;
}) {
if (!pkg)
return (
<Base>
<h1>Not found</h1>
</Base>
);
return (
<Base title={"DenReg - " + pkg.name}>
<Main>
<BrowseHeader pkg={pkg} version={version} path={path} />
<RenderFile content={content} ext={ext} />
</Main>
<Menu></Menu>
</Base>
);
}

View File

@ -0,0 +1,43 @@
/// <reference path="../types/jsx.d.ts" />
import { React } from "../deps.ts";
import Base from "./_base.tsx";
import type { IPackage } from "../db.ts";
import { Main, Menu } from "./_default.tsx";
import { RenderFile, EntryList, BrowseHeader } from "./_browse.tsx";
export default async function index({
pkg,
version,
files,
directories,
readme,
path,
}: {
pkg: IPackage;
version?: string;
files: { name: string; size: number }[];
directories: { name: string }[];
readme?: string;
path: string;
}) {
if (!pkg)
return (
<Base>
<h1>Not found</h1>
</Base>
);
return (
<Base title={"DenReg - " + pkg.name}>
<Main>
<BrowseHeader pkg={pkg} version={version} path={path} />
<EntryList directories={directories} files={files} />
{readme && <RenderFile content={readme} ext={".md"} />}
</Main>
<Menu></Menu>
</Base>
);
}

View File

@ -1,7 +1,7 @@
/// <reference path="../types/jsx.d.ts" />
import { React, Fragment } from "../deps.ts";
import Base from "./_base.tsx";
import DB, { IPackage } from "../db.ts";
import type { IPackage } from "../db.ts";
import { sortVersions } from "../utils.ts";
function Package({ pkg }: { pkg: IPackage }) {

View File

@ -1,9 +1,9 @@
/// <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";
import type { IPackage } from "../db.ts";
import { sortVersions, getFile, getAbsolutePackageVersion } from "../utils.ts";
import PkgHeader from "./_pkgheader.tsx";
// function Package({ pkg }: { pkg: IPackage }) {
// const { name, versions, author } = pkg;
@ -44,6 +44,7 @@ export default async function index({
</Base>
);
version = getAbsolutePackageVersion(pkg, version);
const readmeContent = await getFile(pkg.name, version, "README.md").then(
(res) => {
if (res)
@ -56,23 +57,13 @@ export default async function index({
return (
<Base title={"DenReg - " + pkg.name}>
<Main>
<h2 style="margin-bottom: 0">Package: {pkg.name}</h2>
<h4 class="text-muted" style="margin-top: 0; margin-left: .5rem">
By {pkg.owner}
</h4>
<PkgHeader pkg={pkg} version={version} />
<div class="tabs">
<input id="tab1" type="radio" name="tabs" checked />
<label for="tab1">Readme</label>
<input id="tab2" type="radio" name="tabs" />
<label for="tab2">Versions</label>
{/*
<input id="tab3" type="radio" name="tabs" />
<label for="tab3">Tab 3</label>
<input id="tab4" type="radio" name="tabs" />
<label for="tab4">Tab 4</label> */}
<div class="content" id="content1">
{readmeContent !== undefined ? (
<div
@ -95,7 +86,11 @@ export default async function index({
</div>
</div>
</Main>
<Menu></Menu>
<Menu>
<a href={`/browse/${pkg.name}${version ? "@" + version : ""}/`}>
Browse Files
</a>
</Menu>
</Base>
);
}

View File

@ -27,3 +27,34 @@ code {
margin: 1rem 0;
}
}
.browse-list-item {
display: flex;
margin: 0.2rem;
text-decoration: none;
color: unset;
background-image: unset;
/* flex */
}
.browse-list-item:hover {
background-color: var(--muted-light);
}
.browse-list-item > img {
height: 1rem;
width: auto;
border: none !important;
margin-right: 1rem;
}
.browse-list-item-size {
margin-left: auto;
}
.browse-path {
margin: 1rem;
font-size: 1.2rem;
font-weight: bold;
}