This commit is contained in:
monoid 2023-01-06 18:24:27 +09:00
parent d959076d28
commit 8b3db1e2f1
33 changed files with 1174 additions and 1034 deletions

View File

@ -9,7 +9,7 @@ const TypeToExt = {
"md": new Set([".md"]),
"text": new Set([".txt"]),
"code": new Set([".json", ".js", ".ts", ".css", ".tsx", ".jsx"]),
}
};
function extToType(ext: string) {
for (const [type, exts] of Object.entries(TypeToExt)) {
@ -20,12 +20,12 @@ function extToType(ext: string) {
return "unknown";
}
function FetchAndRender(props: {src: string, type: string}){
function FetchAndRender(props: { src: string; type: string }) {
const src = props.src;
const ext = extname(src);
const [content, setContent] = useState("");
useEffect(() => {
fetch(src).then(res => res.text()).then(setContent);
fetch(src).then((res) => res.text()).then(setContent);
}, [src]);
switch (props.type) {
case "text":
@ -33,7 +33,11 @@ function FetchAndRender(props: {src: string, type: string}){
case "md":
return <MarkdownRenderer text={content} />;
case "code":
return <pre style={{ whiteSpace: "pre-wrap" }}><code lang={ext.slice(1)}>{content}</code></pre>;
return (
<pre
style={{ whiteSpace: "pre-wrap" }}
><code lang={ext.slice(1)}>{content}</code></pre>
);
//case "csv":
// return <CsvRenderer content={content} />;
default:
@ -48,7 +52,7 @@ export function RenderView(props: {src: string}) {
case "text":
case "md":
case "code":
return <FetchAndRender src={src} type={type}></FetchAndRender>
return <FetchAndRender src={src} type={type}></FetchAndRender>;
//case "csv":
// return <CsvRenderer content={content} />;
case "image":

View File

@ -1,4 +1,4 @@
import { Head, asset } from "$fresh/runtime.ts";
import { asset, Head } from "$fresh/runtime.ts";
import { useState } from "preact/hooks";
import { extname, join } from "path/posix.ts";
import { ComponentChild } from "preact";
@ -7,14 +7,16 @@ import {extToIcon} from "../src/media.ts";
import { encodePath } from "../util/util.ts";
function ListItem(props: {
href: string,
icon: string,
children: ComponentChild
href: string;
icon: string;
children: ComponentChild;
}) {
return (
<li class="p-1 hover:bg-gray-400 transition-colors">
<a class="flex gap-2" href={props.href}>
<img src={asset(props.icon)}/><p class="">{props.children}</p></a>
<img src={asset(props.icon)} />
<p class="">{props.children}</p>
</a>
</li>
);
}
@ -37,7 +39,8 @@ export function DirList(props: DirListProps) {
const data = props;
const [files, setFiles] = useState(data.files);
return (<div>
return (
<div>
<UpList path={data.path}></UpList>
<ul class="border-2 rounded-md">
<li class="p-1 flex gap-2">
@ -48,20 +51,30 @@ export function DirList(props: DirListProps) {
<img src={asset("/icon/sort-alpha-down.svg")} /> Sort Alphabet
</button>
</li>
<ListItem key=".." href={`/dir/${encodePath(join(data.path,".."))}`}
<ListItem
key=".."
href={`/dir/${encodePath(join(data.path, ".."))}`}
icon="/icon/back.svg"
>...</ListItem>
>
...
</ListItem>
{files.map((file) => (
<ListItem key={file.name} href={`/dir/${encodePath(join(data.path,file.name))}`}
icon={file.isDirectory ? "/icon/folder.svg": extToIcon(extname(file.name))}
>{file.name}</ListItem>
<ListItem
key={file.name}
href={`/dir/${encodePath(join(data.path, file.name))}`}
icon={file.isDirectory
? "/icon/folder.svg"
: extToIcon(extname(file.name))}
>
{file.name}
</ListItem>
))}
</ul>
</div>)
</div>
);
function sortDir() {
// sort by directory first then by index
const sorted_files = files.map((x,i)=>
([x,i] as [EntryInfo, number]))
const sorted_files = files.map((x, i) => ([x, i] as [EntryInfo, number]))
.sort(
([a, ai], [b, bi]) => {
if (a.isDirectory && !b.isDirectory) {
@ -71,13 +84,13 @@ export function DirList(props: DirListProps) {
} else {
return ai - bi;
}
});
},
);
setFiles(sorted_files.map(([x, _]) => x));
}
function sortAlpha() {
// sort by alphabet first then by index
const sorted_files = files.map((x,i)=>
([x,i] as [EntryInfo, number]))
const sorted_files = files.map((x, i) => ([x, i] as [EntryInfo, number]))
.sort(
([a, ai], [b, bi]) => {
const ret = a.name.localeCompare(b.name);
@ -86,7 +99,8 @@ export function DirList(props: DirListProps) {
} else {
return ret;
}
});
},
);
setFiles(sorted_files.map(([x, _]) => x));
}
}

View File

@ -10,28 +10,37 @@ function SearchBar(props:{
const [search, setSearch] = useState(props.search ?? "");
return (
<div class="flex items-center justify-center">
<input type="text" class="w-1/2 px-4 py-2 border-2 rounded-lg" placeholder="Search..."
<input
type="text"
class="w-1/2 px-4 py-2 border-2 rounded-lg"
placeholder="Search..."
//onChange={(event)=>{}}
onKeyUp={(event) => {
if (event.currentTarget.value === search) return;
setSearch(event.currentTarget.value);
props.onSearch?.(event.currentTarget.value);
}}>{search}</input>
}}
>
{search}
</input>
</div>
)
);
}
export default function DocSearch(props: {
docs: Doc[]
docs: Doc[];
}) {
const [docs, setDocs] = useState(props.docs);
const index = Index.createIndex(props.docs);
return (
<>
<SearchBar onSearch={(s)=>{
<SearchBar
onSearch={(s) => {
setDocs(index.search(s));
}}></SearchBar>
}}
>
</SearchBar>
<h1 class="text-2xl font-bold">Doc</h1>
<ul class="mt-4">
{docs.map((doc) => (
@ -41,5 +50,5 @@ export default function DocSearch(props: {
))}
</ul>
</>
)
);
}

View File

@ -14,5 +14,5 @@ export default function FileViewer(props: { path: string }) {
<RenderView src={srcPath} />
</div>
</div>
)
);
}

View File

@ -5,9 +5,14 @@ export function MarkdownRenderer(props: { text: string | undefined }) {
if (text === undefined) {
text = "";
}
const index = text.indexOf('\n---', 3);
const index = text.indexOf("\n---", 3);
const c = text.slice(index + 4, text.length);
return <div class="markdown-body" dangerouslySetInnerHTML={{ __html: marked.parse(c) }} />;
return (
<div
class="markdown-body"
dangerouslySetInnerHTML={{ __html: marked.parse(c) }}
/>
);
}
export default MarkdownRenderer;

View File

@ -1,9 +1,8 @@
import { Head, asset } from "$fresh/runtime.ts";
import { asset, Head } from "$fresh/runtime.ts";
import { join } from "path/posix.ts";
import { ComponentChild } from "preact";
import { encodePath } from "../util/util.ts";
function stairs(path: string) {
if (path === ".") return [];
const uplist = path.split("/");
@ -20,21 +19,26 @@ export default function UpList(props:{path: string}) {
const data = props;
const uplist = stairs(data.path);
return (<div>
return (
<div>
<h1 class={"text-2xl flex flex-wrap"}>
<a href="/dir/" class="flex flex-wrap p-2 hover:bg-gray-400 rounded-sm">
<img src={asset("/icon/house.svg")} />
<span class="ml-1">Home</span></a>
{
uplist.map(([cur, up], i) => (
<span class="ml-1">Home</span>
</a>
{uplist.map(([cur, up], i) => (
<>
<span class="p-2">/</span>
<a class="flex flex-wrap p-2 hover:bg-gray-400 rounded-sm" href={`/dir/${encodePath(cur)}`}>
<a
class="flex flex-wrap p-2 hover:bg-gray-400 rounded-sm"
href={`/dir/${encodePath(cur)}`}
>
<img src={asset("/icon/folder.svg")} />
<span class="ml-1">{up}</span>
</a>
</>
))
}</h1>
</div>)
))}
</h1>
</div>
);
}

View File

@ -5,7 +5,10 @@ import { prepareSecretKey } from "./util/secret.ts";
export const key_out_cmd = new Command();
key_out_cmd.name("keyout")
.description("Output the secret key.")
.option("-f, --file <file:string>", "The file to output the key to. (default: stdout)")
.option(
"-f, --file <file:string>",
"The file to output the key to. (default: stdout)",
)
.option("--dotenv", "Output the key in dotenv format.")
.action(async ({ file, dotenv }) => {
const key = await prepareSecretKey();

44
main.ts
View File

@ -4,14 +4,19 @@
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
import { PluginRenderResult, Plugin, ServerContext, Manifest, StartOptions } from "$fresh/server.ts";
import {
Manifest,
Plugin,
PluginRenderResult,
ServerContext,
StartOptions,
} from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
import twindPlugin from "$fresh/plugins/twind.ts";
import twindConfig from "./twind.config.ts";
import "https://deno.land/std@0.170.0/dotenv/load.ts";
import { Command } from "https://deno.land/x/cliffy@v0.25.6/mod.ts";
import { fromFileUrl, join } from "path/mod.ts";
import { prepareSecretKey } from "./util/secret.ts";
@ -20,7 +25,9 @@ import { serve } from "http/server.ts";
import { user_command } from "./user.ts";
import { key_out_cmd } from "./keyout.ts";
const github_markdown= await Deno.readTextFile(join(fromFileUrl(import.meta.url), "..", "static", "github-markdown.css"))
const github_markdown = await Deno.readTextFile(
join(fromFileUrl(import.meta.url), "..", "static", "github-markdown.css"),
);
const CSSPlugin: Plugin = {
name: "css plugin",
@ -30,9 +37,9 @@ const CSSPlugin: Plugin = {
return {
styles: [{
cssText: github_markdown,
}]
}
}
}],
};
},
};
await prepareSecretKey();
@ -48,8 +55,14 @@ async function startServer(manifest: Manifest, options: StartOptions = {}){
}
}
async function start({port = 8000, hostname = "localhost"}: {port?: number, hostname?: string} = {}){
await startServer(manifest, { plugins: [twindPlugin(twindConfig), CSSPlugin],
async function start(
{ port = 8000, hostname = "localhost" }: {
port?: number;
hostname?: string;
} = {},
) {
await startServer(manifest, {
plugins: [twindPlugin(twindConfig), CSSPlugin],
port: port,
hostname: hostname,
});
@ -58,12 +71,15 @@ async function start({port = 8000, hostname = "localhost"}: {port?: number, host
if (import.meta.main) {
const cmd = new Command();
cmd.name("fs-server")
.description("A simple file server that supports search, upload, and download.")
.description(
"A simple file server that supports search, upload, and download.",
)
.version("0.0.1")
.globalOption("-d, --debug", "Enable debug mode.")
.command("start", "Start the server.")
.option("-p, --port <port:number>", "The port to listen on.",
{ default: 8000 })
.option("-p, --port <port:number>", "The port to listen on.", {
default: 8000,
})
.option("--auth", "Enable authentication.")
.arguments("[hostname:string]")
.action(async ({ debug, port, auth }, hostname) => {
@ -78,13 +94,11 @@ if (import.meta.main){
port: port,
hostname: hostname,
});
}
)
})
.command("user", user_command)
.command("keyout", key_out_cmd);
await cmd.parse(Deno.args);
}
else {
} else {
await start();
}

View File

@ -1,20 +1,21 @@
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import { getCookies } from "http/cookie.ts";
import { decode, verify } from "djwt";
import { verify } from "djwt";
import { prepareSecretKey } from "../util/secret.ts";
const secret_key = await prepareSecretKey();
export const handler =
async (req: Request , ctx: MiddlewareHandlerContext<Record<string, unknown>>) => {
export const handler = async (
req: Request,
ctx: MiddlewareHandlerContext<Record<string, unknown>>,
) => {
const cookies = getCookies(req.headers);
const jwt = cookies["auth"];
try {
const payload = await verify(jwt, secret_key);
ctx.state["login"] = payload;
}
catch (e){
} catch (e) {
ctx.state["login"] = null;
}
return await ctx.next();
}
};

View File

@ -6,7 +6,6 @@ import { getUser, verifyUser } from "../../src/user/user.ts";
import { create as createJWT } from "djwt";
import { prepareSecretKey } from "../../util/secret.ts";
const SECRET_KEY = await prepareSecretKey();
async function POST(req: Request, ctx: HandlerContext): Promise<Response> {
@ -21,7 +20,7 @@ async function POST(req: Request, ctx: HandlerContext): Promise<Response> {
if (await verifyUser(user, password.toString())) {
const headers = new Headers();
const jwt = await createJWT({ alg: "HS512", typ: "JWT" }, {
username: user.name
username: user.name,
}, SECRET_KEY);
setCookie(headers, {
name: "auth",
@ -31,19 +30,20 @@ async function POST(req: Request, ctx: HandlerContext): Promise<Response> {
maxAge: 60 * 60 * 24 * 7,
domain: url.hostname,
path: "/",
secure: url.protocol === "https:"
secure: url.protocol === "https:",
});
headers.set("Location", "/");
return new Response(null, {
status: Status.SeeOther, // See Other
headers: headers
headers: headers,
});
}
}
}
return new Response(`<!DOCTYPE html><html>
return new Response(
`<!DOCTYPE html><html>
<head> <title> Login Failed </title> </head>
<body>
<h1> Login Failed </h1>
@ -52,14 +52,16 @@ async function POST(req: Request, ctx: HandlerContext): Promise<Response> {
document.location.href = "/login";
</script>
</body>
</html>`, {
</html>`,
{
headers: {
"Content-Type": "text/html"
"Content-Type": "text/html",
},
status: Status.Forbidden,
});
},
);
}
export const handler = {
POST
POST,
};

View File

@ -1,5 +1,5 @@
import { PageProps, Handlers, HandlerContext } from "$fresh/server.ts";
import { Head, asset } from "$fresh/runtime.ts";
import { HandlerContext, Handlers, PageProps } from "$fresh/server.ts";
import { asset, Head } from "$fresh/runtime.ts";
import { removePrefixFromPathname } from "../../util/util.ts";
import { join } from "path/posix.ts";
import DirList, { EntryInfo } from "../../islands/DirList.tsx";
@ -10,12 +10,12 @@ type DirProps = {
path: string;
stat: Deno.FileInfo;
files: EntryInfo[];
}
};
type FileProps = {
type: "file";
path: string;
stat: Deno.FileInfo;
}
};
type DirOrFileProps = DirProps | FileProps;
@ -25,13 +25,15 @@ async function GET(req: Request, ctx: HandlerContext): Promise<Response>{
const login = ctx.state["login"];
if (!login) {
return new Response(null, {
status: 401,
status: 302,
headers: {
"Location": "/login",
"content-type": "text/plain",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
}
"Access-Control-Allow-Headers":
"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
},
});
}
}
@ -40,43 +42,45 @@ async function GET(req: Request, ctx: HandlerContext): Promise<Response>{
const stat = await Deno.stat(path);
if (stat.isDirectory) {
const filesIter = await Deno.readDir(path);
const files: EntryInfo[] = []
const files: EntryInfo[] = [];
for await (const file of filesIter) {
const fileStat = await Deno.stat(join(path, file.name));
files.push({
...file,
lastModified: fileStat.mtime ? new Date(fileStat.mtime) : undefined,
size: fileStat.size
size: fileStat.size,
});
}
return await ctx.render({
type: "dir",
stat,
files
, path
})
}
else{
files,
path,
});
} else {
return await ctx.render({
type: "file",
stat, path
stat,
path,
});
}
}
export const handler: Handlers = {
GET
}
GET,
};
export default function DirLists(props: PageProps<DirOrFileProps>) {
const data = props.data;
return (<>
return (
<>
<Head>
<title>Simple file server : {data.path}</title>
</Head>
<div class="p-4 mx-auto max-w-screen-md">
{data.type === "dir" ? (<DirList path={data.path} files={data.files}></DirList>) :
(<FileViewer path={data.path}></FileViewer>)}
{data.type === "dir"
? <DirList path={data.path} files={data.files}></DirList>
: <FileViewer path={data.path}></FileViewer>}
</div>
</>
);

View File

@ -1,17 +1,34 @@
import { Head } from "$fresh/runtime.ts";
import { PageProps, Handlers, HandlerContext } from "$fresh/server.ts";
import { HandlerContext, Handlers, PageProps } from "$fresh/server.ts";
import DocSearch from "../../islands/DocSearch.tsx";
import { Doc } from "../../src/collect.ts";
import { docCollector } from "../../src/store/doc.ts";
async function GET(req: Request, ctx: HandlerContext): Promise<Response> {
const authRequired = Deno.env.get("AUTH_REQUIRED") === "true";
if (authRequired) {
const login = ctx.state["login"];
if (!login) {
return new Response(null, {
status: 302,
headers: {
"Location": "/login",
"content-type": "text/plain",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"Access-Control-Allow-Headers":
"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
},
});
}
}
const docs = docCollector.getDocs();
return await ctx.render({ docs });
}
export const handler: Handlers = {
GET,
}
};
export default function Docs(props: PageProps<{ docs: Doc[] }>) {
const { docs } = props.data;

View File

@ -2,7 +2,10 @@ import { HandlerContext, Handlers } from "$fresh/server.ts";
import { serveFile } from "http/file_server.ts";
import { removePrefixFromPathname } from "../../util/util.ts";
export async function GET(req: Request, ctx: HandlerContext): Promise<Response> {
export async function GET(
req: Request,
ctx: HandlerContext,
): Promise<Response> {
const url = new URL(req.url);
const path = removePrefixFromPathname(decodeURI(url.pathname), "/fs");
// if auth is required, check if the user is logged in.
@ -12,19 +15,20 @@ export async function GET(req: Request, ctx: HandlerContext): Promise<Response>
const login = ctx.state["login"];
if (!login) {
return new Response(null, {
status: 401,
status: 302,
headers: {
"Location": "/login",
"content-type": "text/plain",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
}
"Access-Control-Allow-Headers":
"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
},
});
}
}
try {
const fileInfo = await Deno.stat(path);
if (fileInfo.isDirectory) {
// if index.html exists, serve it.
@ -32,32 +36,38 @@ export async function GET(req: Request, ctx: HandlerContext): Promise<Response>
const indexPath = path + "/index.html";
try {
await Deno.stat(indexPath);
const res = await serveFile(req, indexPath)
const res = await serveFile(req, indexPath);
return res;
} catch (e) {
if (e instanceof Deno.errors.NotFound) {
const list: Deno.DirEntry[] = []
const list: Deno.DirEntry[] = [];
for await (const entry of Deno.readDir(path)) {
list.push(entry);
}
return new Response(JSON.stringify(
list
), {
headers: { "content-type": "application/json",
return new Response(
JSON.stringify(
list,
),
{
headers: {
"content-type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"},
"Access-Control-Allow-Methods":
"GET,HEAD,PUT,PATCH,POST,DELETE",
"Access-Control-Allow-Headers":
"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
},
status: 200,
});
},
);
}
}
}
const res = await serveFile(req, path, {
fileInfo
fileInfo,
});
return res;
}
catch (e) {
} catch (e) {
if (e instanceof Deno.errors.NotFound) {
return new Response("Not Found", {
status: 404,
@ -67,8 +77,6 @@ export async function GET(req: Request, ctx: HandlerContext): Promise<Response>
}
}
export const handler: Handlers = {
GET
}
GET,
};

View File

@ -13,7 +13,8 @@ export default function Home() {
alt="the fresh logo: a sliced lemon dripping with juice"
/>
<p class="my-6">
This is a simple file server. It serves files from the <code>CWD</code>.
This is a simple file server. It serves files from the{" "}
<code>CWD</code>.
</p>
<a href="/dir/">Go To CWD</a> | <a href="/doc/">Doc</a>
<hr></hr>

View File

@ -15,21 +15,38 @@ export default function Login() {
class="w-32 h-32"
alt="the fresh logo: a sliced lemon dripping with juice"
/>
<form action="/api/login" method="POST" class="flex flex-col gap-2 items-stretch">
<form
action="/api/login"
method="POST"
class="flex flex-col gap-2 items-stretch"
>
<div class="flex gap-2 flex-wrap">
<div class="basis-40 flex items-center flex-1">
<label for="username" class="w-20">Username</label>
<input type="text" name="username" id="username"
class="border-b-2 focus:border-green-500 transition-colors flex-1" />
<input
type="text"
name="username"
id="username"
class="border-b-2 focus:border-green-500 transition-colors flex-1"
/>
</div>
<div class="flex items-center flex-1">
<label for="password" class="w-20">Password</label>
<input type="password" name="password" id="password"
class="border-b-2 focus:border-green-500 transition-colors flex-1" />
<input
type="password"
name="password"
id="password"
class="border-b-2 focus:border-green-500 transition-colors flex-1"
/>
</div>
</div>
<button type="submit" class="bg-gray-400 p-2 rounded
m-auto">Login</button>
<button
type="submit"
class="bg-gray-400 p-2 rounded
m-auto"
>
Login
</button>
</form>
</div>
</div>

View File

@ -10,7 +10,7 @@ export async function collectDocuments(path: string, out: string){
}
export async function watchDocuments(doc: string, options?: {
abort?: AbortSignal,
abort?: AbortSignal;
}) {
const doc_dump = await loadDocuments(doc);
const collector = new DocCollector({ summaryOnly: true, dropContent: true });
@ -44,7 +44,9 @@ search.name("search")
.command("collect", "Collect a document.")
.description("Collect documents and index documents.")
.arguments("<path:string>")
.option("-o, --out <path:string>", "out file to write the index to.", {default: "collected_docs.json"})
.option("-o, --out <path:string>", "out file to write the index to.", {
default: "collected_docs.json",
})
.action(async ({ out }, path: string) => {
console.log("collecting", path);
await collectDocuments(path, out);
@ -52,7 +54,9 @@ search.name("search")
.command("search", "Search for a document.")
.description("Search for a document.")
.arguments("<query:string>")
.option("-d, --docs <path:string>", "collected document file to search", {default: "collected_docs.json"})
.option("-d, --docs <path:string>", "collected document file to search", {
default: "collected_docs.json",
})
.action(async ({ docs }, query: string) => {
console.log("searching", query);
const doc_dump = await loadDocuments(docs);
@ -61,7 +65,9 @@ search.name("search")
})
.command("interact", "watch the index and search for a document.")
.description("watch the index and search for a document.")
.option("-d, --doc <path:string>", "doc file to read the docs from.", {default: "collected_docs.json"})
.option("-d, --doc <path:string>", "doc file to read the docs from.", {
default: "collected_docs.json",
})
.action(async ({ doc }) => {
console.log("interacting");
const index = await watchDocuments(doc);

View File

@ -1,5 +1,5 @@
//// @deno-types="https://deno.land/x/fuse@v6.4.1/dist/fuse.d.ts"
import Fuse from 'https://deno.land/x/fuse@v6.4.1/dist/fuse.esm.min.js';
import Fuse from "https://deno.land/x/fuse@v6.4.1/dist/fuse.esm.min.js";
import { Doc } from "./collect.ts";
export class Index {
@ -15,16 +15,23 @@ export class Index{
public search(query: string): Doc[] {
return this.index.search(query, { limit: 10 }).map((
result) =>
result.item as Doc);
result,
) => result.item as Doc);
}
public static createIndex(docs: Doc[]) {
const index = new Fuse(docs, {
keys: ["attributes.title", "attributes.japanese_title", "attributes.tags", "attributes.rjcode", "attributes.author", "path"],
keys: [
"attributes.title",
"attributes.japanese_title",
"attributes.tags",
"attributes.rjcode",
"attributes.author",
"path",
],
includeScore: true,
includeMatches: true,
})
});
return new Index(index);
}

View File

@ -1,4 +1,4 @@
import {join, extname, relative, basename} from "path/mod.ts";
import { basename, extname, join, relative } from "path/mod.ts";
import { readMarkdownDoc } from "./readDoc.ts";
import { Index } from "./client_search.ts";
@ -60,19 +60,19 @@ export class DocCollector {
}
if (fileList.some((entry) => entry.name === "SUMMARY.md")) {
const {content, metadata} = await readMarkdownDoc(join(path, "SUMMARY.md"));
const { content, metadata } = await readMarkdownDoc(
join(path, "SUMMARY.md"),
);
this.setDoc({
path: join(path, "SUMMARY.md"),
content: content,
attributes: metadata,
});
}
else {
} else {
for (const entry of fileList) {
if (entry.isDirectory) {
await this.walkDir(join(path, entry.name));
}
else if (entry.isFile && !this.options.summaryOnly){
} else if (entry.isFile && !this.options.summaryOnly) {
const doc = await this.readDoc(join(path, entry.name));
this.setDoc(doc);
}
@ -84,19 +84,16 @@ export class DocCollector {
const ext = extname(path);
if (ext === ".md") {
return await this.readMarkdown(path);
}
else if (ext === ".html" || ext === ".htm" || ext === ".xhtml"){
} else if (ext === ".html" || ext === ".htm" || ext === ".xhtml") {
return await this.readHTML(path);
}
else if (ext === ".txt"){
} else if (ext === ".txt") {
return await this.readText(path);
}
else {
} else {
return {
path: path,
content: "",
attributes: {}
}
attributes: {},
};
}
}
@ -106,7 +103,7 @@ export class DocCollector {
path: path,
content: content,
attributes: {},
}
};
}
public async readText(path: string): Promise<Doc> {
const content = await Deno.readTextFile(path);
@ -114,7 +111,7 @@ export class DocCollector {
path: path,
content: content,
attributes: {},
}
};
}
public async readMarkdown(path: string): Promise<Doc> {
const { content, metadata } = await readMarkdownDoc(path);
@ -123,7 +120,7 @@ export class DocCollector {
path: path,
content: content,
attributes: metadata,
}
};
}
async watchDir(path: string, {
onRemove = (_path: string) => {},
@ -131,10 +128,10 @@ export class DocCollector {
onChange = (_doc: Doc) => {},
abort = undefined,
}: {
onRemove?: (path: string) => void | Promise<void>,
onAdd?: (doc: Doc) => void | Promise<void>,
onChange?: (doc: Doc) => void | Promise<void>,
abort?: AbortSignal,
onRemove?: (path: string) => void | Promise<void>;
onAdd?: (doc: Doc) => void | Promise<void>;
onChange?: (doc: Doc) => void | Promise<void>;
abort?: AbortSignal;
}) {
const watcher = Deno.watchFs(path);
if (abort) {
@ -143,7 +140,10 @@ export class DocCollector {
});
}
for await (const event of watcher) {
if (event.kind === "access" || event.kind === "other" || event.kind === "any"){
if (
event.kind === "access" || event.kind === "other" ||
event.kind === "any"
) {
continue;
}
if (event.paths.length === 0) {
@ -156,8 +156,7 @@ export class DocCollector {
if (event.kind === "remove") {
this.doc_map.delete(relpath);
await onRemove(relpath);
}
else if (event.kind === "create" || event.kind === "modify"){
} else if (event.kind === "create" || event.kind === "modify") {
const { content, metadata } = await readMarkdownDoc(relpath);
const doc = {
path: relpath,
@ -167,8 +166,7 @@ export class DocCollector {
this.setDoc(doc);
if (event.kind === "create") {
await onAdd(doc);
}
else if (event.kind === "modify"){
} else if (event.kind === "modify") {
await onChange(doc);
}
}
@ -178,9 +176,9 @@ export class DocCollector {
}
makeIndex(options?: {
onUpdate?: (() => void) | (() => Promise<void>),
abort?: AbortSignal,
watch?: boolean,
onUpdate?: (() => void) | (() => Promise<void>);
abort?: AbortSignal;
watch?: boolean;
}) {
const opt = options ?? {};
const index = Index.createIndex(this.getDocs());
@ -192,7 +190,7 @@ export class DocCollector {
if (opt.onUpdate) {
await opt.onUpdate();
}
}
};
this.watchDir(".", {
async onAdd(_doc) {
await update();

View File

@ -1,4 +1,3 @@
const ICON_MAP: Record<string, string> = {
".pdf": "file-pdf",
".zip": "file-zip",
@ -53,7 +52,7 @@ const ICON_MAP: Record<string, string> = {
".tif": "file-image",
".webp": "file-image",
".psd": "file-image",
}
};
export function extToIcon(s: string): string {
return `/icon/${(ICON_MAP[s] ?? "file")}.svg`;

View File

@ -1,7 +1,13 @@
import {parse as parseYaml, stringify} from "https://deno.land/std@0.170.0/encoding/yaml.ts";
import {
parse as parseYaml,
stringify,
} from "https://deno.land/std@0.170.0/encoding/yaml.ts";
function trimSubstring(str: string, start: number, end: number) {
while (str[start] === ' ' || str[start] === '\t' || str[start] === '\r' || str[start] === '\n') {
while (
str[start] === " " || str[start] === "\t" || str[start] === "\r" ||
str[start] === "\n"
) {
start++;
}
return str.substring(start, end);
@ -13,24 +19,23 @@ interface Doc {
}
export function parse(content: string): Doc {
if(!content.startsWith('---')){
if (!content.startsWith("---")) {
return {
metadata: {},
content: content,
};
}
}
const index = content.indexOf('\n---', 3);
const index = content.indexOf("\n---", 3);
const meta = content.substring(3, index);
const c = trimSubstring(content, index + 4, content.length);
const metadata = parseYaml(meta) as Record<string, string | string[]>;
return {
metadata: metadata,
content: c,
}
};
}
export async function readMarkdownDoc(path: string): Promise<Doc> {
const doc = await Deno.readTextFile(path);
return parse(doc);
}

View File

@ -1,12 +1,12 @@
import { Index } from "../client_search.ts";
import { Doc, DocCollector } from "../collect.ts";
export const docCollector = new DocCollector(
{
dropContent: true,
summaryOnly: true,
});
},
);
export let docIndex: Index | undefined = undefined;
@ -15,7 +15,7 @@ export async function prepareDocs() {
if (!docPath) {
await docCollector.walkDir(".");
docIndex = docCollector.makeIndex({
watch: true
watch: true,
});
return docIndex;
}
@ -28,8 +28,7 @@ export async function prepareDocs() {
if (error instanceof Deno.errors.NotFound) {
await docCollector.walkDir(".");
await Deno.writeTextFile(docPath, JSON.stringify(docCollector.getDocs()));
}
else {
} else {
throw error;
}
}
@ -38,7 +37,7 @@ export async function prepareDocs() {
watch: true,
onUpdate: async () => {
await Deno.writeTextFile(docPath, JSON.stringify(docCollector.getDocs()));
}
},
});
return docIndex;
}

View File

@ -1,5 +1,5 @@
import {DB} from 'sqlite';
import {createSchema} from './user.ts';
import { DB } from "sqlite";
import { createSchema } from "./user.ts";
export function connectDB(): DB {
let DB_path = Deno.env.get("DB_PATH");

View File

@ -1,4 +1,4 @@
import {genSalt, hash, compare} from "bcrypt";
import { compare, genSalt, hash } from "bcrypt";
import { DB } from "sqlite";
interface User {
@ -13,8 +13,8 @@ export async function createUser(name: string, password: string){
const user: User = {
name: name,
salted_password: salted_password,
salt: salt
}
salt: salt,
};
return user;
}
@ -23,8 +23,9 @@ export async function verifyUser(user: User, password: string){
}
export async function getAllUsers(db: DB): Promise<User[]> {
const users = await db.query<[string, string, string]>
("SELECT name, salted_password, salt FROM users");
const users = await db.query<[string, string, string]>(
"SELECT name, salted_password, salt FROM users",
);
return users.map(([name, salted_password, salt]) => ({
name,
salted_password,
@ -33,8 +34,10 @@ export async function getAllUsers(db :DB): Promise<User[]>{
}
export async function getUser(db: DB, name: string): Promise<User | undefined> {
const users = await db.query<[string, string, string]>
("SELECT name, salted_password, salt FROM users WHERE name = ?", [name]);
const users = await db.query<[string, string, string]>(
"SELECT name, salted_password, salt FROM users WHERE name = ?",
[name],
);
if (users === undefined || users.length === 0) {
return undefined;
}
@ -43,17 +46,21 @@ export async function getUser(db: DB, name: string): Promise<User | undefined>{
name: user[0],
salted_password: user[1],
salt: user[2],
}
};
}
export async function addUser(db: DB, user: User) {
await db.query("INSERT INTO users (name, salted_password, salt) VALUES (?, ?, ?)",
[user.name, user.salted_password, user.salt]);
await db.query(
"INSERT INTO users (name, salted_password, salt) VALUES (?, ?, ?)",
[user.name, user.salted_password, user.salt],
);
}
export async function updateUser(db: DB, user: User) {
await db.query("UPDATE users SET salted_password = ?, salt = ? WHERE name = ?",
[user.salted_password, user.salt, user.name]);
await db.query(
"UPDATE users SET salted_password = ?, salt = ? WHERE name = ?",
[user.salted_password, user.salt, user.name],
);
}
export async function deleteUser(db: DB, name: string) {
@ -61,5 +68,7 @@ export async function deleteUser(db: DB, name: string){
}
export async function createSchema(db: DB) {
await db.query("CREATE TABLE IF NOT EXISTS users (name TEXT PRIMARY KEY, salted_password TEXT, salt TEXT)");
await db.query(
"CREATE TABLE IF NOT EXISTS users (name TEXT PRIMARY KEY, salted_password TEXT, salt TEXT)",
);
}

View File

@ -5,4 +5,4 @@ module.exports = {
extend: {},
},
plugins: [],
}
};

View File

@ -2,6 +2,7 @@
title: "hello"
tags: ["asdf","wer"]
---
# hello
- [hello](hello.md)

View File

@ -3,8 +3,12 @@ rjcode: RJ130512
title: Summary of the 13th Meeting of the Joint Committee on the Safety of Nuclear Installations
tags: ["summary", "meeting", "joint committee", "safety", "nuclear installations"]
---
# Summary of the 13th Meeting of the Joint Committee on the Safety of Nuclear Installations
## 1. Opening of the meeting
The 13th meeting of the Joint Committee on the Safety of Nuclear Installations (JCSNI) was held in Vienna on 12 May 2013. The meeting was chaired by Mr. J. M. Sánchez, Director of the Nuclear Safety Department of the Spanish Ministry of Industry, Energy and Tourism, and the Vice-Chairman of the JCSNI.
The 13th meeting of the Joint Committee on the Safety of Nuclear Installations
(JCSNI) was held in Vienna on 12 May 2013. The meeting was chaired by Mr. J. M.
Sánchez, Director of the Nuclear Safety Department of the Spanish Ministry of
Industry, Energy and Tourism, and the Vice-Chairman of the JCSNI.

View File

@ -1,4 +1,8 @@
import { Command, Input, Secret } from "https://deno.land/x/cliffy@v0.25.6/mod.ts";
import {
Command,
Input,
Secret,
} from "https://deno.land/x/cliffy@v0.25.6/mod.ts";
import { connectDB } from "./src/user/db.ts";
import * as users from "./src/user/user.ts";
@ -8,8 +12,7 @@ export const user_command = new Command()
.arguments("[username:string]")
.option("-p, --password <password:string>", "The password for the user.")
.option("-q, --quiet", "quiet output.")
.action(async ({quiet, password}
, username) => {
.action(async ({ quiet, password }, username) => {
if (username === undefined) {
username = await Input.prompt("Username: ");
}

View File

@ -1,4 +1,3 @@
export async function generateSecretKey() {
const key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-512" },
@ -14,12 +13,16 @@ export async function prepareSecretKey(){
if (key) {
const jwk = JSON.parse(key) as JsonWebKey;
{
const key = await crypto.subtle.importKey("jwk", jwk,
{ name: "HMAC", hash: "SHA-512" }, true, ["sign", "verify"]);
const key = await crypto.subtle.importKey(
"jwk",
jwk,
{ name: "HMAC", hash: "SHA-512" },
true,
["sign", "verify"],
);
return key;
}
}
else {
} else {
const key = await generateSecretKey();
const out = await crypto.subtle.exportKey("jwk", key);
Deno.env.set("SECRET_KEY", JSON.stringify(out));

View File

@ -1,4 +1,7 @@
export function removePrefixFromPathname(pathname: string, prefix: string): string {
export function removePrefixFromPathname(
pathname: string,
prefix: string,
): string {
let ret = pathname;
ret = ret.slice(prefix.length);
if (ret.startsWith("/")) {