Compare commits
36 Commits
6bfcb3642d
...
master
Author | SHA1 | Date | |
---|---|---|---|
a0fef1ef76 | |||
3cda4ee8c8 | |||
e5829d9a4f | |||
3efe4fc34e | |||
22a447604b | |||
9dfb8d65d3 | |||
6bc090e51b | |||
965ca33d33 | |||
0202946813 | |||
53a11eccf6 | |||
325c1a4d7d | |||
c718e8898d | |||
6fe3ddbd37 | |||
79bcef0698 | |||
2af5d4f823 | |||
7c1166bf87 | |||
639287663d | |||
fbbb66d5af | |||
df1f4965ad | |||
10930db13d | |||
6b88be1b18 | |||
c9bd5c7d18 | |||
d1244ac0a7 | |||
e56d8b4548 | |||
ed1ce0cc0f | |||
252bf4aac3 | |||
a3a7370be0 | |||
10a7c26642 | |||
7e821f9771 | |||
1b7c4847dc | |||
2194bf199e | |||
f3f6f0d7bc | |||
13bc1b07cd | |||
1b2d85eeef | |||
46d8f8b289 | |||
78c40e4819 |
@ -12,7 +12,7 @@ steps:
|
||||
from_secret: docker_password
|
||||
auto_tag: true
|
||||
context: registry
|
||||
repo: hibas123.azurecr.io/denreg
|
||||
registry: hibas123.azurecr.io
|
||||
repo: docker.hibas123.de/denreg
|
||||
registry: docker.hibas123.de
|
||||
dockerfile: registry/Dockerfile
|
||||
debug: false
|
||||
|
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test.ini
|
5
.vscode/settings.json
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +1,31 @@
|
||||
import { Colors, Path, FS, Compress, Base64 } from "../deps.ts";
|
||||
import { getMeta, IMeta, log, getConfig } from "../global.ts";
|
||||
import { runHooks } from "../helper/run_script.ts";
|
||||
import { ServerError } from "../helper/server_error.ts";
|
||||
|
||||
async function runScript(script: string) {
|
||||
console.log(Colors.bold(Colors.blue("Running script:")), script);
|
||||
const runPerm = await Deno.permissions.query({
|
||||
name: "run",
|
||||
});
|
||||
|
||||
if (runPerm.state !== "granted") {
|
||||
console.log(
|
||||
Colors.red("Missing --allow-run permission. Cannot run hooks!")
|
||||
);
|
||||
throw new Error("Missing --allow-run permission. Cannot run hooks!");
|
||||
}
|
||||
|
||||
const process = Deno.run({
|
||||
cmd: ["deno", "run", "-A", "--unstable", script],
|
||||
});
|
||||
|
||||
const status = await process.status();
|
||||
|
||||
console.log(Colors.bold(Colors.blue("Finished script:")), script);
|
||||
|
||||
if (!status.success) {
|
||||
throw new Error(
|
||||
"A hook did not complete sucessfully. This is not a issue of denreg!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function runHooks(hooks: undefined | string | string[]) {
|
||||
if (!hooks) return;
|
||||
if (typeof hooks === "string") {
|
||||
hooks = [hooks];
|
||||
}
|
||||
|
||||
for (const hook of hooks) {
|
||||
await runScript(hook);
|
||||
}
|
||||
}
|
||||
import { checkPermOrExit } from "../helper/permission.ts";
|
||||
|
||||
export default async function publish(options: { dry: boolean }) {
|
||||
const meta: IMeta = await getMeta();
|
||||
const originalMeta = await getMeta();
|
||||
|
||||
if (originalMeta.hooks) {
|
||||
log("Running prepublish hooks");
|
||||
await runHooks(originalMeta.hooks.prepublish);
|
||||
}
|
||||
|
||||
const meta = await getMeta();
|
||||
|
||||
//TODO: Output Diff between original and result meta
|
||||
|
||||
if (!meta.name) throw new Error("name is not set in meta.json");
|
||||
if (!meta.version) throw new Error("version is not set in meta.json");
|
||||
if (!meta.files || !Array.isArray(meta.files) || meta.files.length <= 0)
|
||||
throw new Error("files is not set or empty in meta.json");
|
||||
|
||||
if (meta.hooks) {
|
||||
log("Running prepublish hooks");
|
||||
await runHooks(meta.hooks.prepublish);
|
||||
}
|
||||
|
||||
const tmpDir = await Deno.makeTempDir();
|
||||
const packedFile = (await Deno.makeTempFile()) + ".tar";
|
||||
|
||||
try {
|
||||
const walker = FS.walk(".", {
|
||||
const walker = FS.walk(meta.root || ".", {
|
||||
includeDirs: false,
|
||||
includeFiles: true,
|
||||
match: meta.files.map((file) => Path.globToRegExp(file)),
|
||||
@ -66,17 +33,18 @@ export default async function publish(options: { dry: boolean }) {
|
||||
|
||||
log("Copying files to package to", tmpDir);
|
||||
|
||||
const copy = async (path: string) => {
|
||||
const dest = Path.join(tmpDir, path);
|
||||
const copy = async (path: string, abs?: boolean) => {
|
||||
const relative = abs ? path : Path.relative(meta.root || ".", path);
|
||||
log("Adding file:", path, "as", relative);
|
||||
const dest = Path.join(tmpDir, relative);
|
||||
await FS.ensureDir(Path.dirname(dest));
|
||||
await FS.copy(path, dest);
|
||||
};
|
||||
|
||||
await copy("meta.json");
|
||||
await copy("meta.json", true);
|
||||
|
||||
for await (const file of walker) {
|
||||
await copy(file.path);
|
||||
log("Adding file:", file.path);
|
||||
}
|
||||
|
||||
log("Compressing files into", packedFile);
|
||||
@ -88,9 +56,16 @@ export default async function publish(options: { dry: boolean }) {
|
||||
const url = new URL(getConfig("registry"));
|
||||
url.pathname = "/api/package/" + meta.name;
|
||||
|
||||
if (!options.dry) {
|
||||
log("Uploading new package version");
|
||||
console.log(
|
||||
"Pushing version",
|
||||
Colors.blue(meta.version),
|
||||
"to repository"
|
||||
);
|
||||
|
||||
if (!options.dry) {
|
||||
await checkPermOrExit("net", "Net permission required for publishing");
|
||||
|
||||
log("Uploading new package version");
|
||||
await fetch(url, {
|
||||
method: "POST",
|
||||
body: await Deno.readFile(packedFile),
|
||||
|
18
cli/commands/run.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Colors, Path, FS, Compress, Base64 } from "../deps.ts";
|
||||
import { getMeta, IMeta, log, getConfig } from "../global.ts";
|
||||
import { ServerError } from "../helper/server_error.ts";
|
||||
|
||||
import { runScript } from "../helper/run_script.ts";
|
||||
|
||||
export default async function run(options: {}, name: string) {
|
||||
const { scripts } = await getMeta();
|
||||
if (!scripts || !scripts[name]) {
|
||||
console.log(Colors.bold(Colors.red("Script not found:")), name);
|
||||
} else {
|
||||
let script = scripts[name];
|
||||
if (!Array.isArray(script)) script = [script];
|
||||
for (const s of script) {
|
||||
await runScript(s);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,63 @@
|
||||
import { Cliffy, Colors } from "../deps.ts";
|
||||
import { version } from "../version.ts";
|
||||
import { requestPermOrExit } from "../helper/permission.ts";
|
||||
|
||||
export default async function upgrade() {
|
||||
await requestPermOrExit(
|
||||
"net",
|
||||
"Net permission required to fetch new version"
|
||||
);
|
||||
const meta = await fetch(
|
||||
"https://deno.hibas123.de/raw/@denreg-cli/meta.json"
|
||||
).then((e) => e.json());
|
||||
|
||||
if (meta.version === version) {
|
||||
const res = await Cliffy.Confirm.prompt({
|
||||
message: Colors.yellow("No update available!") + " Continue?",
|
||||
default: false,
|
||||
});
|
||||
if (!res) return;
|
||||
}
|
||||
|
||||
const res = await Cliffy.Confirm.prompt({
|
||||
message: "Are you sure you want to upgrade the denreg cli?",
|
||||
message: "Are you sure you want to upgrade?",
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
const process = Deno.run({
|
||||
cmd: [
|
||||
"deno",
|
||||
"install",
|
||||
"-A",
|
||||
"--unstable",
|
||||
"-f",
|
||||
"https://deno.hibas123.de/raw/@denreg-cli/denreg.ts",
|
||||
],
|
||||
const cmd_base = ["deno", "install", "-A", "--unstable", "-f"];
|
||||
|
||||
const cmd1 = [
|
||||
...cmd_base,
|
||||
`https://deno.hibas123.de/raw/@denreg-cli@${meta.version}/denreg.ts`,
|
||||
];
|
||||
|
||||
const cmd2 = [
|
||||
...cmd_base,
|
||||
`https://deno.hibas123.de/raw/@denreg-cli@${meta.version}/dpm.ts`,
|
||||
];
|
||||
|
||||
await requestPermOrExit(
|
||||
"run",
|
||||
"Run permission required to install new version. Or run it manually: " +
|
||||
cmd1.join(" ")
|
||||
);
|
||||
|
||||
const process1 = Deno.run({
|
||||
cmd: cmd1,
|
||||
});
|
||||
|
||||
const s = await process.status();
|
||||
if (!s) {
|
||||
const s1 = await process1.status();
|
||||
if (!s1) {
|
||||
console.log(Colors.red("Upgrade failed!"));
|
||||
}
|
||||
|
||||
const process2 = Deno.run({
|
||||
cmd: cmd2,
|
||||
});
|
||||
|
||||
const s2 = await process2.status();
|
||||
if (!s2) {
|
||||
console.log(Colors.red("Upgrade failed!"));
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,20 @@
|
||||
import { Cliffy, Path, Colors } from "./deps.ts";
|
||||
|
||||
import { checkPermOrExit } from "./helper/permission.ts";
|
||||
|
||||
await checkPermOrExit("env", "Requires --allow-env");
|
||||
await checkPermOrExit("read", "Requires --allow-read");
|
||||
await checkPermOrExit("write", "Requires --allow-write");
|
||||
|
||||
import { init } from "./global.ts";
|
||||
|
||||
import { version } from "./version.ts";
|
||||
|
||||
import setupCMD from "./commands/setup.ts";
|
||||
import initCMD from "./commands/init.ts";
|
||||
import bumpCMD from "./commands/bump.ts";
|
||||
import publishCMD from "./commands/publish.ts";
|
||||
import deprecateCMD from "./commands/deprecate.ts";
|
||||
import upgradeCMD from "./commands/upgrade.ts";
|
||||
import runCMD from "./commands/run.ts";
|
||||
|
||||
const HOME_FOLDER = Deno.env.get("HOME") || Deno.env.get("USERPROFILE") || "";
|
||||
|
||||
@ -83,7 +89,16 @@ const flags = await new Cliffy.Command()
|
||||
.description("Upgrade to latest version of denreg cli")
|
||||
.action(commandWrapper(upgradeCMD))
|
||||
)
|
||||
.command(
|
||||
"run",
|
||||
new Cliffy.Command()
|
||||
.arguments("<name>")
|
||||
// .complete("name", ()=>) //TODO: add autocomplete?
|
||||
.description("Run script from meta.json")
|
||||
.action(commandWrapper(runCMD))
|
||||
)
|
||||
.command("completions", new Cliffy.CompletionsCommand())
|
||||
.command("help", new Cliffy.HelpCommand().global())
|
||||
.parse(Deno.args);
|
||||
|
||||
await init(flags.options);
|
||||
@ -94,4 +109,6 @@ if (command) {
|
||||
} catch (err) {
|
||||
console.log(Colors.bold(Colors.red("An error occured:")), err.message);
|
||||
}
|
||||
} else {
|
||||
flags.cmd.showHelp();
|
||||
}
|
||||
|
10
cli/deps.ts
@ -1,7 +1,7 @@
|
||||
export * as Compress from "https://deno.hibas123.de/raw/@denreg-tar/mod.ts";
|
||||
export * as Ini from "https://deno.land/x/ini@v2.1.0/mod.ts";
|
||||
export * as Cliffy from "https://deno.land/x/cliffy@v0.14.1/mod.ts";
|
||||
export * as Base64 from "https://deno.land/std@0.65.0/encoding/base64.ts";
|
||||
export * as FS from "https://deno.land/std@0.65.0/fs/mod.ts";
|
||||
export * as Colors from "https://deno.land/std@0.65.0/fmt/colors.ts";
|
||||
export * as Path from "https://deno.land/std@0.65.0/path/mod.ts";
|
||||
export * as Cliffy from "https://deno.land/x/cliffy@v0.18.2/mod.ts";
|
||||
export * as Base64 from "https://deno.land/std@0.95.0/encoding/base64.ts";
|
||||
export * as FS from "https://deno.land/std@0.95.0/fs/mod.ts";
|
||||
export * as Colors from "https://deno.land/std@0.95.0/fmt/colors.ts";
|
||||
export * as Path from "https://deno.land/std@0.95.0/path/mod.ts";
|
||||
|
1
cli/dpm.ts
Normal file
@ -0,0 +1 @@
|
||||
import "./denreg.ts";
|
@ -9,10 +9,14 @@ export interface IMeta {
|
||||
contributors?: string[];
|
||||
deprecated?: boolean;
|
||||
files: string[];
|
||||
root?: string;
|
||||
hooks?: {
|
||||
prepublish?: string | string[];
|
||||
postpublish?: string | string[];
|
||||
};
|
||||
scripts?: {
|
||||
[key: string]: string | string[];
|
||||
};
|
||||
}
|
||||
|
||||
let verbose = false;
|
||||
@ -41,16 +45,18 @@ export async function setConfig(name: string, value: string) {
|
||||
});
|
||||
}
|
||||
|
||||
const readJson = (name: string) => Deno.readTextFile(name).then(JSON.parse);
|
||||
const writeJson = (name: string, value: any, spaces?: string) =>
|
||||
Deno.writeTextFile(name, JSON.stringify(value, null, spaces));
|
||||
|
||||
export async function getMeta() {
|
||||
log("Reading meta.json");
|
||||
return (await FS.readJson("meta.json")) as IMeta;
|
||||
return (await readJson("meta.json")) as IMeta;
|
||||
}
|
||||
|
||||
export async function setMeta(meta: IMeta): Promise<void> {
|
||||
log("Saving meta.json");
|
||||
return FS.writeJson("meta.json", meta, {
|
||||
spaces: " ",
|
||||
});
|
||||
return writeJson("meta.json", meta, " ");
|
||||
}
|
||||
|
||||
let interactive = true;
|
||||
|
26
cli/helper/permission.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Colors } from "../deps.ts";
|
||||
|
||||
export const checkPermOrExit = (name: string, err: string) =>
|
||||
Deno.permissions.query({ name: name as any }).then((res) => {
|
||||
if (res.state !== "granted") {
|
||||
console.log(Colors.bold(Colors.red(err)));
|
||||
Deno.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
export const requestPermOrExit = (name: string, err: string) => {
|
||||
Deno.permissions
|
||||
.query({ name: name as any })
|
||||
.then((res) => {
|
||||
if (res.state === "prompt") {
|
||||
return Deno.permissions.request({ name: name as any });
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.state !== "granted") {
|
||||
console.log(Colors.bold(Colors.red(err)));
|
||||
Deno.exit(1);
|
||||
}
|
||||
});
|
||||
};
|
51
cli/helper/run_script.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Colors } from "../deps.ts";
|
||||
import { checkPermOrExit } from "../helper/permission.ts";
|
||||
|
||||
export async function runScript(script: string) {
|
||||
await checkPermOrExit(
|
||||
"run",
|
||||
"Requires --allow-run to run scripts and hooks"
|
||||
);
|
||||
console.log(Colors.bold(Colors.blue("Running script:")), script);
|
||||
const runPerm = await Deno.permissions.query({
|
||||
name: "run",
|
||||
});
|
||||
|
||||
if (runPerm.state !== "granted") {
|
||||
console.log(
|
||||
Colors.red("Missing --allow-run permission. Cannot run hooks!")
|
||||
);
|
||||
throw new Error("Missing --allow-run permission. Cannot run hooks!");
|
||||
}
|
||||
|
||||
const process = Deno.run({
|
||||
cmd: ["deno", "run", "-A", "--unstable", script],
|
||||
});
|
||||
|
||||
const status = await process.status();
|
||||
|
||||
console.log(Colors.bold(Colors.blue("Finished script:")), script);
|
||||
|
||||
if (!status.success) {
|
||||
throw new Error(
|
||||
"A script did not complete sucessfully. This is not a issue of denreg!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHooks(hooks: undefined | string | string[]) {
|
||||
if (!hooks) return;
|
||||
if (typeof hooks === "string") {
|
||||
hooks = [hooks];
|
||||
}
|
||||
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
await runScript(hook);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
"A hook did not complete sucessfully. This is not a issue of denreg!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,17 @@
|
||||
{
|
||||
"name": "@denreg-cli",
|
||||
"version": "0.2.5",
|
||||
"version": "0.3.3",
|
||||
"description": "CLI for the DenReg package registry",
|
||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||
"contributors": [],
|
||||
"files": ["**/*.ts", "**/*.js", "README.md"],
|
||||
"files": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "version.ts"
|
||||
},
|
||||
"hooks": {
|
||||
"prepublish": "pre.ts"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FS } from "./deps.ts";
|
||||
|
||||
const meta = (await FS.readJson("./meta.json")) as any;
|
||||
const meta = (await Deno.readTextFile("./meta.json").then(JSON.parse)) as any;
|
||||
|
||||
await Deno.writeTextFile(
|
||||
"version.ts",
|
||||
|
0
cli/test.ts
Normal file
@ -1 +1 @@
|
||||
export const version = "0.2.5"
|
||||
export const version = "0.3.3"
|
@ -1,5 +0,0 @@
|
||||
Denreg JSX renderer
|
||||
|
||||
**deprecated**
|
||||
|
||||
**DO NOT USE**
|
@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "@denreg-jsx",
|
||||
"version": "0.0.3",
|
||||
"version": "0.1.2",
|
||||
"description": "Denreg JSX renderer",
|
||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||
"contributors": [],
|
||||
"deprecated": true,
|
||||
"deprecated": false,
|
||||
"files": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"tsconfig.json",
|
||||
"README.md"
|
||||
]
|
||||
}
|
145
jsx/mod.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import "./types.ts";
|
||||
|
||||
const Fragment = Symbol("fragment");
|
||||
|
||||
declare namespace JSX {
|
||||
interface Element {}
|
||||
interface IntrinsicElements {
|
||||
div: any;
|
||||
}
|
||||
}
|
||||
|
||||
export type Element = {
|
||||
component: Component | string | typeof Fragment;
|
||||
props: any;
|
||||
children: any[];
|
||||
};
|
||||
export type ComponentRetElm = Element | Element[];
|
||||
export type Component = (
|
||||
props: any,
|
||||
children: any
|
||||
) => ComponentRetElm | Promise<ComponentRetElm>;
|
||||
|
||||
export function h(
|
||||
component: string | Component,
|
||||
props: any,
|
||||
...children: Element[]
|
||||
): Element {
|
||||
return {
|
||||
component,
|
||||
props,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
const createElement = h;
|
||||
|
||||
export { Fragment, createElement };
|
||||
|
||||
export async function renderSSR(element: Element | string): Promise<string> {
|
||||
if (typeof element === "string") return element;
|
||||
else if (typeof element.component === "string")
|
||||
return await renderHTML(element as Element);
|
||||
else if (
|
||||
typeof element.component === "function" ||
|
||||
element.component === Fragment
|
||||
)
|
||||
return await renderCustom(element as Element);
|
||||
|
||||
console.warn("renderSSR: invalid element", element);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
const selfClosing = new Set([
|
||||
"area",
|
||||
"base",
|
||||
"br",
|
||||
"col",
|
||||
"embed",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"track",
|
||||
"wbr",
|
||||
]);
|
||||
|
||||
function flatDeep(arr: any, d = Infinity): any[] {
|
||||
if (Array.isArray(arr) && d >= 0) {
|
||||
let res = [];
|
||||
for (const val of arr) {
|
||||
const v = flatDeep(val, d - 1);
|
||||
res.push(...v);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
return [arr];
|
||||
}
|
||||
|
||||
function cleanChildren(children: any) {
|
||||
return flatDeep(children).filter((e) => !!e);
|
||||
}
|
||||
|
||||
async function renderHTML(element: Element) {
|
||||
if (typeof element.component !== "string")
|
||||
throw new Error("Internal consistency error");
|
||||
|
||||
let props = "";
|
||||
|
||||
for (const key in element.props) {
|
||||
if (key == "innerHTML") continue;
|
||||
props += `${key}="${element.props[key] || ""}" `;
|
||||
}
|
||||
|
||||
const tag = element.component;
|
||||
|
||||
if (selfClosing.has(element.component)) {
|
||||
return `<${tag} ${props}/>`;
|
||||
} else {
|
||||
let inner = "";
|
||||
if (element.props && element.props["innerHTML"]) {
|
||||
inner = element.props["innerHTML"];
|
||||
} else {
|
||||
const children = cleanChildren(element.children);
|
||||
inner = (
|
||||
await Promise.all(children.map((child) => renderSSR(child)))
|
||||
).join("");
|
||||
}
|
||||
return `<${tag} ${props}>${inner || ""}</${tag}>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function renderCustom(element: Element) {
|
||||
if (typeof element.component === "string")
|
||||
throw new Error("Internal consistency error");
|
||||
|
||||
if (element.component === Fragment) {
|
||||
const ch = (
|
||||
await Promise.all(
|
||||
cleanChildren(element.children).map((child) => renderSSR(child))
|
||||
)
|
||||
).join("");
|
||||
|
||||
return ch;
|
||||
} else {
|
||||
const res = await Promise.resolve(
|
||||
element.component(
|
||||
{
|
||||
...element.props,
|
||||
children: element.children,
|
||||
},
|
||||
element.children
|
||||
)
|
||||
);
|
||||
|
||||
const ch = (
|
||||
await Promise.all(cleanChildren(res).map((child) => renderSSR(child)))
|
||||
).join("");
|
||||
|
||||
return ch;
|
||||
}
|
||||
}
|
7
jsx/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext", "deno.ns", "deno.unstable"],
|
||||
"jsxFactory": "h",
|
||||
"strictPropertyInitialization": false
|
||||
}
|
||||
}
|
11
jsx/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
declare namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
[elemName: string]: any;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace JSX {
|
||||
interface ElementClass {
|
||||
render: any;
|
||||
}
|
||||
}
|
14
markdown/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Deno",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "deno",
|
||||
"runtimeArgs": ["run", "--inspect", "-A", "app.ts"],
|
||||
"port": 9229
|
||||
}
|
||||
]
|
||||
}
|
9
markdown/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "axetroy.vscode-deno"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "axetroy.vscode-deno"
|
||||
}
|
||||
}
|
21
markdown/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Eivind Furuberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
62
markdown/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# markdown
|
||||
|
||||
Deno Markdown module forked from https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831
|
||||
|
||||
### Example usage
|
||||
|
||||
Simple md2html.ts script:
|
||||
|
||||
```typescript
|
||||
import { Marked } from "./mod.ts";
|
||||
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const filename = Deno.args[0];
|
||||
const markdown = decoder.decode(await Deno.readFile(filename));
|
||||
const markup = Marked.parse(markdown);
|
||||
console.log(markup.content);
|
||||
console.log(JSON.stringify(markup.meta))
|
||||
```
|
||||
|
||||
Now running:
|
||||
|
||||
```bash
|
||||
deno run --allow-read md2html.ts example.md > example.html
|
||||
```
|
||||
|
||||
Will output:
|
||||
|
||||
```html
|
||||
<h1 id="hello-world">Hello World</h1>
|
||||
<h2 id="this-an-example-for-md2html-ts-">
|
||||
This an example for <code>md2html.ts</code>
|
||||
</h2>
|
||||
<p>A small paragraph that will become a <code><p></code> tag</p>
|
||||
<hr />
|
||||
<p>Code Block (md2html.ts)</p>
|
||||
|
||||
<pre><code class="lang-typescript">import { Marked } from "./mod.ts";
|
||||
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const filename = Deno.args[0];
|
||||
const markdown = decoder.decode(await Deno.readFile(filename));
|
||||
const markup = Marked.parse(markdown);
|
||||
console.log(markup.content);
|
||||
console.log(JSON.stringify(markup.meta))
|
||||
</code></pre>
|
||||
<p>
|
||||
This module is forked from
|
||||
<a
|
||||
href="https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831"
|
||||
>ts-stack/markdown</a
|
||||
>
|
||||
</p>
|
||||
<p>Made for Deno <img src="https://deno.land/logo.svg" alt="deno-logo" /></p>
|
||||
|
||||
{"title":"Hello world!","subtitle":"Front-matter is supported!","boolean":true,"list-example":["this","is",{"a":"list"}]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Notes
|
||||
|
||||
I had to do some changes to the source code to make the compiler happy, mostly fixes for things that were uninitialized and possibly null or undefined
|
BIN
markdown/example.html
Normal file
34
markdown/example.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title : Hello world!
|
||||
subtitle : Front-matter is supported!
|
||||
boolean: true
|
||||
list-example:
|
||||
- this
|
||||
- is
|
||||
- a: list
|
||||
---
|
||||
# Hello World
|
||||
|
||||
## This an example for `md2html.ts`
|
||||
|
||||
A small paragraph that will become a `<p>` tag
|
||||
|
||||
---
|
||||
|
||||
Code Block (md2html.ts)
|
||||
|
||||
```typescript
|
||||
import { Marked } from "./mod.ts";
|
||||
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const filename = Deno.args[0];
|
||||
const markdown = decoder.decode(await Deno.readFile(filename));
|
||||
const markup = Marked.parse(markdown);
|
||||
console.log(markup.content);
|
||||
console.log(JSON.stringify(markup.meta));
|
||||
```
|
||||
|
||||
This module is forked from [ts-stack/markdown](https://github.com/ts-stack/markdown/tree/bb47aa8e625e89e6aa84f49a98536a3089dee831)
|
||||
|
||||
Made for Deno
|
||||

|
8
markdown/md2html.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Marked } from "./mod.ts";
|
||||
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const filename = Deno.args[0];
|
||||
const markdown = decoder.decode(await Deno.readFile(filename));
|
||||
const markup = Marked.parse(markdown);
|
||||
console.log(markup.content);
|
||||
console.log(JSON.stringify(markup.meta))
|
12
markdown/meta.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "markdown",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||
"contributors": [],
|
||||
"files": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"README.md"
|
||||
]
|
||||
}
|
8
markdown/mod.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from "./src/block-lexer.ts";
|
||||
export * from "./src/helpers.ts";
|
||||
export * from "./src/inline-lexer.ts";
|
||||
export * from "./src/interfaces.ts";
|
||||
export * from "./src/marked.ts";
|
||||
export * from "./src/parser.ts";
|
||||
export * from "./src/renderer.ts";
|
||||
export * from "./src/extend-regexp.ts";
|
520
markdown/src/block-lexer.ts
Normal file
@ -0,0 +1,520 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import { ExtendRegexp } from "./extend-regexp.ts";
|
||||
import {
|
||||
Align,
|
||||
LexerReturns,
|
||||
Links,
|
||||
MarkedOptions,
|
||||
RulesBlockBase,
|
||||
RulesBlockGfm,
|
||||
RulesBlockTables,
|
||||
Token,
|
||||
TokenType,
|
||||
Obj
|
||||
} from "./interfaces.ts";
|
||||
import { Marked } from "./marked.ts";
|
||||
import { load } from "https://deno.land/std/encoding/_yaml/loader/loader.ts";
|
||||
|
||||
export class BlockLexer<T extends typeof BlockLexer> {
|
||||
static simpleRules: RegExp[] = [];
|
||||
protected static rulesBase: RulesBlockBase;
|
||||
/**
|
||||
* GFM Block Grammar.
|
||||
*/
|
||||
protected static rulesGfm: RulesBlockGfm;
|
||||
/**
|
||||
* GFM + Tables Block Grammar.
|
||||
*/
|
||||
protected static rulesTables: RulesBlockTables;
|
||||
protected rules!: RulesBlockBase | RulesBlockGfm | RulesBlockTables;
|
||||
protected options: MarkedOptions;
|
||||
protected links: Links = {};
|
||||
protected tokens: Token[] = [];
|
||||
protected frontmatter: Obj = {};
|
||||
protected hasRulesGfm!: boolean;
|
||||
protected hasRulesTables!: boolean;
|
||||
|
||||
constructor(protected staticThis: typeof BlockLexer, options?: object) {
|
||||
this.options = options || Marked.options;
|
||||
this.setRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts Markdown text and returns object with tokens and links.
|
||||
*
|
||||
* @param src String of markdown source to be compiled.
|
||||
* @param options Hash of options.
|
||||
*/
|
||||
static lex(
|
||||
src: string,
|
||||
options?: MarkedOptions,
|
||||
top?: boolean,
|
||||
isBlockQuote?: boolean,
|
||||
): LexerReturns {
|
||||
const lexer = new this(this, options);
|
||||
return lexer.getTokens(src, top, isBlockQuote);
|
||||
}
|
||||
|
||||
protected static getRulesBase(): RulesBlockBase {
|
||||
if (this.rulesBase) {
|
||||
return this.rulesBase;
|
||||
}
|
||||
|
||||
const base: RulesBlockBase = {
|
||||
newline: /^\n+/,
|
||||
code: /^( {4}[^\n]+\n*)+/,
|
||||
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
|
||||
heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
|
||||
lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
|
||||
blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
|
||||
list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
|
||||
html:
|
||||
/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
|
||||
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
|
||||
paragraph:
|
||||
/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
|
||||
text: /^[^\n]+/,
|
||||
bullet: /(?:[*+-]|\d+\.)/,
|
||||
item: /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,
|
||||
};
|
||||
|
||||
base.item = new ExtendRegexp(base.item, "gm").setGroup(/bull/g, base.bullet)
|
||||
.getRegexp();
|
||||
|
||||
base.list = new ExtendRegexp(base.list)
|
||||
.setGroup(/bull/g, base.bullet)
|
||||
.setGroup("hr", "\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")
|
||||
.setGroup("def", "\\n+(?=" + base.def.source + ")")
|
||||
.getRegexp();
|
||||
|
||||
const tag = "(?!(?:" +
|
||||
"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code" +
|
||||
"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo" +
|
||||
"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";
|
||||
|
||||
base.html = new ExtendRegexp(base.html)
|
||||
.setGroup("comment", /<!--[\s\S]*?-->/)
|
||||
.setGroup("closed", /<(tag)[\s\S]+?<\/\1>/)
|
||||
.setGroup("closing", /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
|
||||
.setGroup(/tag/g, tag)
|
||||
.getRegexp();
|
||||
|
||||
base.paragraph = new ExtendRegexp(base.paragraph)
|
||||
.setGroup("hr", base.hr)
|
||||
.setGroup("heading", base.heading)
|
||||
.setGroup("lheading", base.lheading)
|
||||
.setGroup("blockquote", base.blockquote)
|
||||
.setGroup("tag", "<" + tag)
|
||||
.setGroup("def", base.def)
|
||||
.getRegexp();
|
||||
|
||||
return (this.rulesBase = base);
|
||||
}
|
||||
|
||||
protected static getRulesGfm(): RulesBlockGfm {
|
||||
if (this.rulesGfm) {
|
||||
return this.rulesGfm;
|
||||
}
|
||||
|
||||
const base = this.getRulesBase();
|
||||
|
||||
const gfm: RulesBlockGfm = {
|
||||
...base,
|
||||
...{
|
||||
fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
|
||||
paragraph: /^/,
|
||||
heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/,
|
||||
},
|
||||
};
|
||||
|
||||
const group1 = gfm.fences.source.replace("\\1", "\\2");
|
||||
const group2 = base.list.source.replace("\\1", "\\3");
|
||||
|
||||
gfm.paragraph = new ExtendRegexp(base.paragraph).setGroup(
|
||||
"(?!",
|
||||
`(?!${group1}|${group2}|`,
|
||||
).getRegexp();
|
||||
|
||||
return (this.rulesGfm = gfm);
|
||||
}
|
||||
|
||||
protected static getRulesTable(): RulesBlockTables {
|
||||
if (this.rulesTables) {
|
||||
return this.rulesTables;
|
||||
}
|
||||
|
||||
return (this.rulesTables = {
|
||||
...this.getRulesGfm(),
|
||||
...{
|
||||
nptable:
|
||||
/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
|
||||
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected setRules() {
|
||||
if (this.options.gfm) {
|
||||
if (this.options.tables) {
|
||||
this.rules = this.staticThis.getRulesTable();
|
||||
} else {
|
||||
this.rules = this.staticThis.getRulesGfm();
|
||||
}
|
||||
} else {
|
||||
this.rules = this.staticThis.getRulesBase();
|
||||
}
|
||||
|
||||
this.hasRulesGfm = (this.rules as RulesBlockGfm).fences !== undefined;
|
||||
this.hasRulesTables = (this.rules as RulesBlockTables).table !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexing.
|
||||
*/
|
||||
protected getTokens(
|
||||
src: string,
|
||||
top?: boolean,
|
||||
isBlockQuote?: boolean,
|
||||
): LexerReturns {
|
||||
let nextPart = src;
|
||||
let execArr, fmArr: RegExpExecArray | null;
|
||||
|
||||
mainLoop:
|
||||
while (nextPart) {
|
||||
// newline
|
||||
if ((execArr = this.rules.newline.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
if (execArr[0].length > 1) {
|
||||
this.tokens.push({ type: TokenType.space });
|
||||
}
|
||||
}
|
||||
|
||||
// code
|
||||
if ((execArr = this.rules.code.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
const code = execArr[0].replace(/^ {4}/gm, "");
|
||||
|
||||
this.tokens.push({
|
||||
type: TokenType.code,
|
||||
text: !this.options.pedantic ? code.replace(/\n+$/, "") : code,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// fences code (gfm)
|
||||
if (
|
||||
this.hasRulesGfm &&
|
||||
(execArr = (this.rules as RulesBlockGfm).fences.exec(nextPart))
|
||||
) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
this.tokens.push({
|
||||
type: TokenType.code,
|
||||
lang: execArr[2],
|
||||
text: execArr[3] || "",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// heading
|
||||
if ((execArr = this.rules.heading.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
this.tokens.push({
|
||||
type: TokenType.heading,
|
||||
depth: execArr[1].length,
|
||||
text: execArr[2],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// table no leading pipe (gfm)
|
||||
if (
|
||||
top && this.hasRulesTables &&
|
||||
(execArr = (this.rules as RulesBlockTables).nptable.exec(nextPart))
|
||||
) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
const item: Token = {
|
||||
type: TokenType.table,
|
||||
header: execArr[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
|
||||
align: execArr[2].replace(/^ *|\| *$/g, "").split(
|
||||
/ *\| */,
|
||||
) as Align[],
|
||||
cells: [],
|
||||
};
|
||||
|
||||
if (!item.align) throw ReferenceError;
|
||||
|
||||
for (let i = 0; i < item.align.length; i++) {
|
||||
if (/^ *-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = "right";
|
||||
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = "center";
|
||||
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
||||
item.align[i] = "left";
|
||||
} else {
|
||||
item.align[i] = "";
|
||||
}
|
||||
}
|
||||
|
||||
const td: string[] = execArr[3].replace(/\n$/, "").split("\n");
|
||||
|
||||
if (!item.cells) throw ReferenceError;
|
||||
|
||||
for (let i = 0; i < td.length; i++) {
|
||||
item.cells[i] = td[i].split(/ *\| */);
|
||||
}
|
||||
|
||||
this.tokens.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// lheading
|
||||
if ((execArr = this.rules.lheading.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
this.tokens.push({
|
||||
type: TokenType.heading,
|
||||
depth: execArr[2] === "=" ? 1 : 2,
|
||||
text: execArr[1],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// hr
|
||||
if ((execArr = this.rules.hr.exec(nextPart))) {
|
||||
|
||||
// Checks if the previous string contains a content.
|
||||
if ((this.tokens.length == 0) || (this.tokens.every(object => object.type == TokenType.space))) {
|
||||
|
||||
// Grabs front-matter data and parse it into Javascript object.
|
||||
if (fmArr = /^(?:\-\-\-)(.*?)(?:\-\-\-|\.\.\.)/s.exec(nextPart)) {
|
||||
nextPart = nextPart.substring(fmArr[0].length);
|
||||
this.frontmatter = <Obj> load(fmArr[1]);
|
||||
}
|
||||
continue;
|
||||
|
||||
} else {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
this.tokens.push({ type: TokenType.hr });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// blockquote
|
||||
if ((execArr = this.rules.blockquote.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
this.tokens.push({ type: TokenType.blockquoteStart });
|
||||
const str = execArr[0].replace(/^ *> ?/gm, "");
|
||||
|
||||
// Pass `top` to keep the current
|
||||
// "toplevel" state. This is exactly
|
||||
// how markdown.pl works.
|
||||
this.getTokens(str);
|
||||
this.tokens.push({ type: TokenType.blockquoteEnd });
|
||||
continue;
|
||||
}
|
||||
|
||||
// list
|
||||
if ((execArr = this.rules.list.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
const bull: string = execArr[2];
|
||||
|
||||
this.tokens.push(
|
||||
{ type: TokenType.listStart, ordered: bull.length > 1 },
|
||||
);
|
||||
|
||||
// Get each top-level item.
|
||||
const str = execArr[0].match(this.rules.item) || "";
|
||||
const length = str.length;
|
||||
|
||||
let next = false;
|
||||
let space: number;
|
||||
let blockBullet: string;
|
||||
let loose: boolean;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
let item = str[i];
|
||||
|
||||
// Remove the list item's bullet so it is seen as the next token.
|
||||
space = item.length;
|
||||
item = item.replace(/^ *([*+-]|\d+\.) +/, "");
|
||||
|
||||
// Outdent whatever the list item contains. Hacky.
|
||||
if (item.indexOf("\n ") !== -1) {
|
||||
space -= item.length;
|
||||
item = !this.options.pedantic
|
||||
? item.replace(new RegExp("^ {1," + space + "}", "gm"), "")
|
||||
: item.replace(/^ {1,4}/gm, "");
|
||||
}
|
||||
|
||||
// Determine whether the next list item belongs here.
|
||||
// Backpedal if it does not belong in this list.
|
||||
if (this.options.smartLists && i !== length - 1) {
|
||||
const bb = this.staticThis.getRulesBase().bullet.exec(str[i + 1]);
|
||||
blockBullet = bb ? bb[0] : "";
|
||||
|
||||
if (
|
||||
bull !== blockBullet &&
|
||||
!(bull.length > 1 && blockBullet.length > 1)
|
||||
) {
|
||||
nextPart = (str.slice(i + 1) as string[]).join("\n") + nextPart;
|
||||
i = length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether item is loose or not.
|
||||
// Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
|
||||
// for discount behavior.
|
||||
loose = next || /\n\n(?!\s*$)/.test(item);
|
||||
|
||||
if (i !== length - 1) {
|
||||
next = item.charAt(item.length - 1) === "\n";
|
||||
|
||||
if (!loose) {
|
||||
loose = next;
|
||||
}
|
||||
}
|
||||
|
||||
this.tokens.push(
|
||||
{
|
||||
type: loose ? TokenType.looseItemStart : TokenType.listItemStart,
|
||||
},
|
||||
);
|
||||
|
||||
// Recurse.
|
||||
this.getTokens(item, false, isBlockQuote);
|
||||
this.tokens.push({ type: TokenType.listItemEnd });
|
||||
}
|
||||
|
||||
this.tokens.push({ type: TokenType.listEnd });
|
||||
continue;
|
||||
}
|
||||
|
||||
// html
|
||||
if ((execArr = this.rules.html.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
const attr = execArr[1];
|
||||
const isPre = attr === "pre" || attr === "script" || attr === "style";
|
||||
|
||||
this.tokens.push({
|
||||
type: this.options.sanitize ? TokenType.paragraph : TokenType.html,
|
||||
pre: !this.options.sanitizer && isPre,
|
||||
text: execArr[0],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// def
|
||||
if (top && (execArr = this.rules.def.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
this.links[execArr[1].toLowerCase()] = {
|
||||
href: execArr[2],
|
||||
title: execArr[3],
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// table (gfm)
|
||||
if (
|
||||
top && this.hasRulesTables &&
|
||||
(execArr = (this.rules as RulesBlockTables).table.exec(nextPart))
|
||||
) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
const item: Token = {
|
||||
type: TokenType.table,
|
||||
header: execArr[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
|
||||
align: execArr[2].replace(/^ *|\| *$/g, "").split(
|
||||
/ *\| */,
|
||||
) as Align[],
|
||||
cells: [],
|
||||
};
|
||||
|
||||
if (!item.align) throw ReferenceError;
|
||||
|
||||
for (let i = 0; i < item.align.length; i++) {
|
||||
if (/^ *-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = "right";
|
||||
} else if (/^ *:-+: *$/.test(item.align[i])) {
|
||||
item.align[i] = "center";
|
||||
} else if (/^ *:-+ *$/.test(item.align[i])) {
|
||||
item.align[i] = "left";
|
||||
} else {
|
||||
item.align[i] = "";
|
||||
}
|
||||
}
|
||||
|
||||
const td = execArr[3].replace(/(?: *\| *)?\n$/, "").split("\n");
|
||||
|
||||
if (!item.cells) throw ReferenceError;
|
||||
|
||||
for (let i = 0; i < td.length; i++) {
|
||||
item.cells[i] = td[i].replace(/^ *\| *| *\| *$/g, "").split(/ *\| */);
|
||||
}
|
||||
|
||||
this.tokens.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// simple rules
|
||||
if (this.staticThis.simpleRules.length) {
|
||||
const simpleRules = this.staticThis.simpleRules;
|
||||
for (let i = 0; i < simpleRules.length; i++) {
|
||||
if ((execArr = simpleRules[i].exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
const type = "simpleRule" + (i + 1);
|
||||
this.tokens.push({ type, execArr });
|
||||
continue mainLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// top-level paragraph
|
||||
if (top && (execArr = this.rules.paragraph.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
if (execArr[1].slice(-1) === "\n") {
|
||||
this.tokens.push({
|
||||
type: TokenType.paragraph,
|
||||
text: execArr[1].slice(0, -1),
|
||||
});
|
||||
} else {
|
||||
this.tokens.push({
|
||||
type: this.tokens.length > 0 ? TokenType.paragraph : TokenType.text,
|
||||
text: execArr[1],
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// text
|
||||
// Top-level should never reach here.
|
||||
if ((execArr = this.rules.text.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
this.tokens.push({ type: TokenType.text, text: execArr[0] });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nextPart) {
|
||||
throw new Error(
|
||||
"Infinite loop on byte: " + nextPart.charCodeAt(0) +
|
||||
`, near text '${nextPart.slice(0, 30)}...'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { tokens: this.tokens, links: this.links, meta: this.frontmatter };
|
||||
}
|
||||
}
|
43
markdown/src/extend-regexp.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
export class ExtendRegexp {
|
||||
private source: string;
|
||||
private flags: string;
|
||||
|
||||
constructor(regex: RegExp, flags: string = "") {
|
||||
this.source = regex.source;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend regular expression.
|
||||
*
|
||||
* @param groupName Regular expression for search a group name.
|
||||
* @param groupRegexp Regular expression of named group.
|
||||
*/
|
||||
setGroup(groupName: RegExp | string, groupRegexp: RegExp | string): this {
|
||||
let newRegexp: string = typeof groupRegexp == "string"
|
||||
? groupRegexp
|
||||
: groupRegexp.source;
|
||||
newRegexp = newRegexp.replace(/(^|[^\[])\^/g, "$1");
|
||||
|
||||
// Extend regexp.
|
||||
this.source = this.source.replace(groupName, newRegexp);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a result of extending a regular expression.
|
||||
*/
|
||||
getRegexp(): RegExp {
|
||||
return new RegExp(this.source, this.flags);
|
||||
}
|
||||
}
|
64
markdown/src/helpers.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import type { Replacements } from "./interfaces.ts";
|
||||
|
||||
const escapeTest = /[&<>"']/;
|
||||
const escapeReplace = /[&<>"']/g;
|
||||
const replacements: Replacements = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
// tslint:disable-next-line:quotemark
|
||||
"'": "'",
|
||||
};
|
||||
|
||||
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
|
||||
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
|
||||
|
||||
export function escape(html: string, encode?: boolean) {
|
||||
if (encode) {
|
||||
if (escapeTest.test(html)) {
|
||||
return html.replace(escapeReplace, (ch: string) => replacements[ch]);
|
||||
}
|
||||
} else {
|
||||
if (escapeTestNoEncode.test(html)) {
|
||||
return html.replace(
|
||||
escapeReplaceNoEncode,
|
||||
(ch: string) => replacements[ch]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
export function unescape(html: string) {
|
||||
// Explicitly match decimal, hex, and named HTML entities
|
||||
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi, function (
|
||||
_,
|
||||
n
|
||||
) {
|
||||
n = n.toLowerCase();
|
||||
|
||||
if (n === "colon") {
|
||||
return ":";
|
||||
}
|
||||
|
||||
if (n.charAt(0) === "#") {
|
||||
return n.charAt(1) === "x"
|
||||
? String.fromCharCode(parseInt(n.substring(2), 16))
|
||||
: String.fromCharCode(+n.substring(1));
|
||||
}
|
||||
|
||||
return "";
|
||||
});
|
||||
}
|
419
markdown/src/inline-lexer.ts
Normal file
@ -0,0 +1,419 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import { ExtendRegexp } from "./extend-regexp.ts";
|
||||
import type {
|
||||
Link,
|
||||
Links,
|
||||
MarkedOptions,
|
||||
RulesInlineBase,
|
||||
RulesInlineBreaks,
|
||||
RulesInlineCallback,
|
||||
RulesInlineGfm,
|
||||
RulesInlinePedantic,
|
||||
} from "./interfaces.ts";
|
||||
import { Marked } from "./marked.ts";
|
||||
import { Renderer } from "./renderer.ts";
|
||||
|
||||
/**
|
||||
* Inline Lexer & Compiler.
|
||||
*/
|
||||
export class InlineLexer {
|
||||
protected static rulesBase: RulesInlineBase;
|
||||
/**
|
||||
* Pedantic Inline Grammar.
|
||||
*/
|
||||
protected static rulesPedantic: RulesInlinePedantic;
|
||||
/**
|
||||
* GFM Inline Grammar
|
||||
*/
|
||||
protected static rulesGfm: RulesInlineGfm;
|
||||
/**
|
||||
* GFM + Line Breaks Inline Grammar.
|
||||
*/
|
||||
protected static rulesBreaks: RulesInlineBreaks;
|
||||
protected rules!:
|
||||
| RulesInlineBase
|
||||
| RulesInlinePedantic
|
||||
| RulesInlineGfm
|
||||
| RulesInlineBreaks;
|
||||
protected renderer: Renderer;
|
||||
protected inLink!: boolean;
|
||||
protected hasRulesGfm!: boolean;
|
||||
protected ruleCallbacks!: RulesInlineCallback[];
|
||||
|
||||
constructor(
|
||||
protected staticThis: typeof InlineLexer,
|
||||
protected links: Links,
|
||||
protected options: MarkedOptions = Marked.options,
|
||||
renderer?: Renderer
|
||||
) {
|
||||
this.renderer =
|
||||
renderer || this.options.renderer || new Renderer(this.options);
|
||||
|
||||
if (!this.links) {
|
||||
throw new Error(`InlineLexer requires 'links' parameter.`);
|
||||
}
|
||||
|
||||
this.setRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static Lexing/Compiling Method.
|
||||
*/
|
||||
static output(src: string, links: Links, options: MarkedOptions): string {
|
||||
const inlineLexer = new this(this, links, options);
|
||||
return inlineLexer.output(src);
|
||||
}
|
||||
|
||||
protected static getRulesBase(): RulesInlineBase {
|
||||
if (this.rulesBase) {
|
||||
return this.rulesBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline-Level Grammar.
|
||||
*/
|
||||
const base: RulesInlineBase = {
|
||||
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
|
||||
autolink: /^<([^ <>]+(@|:\/)[^ <>]+)>/,
|
||||
tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^<'">])*?>/,
|
||||
link: /^!?\[(inside)\]\(href\)/,
|
||||
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
|
||||
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
|
||||
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
|
||||
em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
|
||||
code: /^(`+)([\s\S]*?[^`])\1(?!`)/,
|
||||
br: /^ {2,}\n(?!\s*$)/,
|
||||
text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/,
|
||||
_inside: /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/,
|
||||
_href: /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/,
|
||||
};
|
||||
|
||||
base.link = new ExtendRegexp(base.link)
|
||||
.setGroup("inside", base._inside)
|
||||
.setGroup("href", base._href)
|
||||
.getRegexp();
|
||||
|
||||
base.reflink = new ExtendRegexp(base.reflink)
|
||||
.setGroup("inside", base._inside)
|
||||
.getRegexp();
|
||||
|
||||
return (this.rulesBase = base);
|
||||
}
|
||||
|
||||
protected static getRulesPedantic(): RulesInlinePedantic {
|
||||
if (this.rulesPedantic) {
|
||||
return this.rulesPedantic;
|
||||
}
|
||||
|
||||
return (this.rulesPedantic = {
|
||||
...this.getRulesBase(),
|
||||
...{
|
||||
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
|
||||
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected static getRulesGfm(): RulesInlineGfm {
|
||||
if (this.rulesGfm) {
|
||||
return this.rulesGfm;
|
||||
}
|
||||
|
||||
const base = this.getRulesBase();
|
||||
|
||||
const escape = new ExtendRegexp(base.escape)
|
||||
.setGroup("])", "~|])")
|
||||
.getRegexp();
|
||||
|
||||
const text = new ExtendRegexp(base.text)
|
||||
.setGroup("]|", "~]|")
|
||||
.setGroup("|", "|https?://|")
|
||||
.getRegexp();
|
||||
|
||||
return (this.rulesGfm = {
|
||||
...base,
|
||||
...{
|
||||
escape,
|
||||
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
|
||||
del: /^~~(?=\S)([\s\S]*?\S)~~/,
|
||||
text,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected static getRulesBreaks(): RulesInlineBreaks {
|
||||
if (this.rulesBreaks) {
|
||||
return this.rulesBreaks;
|
||||
}
|
||||
|
||||
const inline = this.getRulesGfm();
|
||||
const gfm = this.getRulesGfm();
|
||||
|
||||
return (this.rulesBreaks = {
|
||||
...gfm,
|
||||
...{
|
||||
br: new ExtendRegexp(inline.br).setGroup("{2,}", "*").getRegexp(),
|
||||
text: new ExtendRegexp(gfm.text).setGroup("{2,}", "*").getRegexp(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected setRules() {
|
||||
if (this.options.gfm) {
|
||||
if (this.options.breaks) {
|
||||
this.rules = this.staticThis.getRulesBreaks();
|
||||
} else {
|
||||
this.rules = this.staticThis.getRulesGfm();
|
||||
}
|
||||
} else if (this.options.pedantic) {
|
||||
this.rules = this.staticThis.getRulesPedantic();
|
||||
} else {
|
||||
this.rules = this.staticThis.getRulesBase();
|
||||
}
|
||||
|
||||
this.hasRulesGfm = (this.rules as RulesInlineGfm).url !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexing/Compiling.
|
||||
*/
|
||||
output(nextPart: string): string {
|
||||
nextPart = nextPart;
|
||||
let execArr: RegExpExecArray | null;
|
||||
let out = "";
|
||||
|
||||
while (nextPart) {
|
||||
// escape
|
||||
if ((execArr = this.rules.escape.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += execArr[1];
|
||||
continue;
|
||||
}
|
||||
|
||||
// autolink
|
||||
if ((execArr = this.rules.autolink.exec(nextPart))) {
|
||||
let text: string;
|
||||
let href: string;
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
|
||||
if (execArr[2] === "@") {
|
||||
text = this.options.escape(
|
||||
execArr[1].charAt(6) === ":"
|
||||
? this.mangle(execArr[1].substring(7))
|
||||
: this.mangle(execArr[1])
|
||||
);
|
||||
href = this.mangle("mailto:") + text;
|
||||
} else {
|
||||
text = this.options.escape(execArr[1]);
|
||||
href = text;
|
||||
}
|
||||
|
||||
out += this.renderer.link(href, "", text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// url (gfm)
|
||||
if (
|
||||
!this.inLink &&
|
||||
this.hasRulesGfm &&
|
||||
(execArr = (this.rules as RulesInlineGfm).url.exec(nextPart))
|
||||
) {
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
let text: string;
|
||||
let href: string;
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
text = this.options.escape(execArr[1]);
|
||||
href = text;
|
||||
out += this.renderer.link(href, "", text);
|
||||
continue;
|
||||
}
|
||||
|
||||
// tag
|
||||
if ((execArr = this.rules.tag.exec(nextPart))) {
|
||||
if (!this.inLink && /^<a /i.test(execArr[0])) {
|
||||
this.inLink = true;
|
||||
} else if (this.inLink && /^<\/a>/i.test(execArr[0])) {
|
||||
this.inLink = false;
|
||||
}
|
||||
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
|
||||
out += this.options.sanitize
|
||||
? this.options.sanitizer
|
||||
? this.options.sanitizer(execArr[0])
|
||||
: this.options.escape(execArr[0])
|
||||
: execArr[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
// link
|
||||
if ((execArr = this.rules.link.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
this.inLink = true;
|
||||
|
||||
out += this.outputLink(execArr, {
|
||||
href: execArr[2],
|
||||
title: execArr[3],
|
||||
});
|
||||
|
||||
this.inLink = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// reflink, nolink
|
||||
if (
|
||||
(execArr = this.rules.reflink.exec(nextPart)) ||
|
||||
(execArr = this.rules.nolink.exec(nextPart))
|
||||
) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
const keyLink = (execArr[2] || execArr[1]).replace(/\s+/g, " ");
|
||||
const link = this.links[keyLink.toLowerCase()];
|
||||
|
||||
if (!link || !link.href) {
|
||||
out += execArr[0].charAt(0);
|
||||
nextPart = execArr[0].substring(1) + nextPart;
|
||||
continue;
|
||||
}
|
||||
|
||||
this.inLink = true;
|
||||
out += this.outputLink(execArr, link);
|
||||
this.inLink = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// strong
|
||||
if ((execArr = this.rules.strong.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += this.renderer.strong(this.output(execArr[2] || execArr[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// em
|
||||
if ((execArr = this.rules.em.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += this.renderer.em(this.output(execArr[2] || execArr[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// code
|
||||
if ((execArr = this.rules.code.exec(nextPart))) {
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += this.renderer.codespan(
|
||||
this.options.escape(execArr[2].trim(), true)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// br
|
||||
if ((execArr = this.rules.br.exec(nextPart))) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += this.renderer.br();
|
||||
continue;
|
||||
}
|
||||
|
||||
// del (gfm)
|
||||
if (
|
||||
this.hasRulesGfm &&
|
||||
(execArr = (this.rules as RulesInlineGfm).del.exec(nextPart))
|
||||
) {
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += this.renderer.del(this.output(execArr[1]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// text
|
||||
if ((execArr = this.rules.text.exec(nextPart))) {
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
nextPart = nextPart.substring(execArr[0].length);
|
||||
out += this.renderer.text(
|
||||
this.options.escape(this.smartypants(execArr[0]))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nextPart) {
|
||||
throw new Error("Infinite loop on byte: " + nextPart.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Link.
|
||||
*/
|
||||
protected outputLink(execArr: RegExpExecArray, link: Link) {
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
const href = this.options.escape(link.href);
|
||||
const title = link.title ? this.options.escape(link.title) : null;
|
||||
|
||||
return execArr[0].charAt(0) !== "!"
|
||||
? this.renderer.link(href, title || "", this.output(execArr[1]))
|
||||
: this.renderer.image(href, title || "", this.options.escape(execArr[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Smartypants Transformations.
|
||||
*/
|
||||
protected smartypants(text: string) {
|
||||
if (!this.options.smartypants) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return (
|
||||
text
|
||||
// em-dashes
|
||||
.replace(/---/g, "\u2014")
|
||||
// en-dashes
|
||||
.replace(/--/g, "\u2013")
|
||||
// opening singles
|
||||
.replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018")
|
||||
// closing singles & apostrophes
|
||||
.replace(/'/g, "\u2019")
|
||||
// opening doubles
|
||||
.replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201c")
|
||||
// closing doubles
|
||||
.replace(/"/g, "\u201d")
|
||||
// ellipses
|
||||
.replace(/\.{3}/g, "\u2026")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mangle Links.
|
||||
*/
|
||||
protected mangle(text: string) {
|
||||
if (!this.options.mangle) {
|
||||
return text;
|
||||
}
|
||||
|
||||
let out = "";
|
||||
const length = text.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
let str: string = "";
|
||||
|
||||
if (Math.random() > 0.5) {
|
||||
str = "x" + text.charCodeAt(i).toString(16);
|
||||
}
|
||||
|
||||
out += "&#" + str + ";";
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
196
markdown/src/interfaces.ts
Normal file
@ -0,0 +1,196 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import { escape, unescape } from "./helpers.ts";
|
||||
import type { Renderer } from "./renderer.ts";
|
||||
|
||||
export interface Obj {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface RulesBlockBase {
|
||||
newline: RegExp;
|
||||
code: RegExp;
|
||||
hr: RegExp;
|
||||
heading: RegExp;
|
||||
lheading: RegExp;
|
||||
blockquote: RegExp;
|
||||
list: RegExp;
|
||||
html: RegExp;
|
||||
def: RegExp;
|
||||
paragraph: RegExp;
|
||||
text: RegExp;
|
||||
bullet: RegExp;
|
||||
/**
|
||||
* List item (<li>).
|
||||
*/
|
||||
item: RegExp;
|
||||
}
|
||||
|
||||
export interface RulesBlockGfm extends RulesBlockBase {
|
||||
fences: RegExp;
|
||||
}
|
||||
|
||||
export interface RulesBlockTables extends RulesBlockGfm {
|
||||
nptable: RegExp;
|
||||
table: RegExp;
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
href: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface Links {
|
||||
[key: string]: Link;
|
||||
}
|
||||
|
||||
export enum TokenType {
|
||||
space = 1,
|
||||
text,
|
||||
paragraph,
|
||||
heading,
|
||||
listStart,
|
||||
listEnd,
|
||||
looseItemStart,
|
||||
looseItemEnd,
|
||||
listItemStart,
|
||||
listItemEnd,
|
||||
blockquoteStart,
|
||||
blockquoteEnd,
|
||||
code,
|
||||
table,
|
||||
html,
|
||||
hr,
|
||||
}
|
||||
|
||||
export type Align = "center" | "left" | "right" | "";
|
||||
|
||||
export interface Token {
|
||||
type: number | string;
|
||||
text?: string;
|
||||
lang?: string;
|
||||
depth?: number;
|
||||
header?: string[];
|
||||
align?: Align[];
|
||||
cells?: string[][];
|
||||
ordered?: boolean;
|
||||
pre?: boolean;
|
||||
escaped?: boolean;
|
||||
execArr?: RegExpExecArray;
|
||||
/**
|
||||
* Used for debugging. Identifies the line number in the resulting HTML file.
|
||||
*/
|
||||
line?: number;
|
||||
}
|
||||
|
||||
export interface RulesInlineBase {
|
||||
escape: RegExp;
|
||||
autolink: RegExp;
|
||||
tag: RegExp;
|
||||
link: RegExp;
|
||||
reflink: RegExp;
|
||||
nolink: RegExp;
|
||||
strong: RegExp;
|
||||
em: RegExp;
|
||||
code: RegExp;
|
||||
br: RegExp;
|
||||
text: RegExp;
|
||||
_inside: RegExp;
|
||||
_href: RegExp;
|
||||
}
|
||||
|
||||
export interface RulesInlinePedantic extends RulesInlineBase {}
|
||||
|
||||
/**
|
||||
* GFM Inline Grammar
|
||||
*/
|
||||
export interface RulesInlineGfm extends RulesInlineBase {
|
||||
url: RegExp;
|
||||
del: RegExp;
|
||||
}
|
||||
|
||||
export interface RulesInlineBreaks extends RulesInlineGfm {}
|
||||
|
||||
export class MarkedOptions {
|
||||
gfm?: boolean = true;
|
||||
tables?: boolean = true;
|
||||
breaks?: boolean = false;
|
||||
pedantic?: boolean = false;
|
||||
sanitize?: boolean = false;
|
||||
sanitizer?: (text: string) => string;
|
||||
mangle?: boolean = false;
|
||||
smartLists?: boolean = false;
|
||||
silent?: boolean = false;
|
||||
/**
|
||||
* @param code The section of code to pass to the highlighter.
|
||||
* @param lang The programming language specified in the code block.
|
||||
*/
|
||||
highlight?: (code: string, lang?: string) => string;
|
||||
langPrefix?: string = "lang-";
|
||||
smartypants?: boolean = false;
|
||||
headerPrefix?: string = "";
|
||||
/**
|
||||
* An object containing functions to render tokens to HTML. Default: `new Renderer()`
|
||||
*/
|
||||
renderer?: Renderer;
|
||||
/**
|
||||
* Self-close the tags for void elements (<br/>, <img/>, etc.)
|
||||
* with a "/" as required by XHTML.
|
||||
*/
|
||||
xhtml?: boolean = false;
|
||||
/**
|
||||
* The function that will be using to escape HTML entities.
|
||||
* By default using inner helper.
|
||||
*/
|
||||
escape?: (html: string, encode?: boolean) => string = escape;
|
||||
/**
|
||||
* The function that will be using to unescape HTML entities.
|
||||
* By default using inner helper.
|
||||
*/
|
||||
unescape?: (html: string) => string = unescape;
|
||||
/**
|
||||
* If set to `true`, an inline text will not be taken in paragraph.
|
||||
*
|
||||
* ```ts
|
||||
* // isNoP == false
|
||||
* Marked.parse('some text'); // returns '<p>some text</p>'
|
||||
*
|
||||
* Marked.setOptions({isNoP: true});
|
||||
*
|
||||
* Marked.parse('some text'); // returns 'some text'
|
||||
* ```
|
||||
*/
|
||||
isNoP?: boolean;
|
||||
}
|
||||
|
||||
export interface LexerReturns {
|
||||
tokens: Token[];
|
||||
links: Links;
|
||||
meta: Obj;
|
||||
}
|
||||
|
||||
export interface Parsed {
|
||||
content: string;
|
||||
meta: Obj;
|
||||
}
|
||||
|
||||
export interface DebugReturns extends LexerReturns {
|
||||
result: string;
|
||||
}
|
||||
|
||||
export interface Replacements {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface RulesInlineCallback {
|
||||
regexp?: RegExp;
|
||||
condition(): RegExp;
|
||||
tokenize(execArr: RegExpExecArray): void;
|
||||
}
|
||||
|
||||
export type SimpleRenderer = (execArr?: RegExpExecArray) => string;
|
154
markdown/src/marked.ts
Normal file
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import { BlockLexer } from "./block-lexer.ts";
|
||||
import {
|
||||
DebugReturns,
|
||||
LexerReturns,
|
||||
Links,
|
||||
MarkedOptions,
|
||||
SimpleRenderer,
|
||||
Token,
|
||||
TokenType,
|
||||
Parsed
|
||||
} from "./interfaces.ts";
|
||||
import { Parser } from "./parser.ts";
|
||||
|
||||
export class Marked {
|
||||
static options = new MarkedOptions();
|
||||
protected static simpleRenderers: SimpleRenderer[] = [];
|
||||
protected static parsed: Parsed = {
|
||||
content: "",
|
||||
meta: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges the default options with options that will be set.
|
||||
*
|
||||
* @param options Hash of options.
|
||||
*/
|
||||
static setOptions(options: MarkedOptions) {
|
||||
Object.assign(this.options, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting simple block rule.
|
||||
*/
|
||||
static setBlockRule(regexp: RegExp, renderer: SimpleRenderer = () => "") {
|
||||
BlockLexer.simpleRules.push(regexp);
|
||||
this.simpleRenderers.push(renderer);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts Markdown text and returns an object containing HTML and metadata.
|
||||
*
|
||||
* @param src String of markdown source to be compiled.
|
||||
* @param options Hash of options. They replace, but do not merge with the default options.
|
||||
* If you want the merging, you can to do this via `Marked.setOptions()`.
|
||||
*/
|
||||
static parse(src: string, options: MarkedOptions = this.options): Parsed {
|
||||
try {
|
||||
const { tokens, links, meta } = this.callBlockLexer(src, options);
|
||||
this.parsed.content = this.callParser(tokens, links, options);
|
||||
this.parsed.meta = meta;
|
||||
return this.parsed;
|
||||
} catch (e) {
|
||||
this.parsed.content = this.callMe(e);
|
||||
return this.parsed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts Markdown text and returns object with text in HTML format,
|
||||
* tokens and links from `BlockLexer.parser()`.
|
||||
*
|
||||
* @param src String of markdown source to be compiled.
|
||||
* @param options Hash of options. They replace, but do not merge with the default options.
|
||||
* If you want the merging, you can to do this via `Marked.setOptions()`.
|
||||
*/
|
||||
static debug(
|
||||
src: string,
|
||||
options: MarkedOptions = this.options,
|
||||
): DebugReturns {
|
||||
const { tokens, links, meta } = this.callBlockLexer(src, options);
|
||||
let origin = tokens.slice();
|
||||
const parser = new Parser(options);
|
||||
parser.simpleRenderers = this.simpleRenderers;
|
||||
const result = parser.debug(links, tokens);
|
||||
|
||||
/**
|
||||
* Translates a token type into a readable form,
|
||||
* and moves `line` field to a first place in a token object.
|
||||
*/
|
||||
origin = origin.map((token) => {
|
||||
token.type = (TokenType as any)[token.type] || token.type;
|
||||
|
||||
const line = token.line;
|
||||
delete token.line;
|
||||
if (line) {
|
||||
return { ...{ line }, ...token };
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
});
|
||||
|
||||
return { tokens: origin, links, meta, result};
|
||||
}
|
||||
|
||||
protected static callBlockLexer(
|
||||
src: string = "",
|
||||
options?: MarkedOptions,
|
||||
): LexerReturns {
|
||||
if (typeof src != "string") {
|
||||
throw new Error(
|
||||
`Expected that the 'src' parameter would have a 'string' type, got '${typeof src}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Preprocessing.
|
||||
src = src
|
||||
.replace(/\r\n|\r/g, "\n")
|
||||
.replace(/\t/g, " ")
|
||||
.replace(/\u00a0/g, " ")
|
||||
.replace(/\u2424/g, "\n")
|
||||
.replace(/^ +$/gm, "");
|
||||
|
||||
return BlockLexer.lex(src, options, true);
|
||||
}
|
||||
|
||||
protected static callParser(
|
||||
tokens: Token[],
|
||||
links: Links,
|
||||
options?: MarkedOptions,
|
||||
): string {
|
||||
if (this.simpleRenderers.length) {
|
||||
const parser = new Parser(options);
|
||||
parser.simpleRenderers = this.simpleRenderers;
|
||||
return parser.parse(links, tokens);
|
||||
} else {
|
||||
return Parser.parse(tokens, links, options);
|
||||
}
|
||||
}
|
||||
|
||||
protected static callMe(err: Error) {
|
||||
err.message +=
|
||||
"\nPlease report this to https://github.com/ts-stack/markdown";
|
||||
|
||||
if (this.options.silent && this.options.escape) {
|
||||
return "<p>An error occured:</p><pre>" +
|
||||
this.options.escape(err.message + "", true) + "</pre>";
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
247
markdown/src/parser.ts
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import { InlineLexer } from "./inline-lexer.ts";
|
||||
import {
|
||||
Links,
|
||||
MarkedOptions,
|
||||
SimpleRenderer,
|
||||
Token,
|
||||
TokenType,
|
||||
} from "./interfaces.ts";
|
||||
import { Marked } from "./marked.ts";
|
||||
import { Renderer } from "./renderer.ts";
|
||||
|
||||
/**
|
||||
* Parsing & Compiling.
|
||||
*/
|
||||
export class Parser {
|
||||
simpleRenderers: SimpleRenderer[] = [];
|
||||
protected tokens: Token[];
|
||||
protected token: Token | undefined;
|
||||
protected inlineLexer!: InlineLexer;
|
||||
protected options: MarkedOptions;
|
||||
protected renderer: Renderer;
|
||||
protected line: number = 0;
|
||||
|
||||
constructor(options?: MarkedOptions) {
|
||||
this.tokens = [];
|
||||
this.token = undefined;
|
||||
this.options = options || Marked.options;
|
||||
this.renderer = this.options.renderer || new Renderer(this.options);
|
||||
}
|
||||
|
||||
static parse(tokens: Token[], links: Links, options?: MarkedOptions): string {
|
||||
const parser = new this(options);
|
||||
return parser.parse(links, tokens);
|
||||
}
|
||||
|
||||
parse(links: Links, tokens: Token[]) {
|
||||
this.inlineLexer = new InlineLexer(
|
||||
InlineLexer,
|
||||
links,
|
||||
this.options,
|
||||
this.renderer,
|
||||
);
|
||||
this.tokens = tokens.reverse();
|
||||
|
||||
let out = "";
|
||||
|
||||
while (this.next()) {
|
||||
out += this.tok();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
debug(links: Links, tokens: Token[]) {
|
||||
this.inlineLexer = new InlineLexer(
|
||||
InlineLexer,
|
||||
links,
|
||||
this.options,
|
||||
this.renderer,
|
||||
);
|
||||
this.tokens = tokens.reverse();
|
||||
|
||||
let out = "";
|
||||
|
||||
while (this.next()) {
|
||||
const outToken: string = this.tok() || "";
|
||||
if (!this.token) throw ReferenceError;
|
||||
this.token.line = this.line += outToken.split("\n").length - 1;
|
||||
out += outToken;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
protected next() {
|
||||
return (this.token = this.tokens.pop());
|
||||
}
|
||||
|
||||
protected getNextElement() {
|
||||
return this.tokens[this.tokens.length - 1];
|
||||
}
|
||||
|
||||
protected parseText() {
|
||||
if (!this.token) throw ReferenceError;
|
||||
let body = this.token.text;
|
||||
let nextElement: Token;
|
||||
|
||||
while (
|
||||
(nextElement = this.getNextElement()) &&
|
||||
nextElement.type == TokenType.text
|
||||
) {
|
||||
body += "\n" + this.next()?.text;
|
||||
}
|
||||
|
||||
return this.inlineLexer.output(body || "");
|
||||
}
|
||||
|
||||
protected tok() {
|
||||
if (!this.token) throw ReferenceError;
|
||||
switch (this.token.type) {
|
||||
case TokenType.space: {
|
||||
return "";
|
||||
}
|
||||
case TokenType.paragraph: {
|
||||
return this.renderer.paragraph(
|
||||
this.inlineLexer.output(this.token.text || ""),
|
||||
);
|
||||
}
|
||||
case TokenType.text: {
|
||||
if (this.options.isNoP) {
|
||||
return this.parseText();
|
||||
} else {
|
||||
return this.renderer.paragraph(this.parseText());
|
||||
}
|
||||
}
|
||||
case TokenType.heading: {
|
||||
return this.renderer.heading(
|
||||
this.inlineLexer.output(this.token.text || ""),
|
||||
this.token.depth || 0,
|
||||
this.token.text || "",
|
||||
);
|
||||
}
|
||||
case TokenType.listStart: {
|
||||
let body = "";
|
||||
const ordered = this.token.ordered;
|
||||
|
||||
while (this.next()?.type != TokenType.listEnd) {
|
||||
body += this.tok();
|
||||
}
|
||||
|
||||
return this.renderer.list(body, ordered);
|
||||
}
|
||||
case TokenType.listItemStart: {
|
||||
let body = "";
|
||||
|
||||
while (this.next()?.type != TokenType.listItemEnd) {
|
||||
body += this.token.type == (TokenType.text as any)
|
||||
? this.parseText()
|
||||
: this.tok();
|
||||
}
|
||||
|
||||
return this.renderer.listitem(body);
|
||||
}
|
||||
case TokenType.looseItemStart: {
|
||||
let body = "";
|
||||
|
||||
while (this.next()?.type != TokenType.listItemEnd) {
|
||||
body += this.tok();
|
||||
}
|
||||
|
||||
return this.renderer.listitem(body);
|
||||
}
|
||||
case TokenType.code: {
|
||||
return this.renderer.code(
|
||||
this.token.text || "",
|
||||
this.token.lang,
|
||||
this.token.escaped,
|
||||
);
|
||||
}
|
||||
case TokenType.table: {
|
||||
let header = "";
|
||||
let body = "";
|
||||
let cell;
|
||||
|
||||
if (
|
||||
!this.token || !this.token.header || !this.token.align ||
|
||||
!this.token.cells
|
||||
) {
|
||||
throw ReferenceError;
|
||||
}
|
||||
// header
|
||||
cell = "";
|
||||
for (let i = 0; i < this.token.header.length; i++) {
|
||||
const flags = { header: true, align: this.token.align[i] };
|
||||
const out = this.inlineLexer.output(this.token.header[i]);
|
||||
|
||||
cell += this.renderer.tablecell(out, flags);
|
||||
}
|
||||
|
||||
header += this.renderer.tablerow(cell);
|
||||
|
||||
for (const row of this.token.cells) {
|
||||
cell = "";
|
||||
|
||||
for (let j = 0; j < row.length; j++) {
|
||||
cell += this.renderer.tablecell(this.inlineLexer.output(row[j]), {
|
||||
header: false,
|
||||
align: this.token.align[j],
|
||||
});
|
||||
}
|
||||
|
||||
body += this.renderer.tablerow(cell);
|
||||
}
|
||||
|
||||
return this.renderer.table(header, body);
|
||||
}
|
||||
case TokenType.blockquoteStart: {
|
||||
let body = "";
|
||||
|
||||
while (this.next()?.type != TokenType.blockquoteEnd) {
|
||||
body += this.tok();
|
||||
}
|
||||
|
||||
return this.renderer.blockquote(body);
|
||||
}
|
||||
case TokenType.hr: {
|
||||
return this.renderer.hr();
|
||||
}
|
||||
case TokenType.html: {
|
||||
const html = !this.token.pre && !this.options.pedantic
|
||||
? this.inlineLexer.output(this.token.text || "")
|
||||
: this.token.text;
|
||||
return this.renderer.html(html || "");
|
||||
}
|
||||
default: {
|
||||
if (this.simpleRenderers.length) {
|
||||
for (let i = 0; i < this.simpleRenderers.length; i++) {
|
||||
if (this.token.type == "simpleRule" + (i + 1)) {
|
||||
return this.simpleRenderers[i].call(
|
||||
this.renderer,
|
||||
this.token.execArr,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const errMsg = `Token with "${this.token.type}" type was not found.`;
|
||||
|
||||
if (this.options.silent) {
|
||||
console.log(errMsg);
|
||||
} else {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
179
markdown/src/renderer.ts
Normal file
@ -0,0 +1,179 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/chjj/marked
|
||||
*
|
||||
* Copyright (c) 2018, Костя Третяк. (MIT Licensed)
|
||||
* https://github.com/ts-stack/markdown
|
||||
*/
|
||||
|
||||
import type { Align, MarkedOptions } from "./interfaces.ts";
|
||||
import { Marked } from "./marked.ts";
|
||||
|
||||
export class Renderer {
|
||||
protected options: MarkedOptions;
|
||||
|
||||
constructor(options?: MarkedOptions) {
|
||||
this.options = options || Marked.options;
|
||||
}
|
||||
|
||||
code(code: string, lang?: string, escaped?: boolean): string {
|
||||
if (this.options.highlight) {
|
||||
const out = this.options.highlight(code, lang);
|
||||
|
||||
if (out != null && out !== code) {
|
||||
escaped = true;
|
||||
code = out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.options.escape) throw ReferenceError;
|
||||
|
||||
if (!lang) {
|
||||
return (
|
||||
"\n<pre><code>" +
|
||||
(escaped ? code : this.options.escape(code, true)) +
|
||||
"\n</code></pre>\n"
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
'\n<pre><code class="' +
|
||||
this.options.langPrefix +
|
||||
this.options.escape(lang, true) +
|
||||
'">' +
|
||||
(escaped ? code : this.options.escape(code, true)) +
|
||||
"\n</code></pre>\n"
|
||||
);
|
||||
}
|
||||
|
||||
blockquote(quote: string): string {
|
||||
return "<blockquote>\n" + quote + "</blockquote>\n";
|
||||
}
|
||||
|
||||
html(html: string): string {
|
||||
return html;
|
||||
}
|
||||
|
||||
heading(text: string, level: number, raw: string): string {
|
||||
const id: string =
|
||||
this.options.headerPrefix + raw.toLowerCase().replace(/[^\w]+/g, "-");
|
||||
|
||||
return `<h${level} id="${id}">${text}</h${level}>\n`;
|
||||
}
|
||||
|
||||
hr(): string {
|
||||
return this.options.xhtml ? "<hr/>\n" : "<hr>\n";
|
||||
}
|
||||
|
||||
list(body: string, ordered?: boolean): string {
|
||||
const type = ordered ? "ol" : "ul";
|
||||
|
||||
return `\n<${type}>\n${body}</${type}>\n`;
|
||||
}
|
||||
|
||||
listitem(text: string): string {
|
||||
return "<li>" + text + "</li>\n";
|
||||
}
|
||||
|
||||
paragraph(text: string): string {
|
||||
return "<p>" + text + "</p>\n";
|
||||
}
|
||||
|
||||
table(header: string, body: string): string {
|
||||
return `
|
||||
<table>
|
||||
<thead>
|
||||
${header}</thead>
|
||||
<tbody>
|
||||
${body}</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
tablerow(content: string): string {
|
||||
return "<tr>\n" + content + "</tr>\n";
|
||||
}
|
||||
|
||||
tablecell(
|
||||
content: string,
|
||||
flags: { header?: boolean; align?: Align }
|
||||
): string {
|
||||
const type = flags.header ? "th" : "td";
|
||||
const tag = flags.align
|
||||
? "<" + type + ' style="text-align:' + flags.align + '">'
|
||||
: "<" + type + ">";
|
||||
return tag + content + "</" + type + ">\n";
|
||||
}
|
||||
|
||||
// *** Inline level renderer methods. ***
|
||||
|
||||
strong(text: string): string {
|
||||
return "<strong>" + text + "</strong>";
|
||||
}
|
||||
|
||||
em(text: string): string {
|
||||
return "<em>" + text + "</em>";
|
||||
}
|
||||
|
||||
codespan(text: string): string {
|
||||
return "<code>" + text + "</code>";
|
||||
}
|
||||
|
||||
br(): string {
|
||||
return this.options.xhtml ? "<br/>" : "<br>";
|
||||
}
|
||||
|
||||
del(text: string): string {
|
||||
return "<del>" + text + "</del>";
|
||||
}
|
||||
|
||||
link(href: string, title: string, text: string): string {
|
||||
if (this.options.sanitize) {
|
||||
let prot: string;
|
||||
if (!this.options.unescape) throw ReferenceError;
|
||||
try {
|
||||
prot = decodeURIComponent(this.options.unescape(href))
|
||||
.replace(/[^\w:]/g, "")
|
||||
.toLowerCase();
|
||||
} catch (e) {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (
|
||||
prot.indexOf("javascript:") === 0 ||
|
||||
prot.indexOf("vbscript:") === 0 ||
|
||||
prot.indexOf("data:") === 0
|
||||
) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
let out = '<a href="' + href + '"';
|
||||
|
||||
if (title) {
|
||||
out += ' title="' + title + '"';
|
||||
}
|
||||
|
||||
out += ">" + text + "</a>";
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
image(href: string, title: string, text: string): string {
|
||||
let out = '<img src="' + href + '" alt="' + text + '"';
|
||||
|
||||
if (title) {
|
||||
out += ' title="' + title + '"';
|
||||
}
|
||||
|
||||
out += this.options.xhtml ? "/>" : ">";
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
text(text: string): string {
|
||||
return text;
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
FROM hibas123.azurecr.io/deno
|
||||
FROM docker.hibas123.de/deno
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ADD src /app/src
|
||||
|
||||
RUN /usr/bin/deno cache --unstable src/registry.ts
|
||||
ADD public /app/public
|
||||
ADD tsconfig.json /app/
|
||||
RUN /usr/bin/deno cache --unstable --config /app/tsconfig.json src/registry.ts
|
||||
|
||||
VOLUME [ "/app/data" ]
|
||||
ENTRYPOINT [ "/usr/bin/deno", "run", "-A", "--unstable", "/app/src/registry.ts" ]
|
||||
ENTRYPOINT [ "/usr/bin/deno", "run", "-A", "--unstable", "--config", "/app/tsconfig.json", "/app/src/registry.ts" ]
|
||||
|
BIN
registry/favicon.png
Normal file
After Width: | Height: | Size: 44 KiB |
11
registry/favicon.svg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
registry/public/android-icon-192x192.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
registry/public/apple-icon-114x114.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
registry/public/apple-icon-120x120.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
registry/public/apple-icon-144x144.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
registry/public/apple-icon-152x152.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
registry/public/apple-icon-180x180.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
registry/public/apple-icon-57x57.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
registry/public/apple-icon-60x60.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
registry/public/apple-icon-72x72.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
registry/public/apple-icon-76x76.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
2
registry/public/browserconfig.xml
Normal 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>
|
BIN
registry/public/favicon-16x16.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
registry/public/favicon-256x256.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
registry/public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
registry/public/favicon-96x96.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
registry/public/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
1
registry/public/file.svg
Normal 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 |
1
registry/public/folder.svg
Normal 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 |
41
registry/public/manifest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
BIN
registry/public/ms-icon-144x144.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
registry/public/ms-icon-150x150.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
registry/public/ms-icon-310x310.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
registry/public/ms-icon-70x70.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
2
registry/public/paper.min.css
vendored
Normal file
218
registry/public/prism.css
Normal 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%;
|
||||
}
|
||||
|
6
registry/scripts/getPaperCSS.ts
Normal 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);
|
@ -14,6 +14,8 @@ const config =
|
||||
) || {};
|
||||
|
||||
if (!config.user) config.user = {};
|
||||
if (!config.web) config.web = {};
|
||||
if (!config.general) config.general = {};
|
||||
|
||||
const env = Deno.env.toObject();
|
||||
|
||||
@ -40,11 +42,30 @@ for (const key in env) {
|
||||
case "S3_SECRET":
|
||||
config.s3 = { ...(config.s3 || {}), secretKey: env[key] };
|
||||
break;
|
||||
case "S3_REGION":
|
||||
config.s3 = { ...(config.s3 || {}), region: env[key] };
|
||||
break;
|
||||
case "WEB_URL":
|
||||
config.web.url = env[key];
|
||||
break;
|
||||
case "WEB_TRACKING":
|
||||
config.web.tracking = env[key];
|
||||
break;
|
||||
case "GENERAL_DEV":
|
||||
config.general.dev = env[key] === "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.general.dev) {
|
||||
console.warn("Dev mode active!!!");
|
||||
}
|
||||
|
||||
if (!config.web.url) {
|
||||
console.error("The web.url configuration has to be set!");
|
||||
}
|
||||
|
||||
console.log("Known users:", Object.keys(config.user));
|
||||
|
||||
export default config;
|
||||
|
@ -8,6 +8,7 @@ export interface IPackage {
|
||||
description: string;
|
||||
versions: string[];
|
||||
deprecated: boolean;
|
||||
readme: string;
|
||||
}
|
||||
|
||||
export interface IApiKey {
|
||||
|
@ -1,29 +1,30 @@
|
||||
// export { MongoClient, ObjectId } from "https://deno.land/x/mongo@v0.9.1/mod.ts";
|
||||
// @deno-types="./types/hotfix.d.ts"
|
||||
|
||||
export * as S3 from "https://deno.land/x/s3/mod.ts";
|
||||
export * as S3 from "https://deno.land/x/s3@0.3.0/mod.ts";
|
||||
export { S3Error } from "https://deno.land/x/s3@0.3.0/src/error.ts";
|
||||
|
||||
export * as Ini from "https://deno.land/x/ini/mod.ts";
|
||||
export * as Ini from "https://deno.hibas123.de/raw/ini@0.0.3/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 ABC from "https://deno.land/x/abc@v1.2.4/mod.ts";
|
||||
export * as CorsMW from "https://deno.land/x/abc@v1.2.4/middleware/cors.ts";
|
||||
export * as LoggerMW from "https://deno.land/x/abc@v1.2.4/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.83.0/path/mod.ts";
|
||||
export * as FS from "https://deno.land/std@0.83.0/fs/mod.ts";
|
||||
export * as Base64 from "https://deno.land/std@0.83.0/encoding/base64.ts";
|
||||
export * as Hash from "https://deno.land/std@0.83.0/hash/mod.ts";
|
||||
export * as Colors from "https://deno.land/std@0.83.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.hibas123.de/raw/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";
|
||||
import * as Pico from "https://deno.hibas123.de/raw/@denreg-jsx@0.1.2/mod.ts";
|
||||
|
||||
export { Pico };
|
||||
|
||||
export const Datastore = DS;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/// <reference path="./types/jsx.d.ts" />
|
||||
import { ABC, CorsMW, LoggerMW } from "./deps.ts";
|
||||
// /// <reference path="./types/jsx.d.ts" />
|
||||
import { ABC, CorsMW, LoggerMW, Path } from "./deps.ts";
|
||||
import config from "./config.ts";
|
||||
|
||||
const port = config?.api?.port || 8000;
|
||||
@ -8,16 +8,50 @@ 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"));
|
||||
|
||||
import raw from "./http/raw.ts";
|
||||
raw(app.group("/raw"));
|
||||
|
||||
import intellisense from "./http/intellisense.ts";
|
||||
intellisense(app.group("/.well-known"));
|
||||
|
||||
import view from "./http/views.ts";
|
||||
view(app);
|
||||
// function logNode(router: Node, indent = 0) {
|
||||
// trees.map((tree) => {
|
||||
// console.log("Path:", tree.path);
|
||||
// tree.children?.forEach((node) => logNode(node, indent + 3));
|
||||
// });
|
||||
// }
|
||||
|
||||
console.log(app.router.trees.GET);
|
||||
// const trees = Object.values(app.router) as Node[];
|
||||
// trees.forEach(logNode);
|
||||
|
||||
import render from "./renderer.tsx";
|
||||
app.renderer = {
|
||||
|
@ -1,7 +1,13 @@
|
||||
import { ABC, Path, Compress, FS } from "../deps.ts";
|
||||
import { ABC, Path, Compress, FS, Colors, S3Error } from "../deps.ts";
|
||||
|
||||
import bucket from "../s3.ts";
|
||||
import { isValidPackageName, basicauth, isValidFullVersion } from "../utils.ts";
|
||||
import {
|
||||
isValidPackageName,
|
||||
basicauth,
|
||||
isValidFullVersion,
|
||||
getAbsolutePackageVersion,
|
||||
getBucketFilePath,
|
||||
} from "../utils.ts";
|
||||
|
||||
import db, { IPackage } from "../db.ts";
|
||||
|
||||
@ -21,8 +27,60 @@ export default function api(g: ABC.Group) {
|
||||
cacheControl
|
||||
);
|
||||
|
||||
g.get("/module", async (ctx) => {
|
||||
return db.package.find({}).then((res) => res.map((e) => e.name));
|
||||
});
|
||||
|
||||
g.get("/module/:module", async (ctx) => {
|
||||
const module = await db.package.findOne({ name: ctx.params.module });
|
||||
if (!module) {
|
||||
ctx.response.status = 404;
|
||||
return "// Not found";
|
||||
} else {
|
||||
return module.versions;
|
||||
}
|
||||
});
|
||||
|
||||
g.get("/module/:module/v/:version", async (ctx) => {
|
||||
const module = await db.package.findOne({ name: ctx.params.module });
|
||||
if (!module) {
|
||||
ctx.response.status = 404;
|
||||
return "// Not found";
|
||||
} else {
|
||||
let version = getAbsolutePackageVersion(
|
||||
module,
|
||||
ctx.params.version
|
||||
) as string;
|
||||
|
||||
if (!version) {
|
||||
ctx.response.status = 404;
|
||||
return "// Not found";
|
||||
}
|
||||
|
||||
const bucketPath = await getBucketFilePath(module.name, version, "/");
|
||||
|
||||
const filesItr = bucket.listAllObjects({
|
||||
batchSize: 100,
|
||||
prefix: bucketPath,
|
||||
});
|
||||
const allowedExts = new Set(
|
||||
(ctx.queryParams.ext || "js|ts").split("|").map((e) => "." + e)
|
||||
);
|
||||
|
||||
let files: string[] = [];
|
||||
for await (let file of filesItr) {
|
||||
const relPath = Path.posix.relative(bucketPath, file.key || "");
|
||||
const ext = Path.extname(relPath);
|
||||
if (allowedExts.has(ext)) files.push(relPath);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
});
|
||||
|
||||
// g.post("/getapikey", getApiKey, basicauth("api"));
|
||||
g.post("/package/:name", uploadPackage, cacheControl, basicauth("api"));
|
||||
g.post("/module/:name", uploadPackage, cacheControl, basicauth("api")); //Switch no module instead of package
|
||||
}
|
||||
|
||||
// async function getApiKey(ctx: ABC.Context) {
|
||||
@ -113,6 +171,7 @@ async function uploadPackage(ctx: ABC.Context) {
|
||||
owner: ctx.customContext.user,
|
||||
description: meta.description,
|
||||
deprecated: false,
|
||||
readme: meta.readme,
|
||||
versions: [],
|
||||
};
|
||||
|
||||
@ -138,16 +197,15 @@ 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));
|
||||
console.log("Put Object", bucketPath, body.byteLength);
|
||||
await bucket.putObject(bucketPath, body, {});
|
||||
}
|
||||
console.log("Setting new live version");
|
||||
|
||||
@ -168,8 +226,11 @@ 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);
|
||||
if (err instanceof S3Error) console.log(err.response);
|
||||
return {
|
||||
success: false,
|
||||
message: err.message,
|
||||
|
43
registry/src/http/intellisense.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { ABC } from "../deps.ts";
|
||||
|
||||
import config from "../config.ts";
|
||||
|
||||
export default function raw(g: ABC.Group) {
|
||||
g.get("/deno-import-intellisense.json", (ctx) => {
|
||||
return {
|
||||
version: 1,
|
||||
registries: [
|
||||
{
|
||||
schema: "/raw/:module([@]*[a-z0-9\\-\\_]*)@:version?/:path*",
|
||||
variables: [
|
||||
{
|
||||
key: "module",
|
||||
url: `${config.web.url}/api/module`,
|
||||
},
|
||||
{
|
||||
key: "version",
|
||||
url: `${config.web.url}/api/module/\${module}`,
|
||||
},
|
||||
{
|
||||
key: "path",
|
||||
url: `${config.web.url}/api/module/\${module}/v/\${{version}}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
schema: "/raw/:module([@]*[a-z0-9\\-\\_]*)/:path*",
|
||||
variables: [
|
||||
{
|
||||
key: "module",
|
||||
url: `${config.web.url}/api/module`,
|
||||
},
|
||||
{
|
||||
key: "path",
|
||||
url: `${config.web.url}/api/module/\${module}/v/latest`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
@ -1,5 +1,12 @@
|
||||
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,
|
||||
getContentType,
|
||||
} 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 +18,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(
|
||||
@ -37,6 +55,8 @@ export default function raw(g: ABC.Group) {
|
||||
if (!result) return E404();
|
||||
|
||||
ctx.response.headers.set("e-tag", result.etag);
|
||||
let contentType = getContentType(filepath);
|
||||
if (contentType) ctx.response.headers.set("Content-Type", contentType);
|
||||
return result.data;
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
/// <reference path="./types/jsx.d.ts" />
|
||||
import { React, jsx } from "./deps.ts";
|
||||
// / <reference path="./types/jsx.d.ts" />
|
||||
import { Pico } from "./deps.ts";
|
||||
|
||||
import config from "./config.ts";
|
||||
|
||||
const React = {
|
||||
createElement: Pico.h.bind(Pico),
|
||||
};
|
||||
|
||||
class StringReader implements Deno.Reader {
|
||||
private data: Uint8Array;
|
||||
private offset = 0;
|
||||
@ -25,15 +29,49 @@ class StringReader implements Deno.Reader {
|
||||
}
|
||||
}
|
||||
|
||||
type ELM = any;
|
||||
|
||||
type Component = () => ELM;
|
||||
|
||||
const componentCache = new Map<string, Component>();
|
||||
|
||||
async function loadComponent(name: string) {
|
||||
let mod = componentCache.get(name);
|
||||
if (!mod || config.general.dev) {
|
||||
mod = (await import(`./views/${name}.tsx`)).default;
|
||||
if (!mod) throw new Error("Invalid component " + name);
|
||||
componentCache.set(name, mod);
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
Promise.resolve().then(async () => {
|
||||
console.log("[PRECACHE] Start loading pages");
|
||||
await loadComponent("index");
|
||||
await loadComponent("package");
|
||||
await loadComponent("browse_folder");
|
||||
await loadComponent("browse_file");
|
||||
console.log("[PRECACHE] Finished loading pages");
|
||||
});
|
||||
|
||||
// import index from "./views/index.tsx";
|
||||
// componentCache.set("index", index as Component);
|
||||
// import pkg from "./views/package.tsx";
|
||||
// componentCache.set("package", pkg as Component);
|
||||
// import browse_folder from "./views/browse_folder.tsx";
|
||||
// componentCache.set("browse_folder", browse_folder as Component);
|
||||
// import browse_file from "./views/browse_file.tsx";
|
||||
// componentCache.set("browse_file", browse_file as Component);
|
||||
|
||||
export default async function render(
|
||||
name: string,
|
||||
data: any
|
||||
): Promise<Deno.Reader> {
|
||||
const component: {
|
||||
default: () => JSX.IntrinsicElements | Promise<JSX.IntrinsicElements>;
|
||||
} = await import(`./views/${name}.tsx`);
|
||||
const Component = await loadComponent(name);
|
||||
|
||||
const res = await (<component.default {...data} />).render();
|
||||
//@ts-ignore
|
||||
const res = await Pico.renderSSR(<Component {...data} />);
|
||||
|
||||
return new StringReader(res as string);
|
||||
return new StringReader("<!DOCTYPE html>\n" + res);
|
||||
}
|
||||
|
@ -5,12 +5,26 @@ if (!config.s3) {
|
||||
throw new Error("Config is missing [s3] section!");
|
||||
}
|
||||
|
||||
const bucket = new S3.S3Bucket({
|
||||
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 s3config: S3.S3BucketConfig = {
|
||||
bucket: config.s3.bucket || "deno-registry",
|
||||
endpointURL: config.s3.endpoint,
|
||||
accessKeyID: config.s3.accessKey,
|
||||
secretKey: config.s3.secretKey,
|
||||
region: config?.s3?.region || "us-east-1",
|
||||
});
|
||||
};
|
||||
|
||||
const bucket = new S3.S3Bucket(s3config);
|
||||
|
||||
export default bucket;
|
||||
|
3
registry/src/test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import * as Prism from "./vendor/prism/prism.js";
|
||||
|
||||
console.log((window as any).Prism);
|
4
registry/src/types/hotfix.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// fixes an issue in std@0.80.0
|
||||
interface ReadableStream<R> {
|
||||
getIterator(): any;
|
||||
}
|
5
registry/src/types/jsx.d.ts
vendored
@ -1,5 +0,0 @@
|
||||
declare namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
[elemName: string]: any;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { ABC, Base64 } from "./deps.ts";
|
||||
import { ABC, Base64, Path } from "./deps.ts";
|
||||
import config from "./config.ts";
|
||||
|
||||
const packageNameRegex = /^[@]?[a-zA-Z][\d\w\-\_]*$/g.compile();
|
||||
@ -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 = pkg.versions.sort(sortVersions).reverse();
|
||||
|
||||
const versions = meta.versions.sort(sortVersions).reverse();
|
||||
|
||||
if (!version) {
|
||||
if (!version || version === "latest") {
|
||||
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,
|
||||
@ -135,3 +156,18 @@ export async function getFile(
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const exts = new Map<string, string>();
|
||||
|
||||
exts.set(".js", "text/javascript");
|
||||
exts.set(".mjs", "text/javascript");
|
||||
exts.set(".json", "application/json");
|
||||
exts.set(".jsonld", "application/ld+json");
|
||||
exts.set(".css", "text/css");
|
||||
exts.set(".html", "text/html");
|
||||
exts.set(".htm", "text/html");
|
||||
|
||||
export function getContentType(filename: string) {
|
||||
const ext = Path.extname(filename);
|
||||
return exts.get(ext);
|
||||
}
|
||||
|
7
registry/src/vendor/prism.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import "./prism/prism.js";
|
||||
|
||||
const Prism = (window as any).Prism;
|
||||
|
||||
delete (window as any).Prism;
|
||||
|
||||
export default Prism;
|
239
registry/src/vendor/prism/prism.js
vendored
Normal file
@ -1,23 +1,99 @@
|
||||
/// <reference path="../types/jsx.d.ts" />
|
||||
import { React } from "../deps.ts";
|
||||
import { Pico } from "../deps.ts";
|
||||
import config from "../config.ts";
|
||||
|
||||
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 (
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"></meta>
|
||||
{/* <link
|
||||
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="stylesheet"
|
||||
href="https://unpkg.com/papercss@1.6.1/dist/paper.min.css"
|
||||
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="/public/prism.css" rel="stylesheet" />
|
||||
<style innerHTML={styles}></style>
|
||||
<title>{title}</title>
|
||||
<meta
|
||||
@ -29,7 +105,12 @@ export default function Base(p: any, children: any[]) {
|
||||
content="width=device-width,initial-scale=1"
|
||||
/>
|
||||
</head>
|
||||
<body class="site">{children}</body>
|
||||
<body class="site">
|
||||
{config.web.tracking && (
|
||||
<tracking innerHTML={config.web.tracking}></tracking>
|
||||
)}
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
112
registry/src/views/_browse.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { Pico, 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>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
/// <reference path="../types/jsx.d.ts" />
|
||||
import { React, Fragment } from "../deps.ts";
|
||||
import { Pico } from "../deps.ts";
|
||||
|
||||
export function Main(a: any, children: any) {
|
||||
return (
|
||||
|
20
registry/src/views/_pkgheader.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
// /// <reference path="../types/jsx.d.ts" />
|
||||
import { Pico, 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>
|
||||
</>
|
||||
);
|
||||
}
|
37
registry/src/views/browse_file.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { Pico } 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>
|
||||
);
|
||||
}
|
42
registry/src/views/browse_folder.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { Pico } 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>
|
||||
);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
/// <reference path="../types/jsx.d.ts" />
|
||||
import { React, Fragment } from "../deps.ts";
|
||||
import { Pico } 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 }) {
|
||||
|
@ -1,9 +1,8 @@
|
||||
/// <reference path="../types/jsx.d.ts" />
|
||||
import { React, Fragment, Marked } from "../deps.ts";
|
||||
import { Pico, 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,35 +43,28 @@ export default async function index({
|
||||
</Base>
|
||||
);
|
||||
|
||||
const readmeContent = await getFile(pkg.name, version, "README.md").then(
|
||||
(res) => {
|
||||
if (res)
|
||||
return Marked.parse(new TextDecoder().decode(res.data))
|
||||
.content as string;
|
||||
else return undefined;
|
||||
}
|
||||
);
|
||||
version = getAbsolutePackageVersion(pkg, version);
|
||||
const readmeContent = await getFile(
|
||||
pkg.name,
|
||||
version,
|
||||
pkg.readme || "README.md"
|
||||
).then((res) => {
|
||||
if (res)
|
||||
return Marked.parse(new TextDecoder().decode(res.data))
|
||||
.content as string;
|
||||
else return undefined;
|
||||
});
|
||||
|
||||
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 +87,11 @@ export default async function index({
|
||||
</div>
|
||||
</div>
|
||||
</Main>
|
||||
<Menu></Menu>
|
||||
<Menu>
|
||||
<a href={`/browse/${pkg.name}${version ? "@" + version : ""}/`}>
|
||||
Browse Files
|
||||
</a>
|
||||
</Menu>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
10
registry/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext", "deno.ns", "deno.unstable"],
|
||||
"jsx": "react",
|
||||
"jsxFactory": "Pico.h",
|
||||
"jsxFragmentFactory": "Pico.Fragment",
|
||||
"noImplicitAny": true,
|
||||
"strictPropertyInitialization": false
|
||||
}
|
||||
}
|