diff --git a/README.md b/README.md
index 5eefd44..e0bb2c5 100644
--- a/README.md
+++ b/README.md
@@ -23,4 +23,4 @@ Start the project:
deno task start
```
-This will watch the project directory and restart as necessary.
\ No newline at end of file
+This will watch the project directory and restart as necessary.
diff --git a/islands/ContentRenderer.tsx b/islands/ContentRenderer.tsx
index e831976..bc12057 100644
--- a/islands/ContentRenderer.tsx
+++ b/islands/ContentRenderer.tsx
@@ -3,63 +3,67 @@ import MarkdownRenderer from "./MarkdownRenderer.tsx";
import { useEffect, useState } from "preact/hooks";
const TypeToExt = {
- "image": new Set([".png", ".jpg", ".jpeg", ".gif", ".svg"]),
- "video": new Set([".mp4", ".webm", ".ogg"]),
- "audio": new Set([".mp3", ".wav", ".flac"]),
- "md": new Set([".md"]),
- "text": new Set([".txt"]),
- "code": new Set([".json", ".js", ".ts", ".css", ".tsx", ".jsx"]),
-}
+ "image": new Set([".png", ".jpg", ".jpeg", ".gif", ".svg"]),
+ "video": new Set([".mp4", ".webm", ".ogg"]),
+ "audio": new Set([".mp3", ".wav", ".flac"]),
+ "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)) {
- if (exts.has(ext)) {
- return type;
- }
+ for (const [type, exts] of Object.entries(TypeToExt)) {
+ if (exts.has(ext)) {
+ return type;
}
- return "unknown";
+ }
+ return "unknown";
}
-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);
- }, [src]);
- switch (props.type) {
- case "text":
- return
{content}
;
- case "md":
- return ;
- case "code":
- return {content}
;
- //case "csv":
- // return ;
- default:
- return <>error: invalid type: {props.type} content: {content}>;
- }
+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);
+ }, [src]);
+ switch (props.type) {
+ case "text":
+ return {content}
;
+ case "md":
+ return ;
+ case "code":
+ return (
+ {content}
+ );
+ //case "csv":
+ // return ;
+ default:
+ return <>error: invalid type: {props.type} content: {content}>;
+ }
}
-export function RenderView(props: {src: string}) {
- const src = props.src;
- const type = extToType(extname(src));
- switch (type) {
- case "text":
- case "md":
- case "code":
- return
- //case "csv":
- // return ;
- case "image":
- return ;
- case "video":
- return ;
- case "audio":
- return ;
- default:
- return <>error: invalid type: {type} src: {src}>;
- }
+export function RenderView(props: { src: string }) {
+ const src = props.src;
+ const type = extToType(extname(src));
+ switch (type) {
+ case "text":
+ case "md":
+ case "code":
+ return ;
+ //case "csv":
+ // return ;
+ case "image":
+ return ;
+ case "video":
+ return ;
+ case "audio":
+ return ;
+ default:
+ return <>error: invalid type: {type} src: {src}>;
+ }
}
export default RenderView;
diff --git a/islands/DirList.tsx b/islands/DirList.tsx
index a4a8496..181d64a 100644
--- a/islands/DirList.tsx
+++ b/islands/DirList.tsx
@@ -1,94 +1,108 @@
-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";
import UpList from "./UpList.tsx";
-import {extToIcon} from "../src/media.ts";
+import { extToIcon } from "../src/media.ts";
import { encodePath } from "../util/util.ts";
-function ListItem(props:{
- href: string,
- icon: string,
- children: ComponentChild
- }) {
- return (
-
-
- {props.children}
-
- );
+function ListItem(props: {
+ href: string;
+ icon: string;
+ children: ComponentChild;
+}) {
+ return (
+
+
+
+ {props.children}
+
+
+ );
}
-export interface EntryInfo{
- name: string;
- isFile: boolean;
- isDirectory: boolean;
- isSymlink: boolean;
- size: number;
- lastModified?: Date;
+export interface EntryInfo {
+ name: string;
+ isFile: boolean;
+ isDirectory: boolean;
+ isSymlink: boolean;
+ size: number;
+ lastModified?: Date;
}
interface DirListProps {
- path: string;
- files: EntryInfo[];
+ path: string;
+ files: EntryInfo[];
}
export function DirList(props: DirListProps) {
- const data = props;
- const [files, setFiles] = useState(data.files);
+ const data = props;
+ const [files, setFiles] = useState(data.files);
- return (
-
-
- -
-
-
-
- ...
- {files.map((file) => (
- {file.name}
- ))}
-
-
)
- function sortDir() {
- // sort by directory first then by index
- const sorted_files = files.map((x,i)=>
- ([x,i] as [EntryInfo, number]))
- .sort(
- ([a, ai],[b,bi]) => {
- if (a.isDirectory && !b.isDirectory) {
- return -1;
- } else if (!a.isDirectory && b.isDirectory) {
- return 1;
- } 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]))
- .sort(
- ([a, ai],[b,bi]) => {
- const ret = a.name.localeCompare(b.name);
- if (ret === 0) {
- return ai - bi;
- } else {
- return ret;
- }
- });
- setFiles(sorted_files.map(([x,_])=>x));
- }
+ return (
+
+
+
+ -
+
+
+
+
+ ...
+
+ {files.map((file) => (
+
+ {file.name}
+
+ ))}
+
+
+ );
+ function sortDir() {
+ // sort by directory first then by index
+ const sorted_files = files.map((x, i) => ([x, i] as [EntryInfo, number]))
+ .sort(
+ ([a, ai], [b, bi]) => {
+ if (a.isDirectory && !b.isDirectory) {
+ return -1;
+ } else if (!a.isDirectory && b.isDirectory) {
+ return 1;
+ } 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]))
+ .sort(
+ ([a, ai], [b, bi]) => {
+ const ret = a.name.localeCompare(b.name);
+ if (ret === 0) {
+ return ai - bi;
+ } else {
+ return ret;
+ }
+ },
+ );
+ setFiles(sorted_files.map(([x, _]) => x));
+ }
}
-export default DirList;
\ No newline at end of file
+export default DirList;
diff --git a/islands/DocSearch.tsx b/islands/DocSearch.tsx
index b94e6af..ca9640f 100644
--- a/islands/DocSearch.tsx
+++ b/islands/DocSearch.tsx
@@ -3,43 +3,52 @@ import { useEffect, useState } from "preact/hooks";
import { Index } from "../src/client_search.ts";
import { encodePath } from "../util/util.ts";
-function SearchBar(props:{
- search?: string;
- onSearch?: (search: string) => void;
-}){
- const [search, setSearch] = useState(props.search ?? "");
- return (
-
- {}}
- onKeyUp={(event)=>{
- if (event.currentTarget.value === search) return;
- setSearch(event.currentTarget.value);
- props.onSearch?.(event.currentTarget.value);
- }}>{search}
-
- )
+function SearchBar(props: {
+ search?: string;
+ onSearch?: (search: string) => void;
+}) {
+ const [search, setSearch] = useState(props.search ?? "");
+ return (
+
+ {}}
+ onKeyUp={(event) => {
+ if (event.currentTarget.value === search) return;
+ setSearch(event.currentTarget.value);
+ props.onSearch?.(event.currentTarget.value);
+ }}
+ >
+ {search}
+
+
+ );
}
export default function DocSearch(props: {
- docs: Doc[]
-}){
- const [docs, setDocs] = useState(props.docs);
- const index = Index.createIndex(props.docs);
+ docs: Doc[];
+}) {
+ const [docs, setDocs] = useState(props.docs);
+ const index = Index.createIndex(props.docs);
- return (
- <>
- {
- setDocs(index.search(s));
- }}>
- Doc
-
- >
- )
-}
\ No newline at end of file
+ return (
+ <>
+ {
+ setDocs(index.search(s));
+ }}
+ >
+
+ Doc
+
+ >
+ );
+}
diff --git a/islands/FileViewer.tsx b/islands/FileViewer.tsx
index 35c3d70..1196d6e 100644
--- a/islands/FileViewer.tsx
+++ b/islands/FileViewer.tsx
@@ -4,15 +4,15 @@ import { extname } from "path/mod.ts";
import { encodePath } from "../util/util.ts";
export default function FileViewer(props: { path: string }) {
- const { path } = props;
- const srcPath = `/fs/${encodePath(path)}`;
- return (
-
- )
-}
\ No newline at end of file
+ const { path } = props;
+ const srcPath = `/fs/${encodePath(path)}`;
+ return (
+
+ );
+}
diff --git a/islands/MarkdownRenderer.tsx b/islands/MarkdownRenderer.tsx
index efbcc2b..877ab67 100644
--- a/islands/MarkdownRenderer.tsx
+++ b/islands/MarkdownRenderer.tsx
@@ -1,13 +1,18 @@
import { marked } from "https://deno.land/x/marked@1.0.1/mod.ts";
export function MarkdownRenderer(props: { text: string | undefined }) {
- let text = props.text;
- if (text === undefined) {
- text = "";
- }
- const index = text.indexOf('\n---', 3);
- const c = text.slice(index + 4, text.length);
- return ;
+ let text = props.text;
+ if (text === undefined) {
+ text = "";
+ }
+ const index = text.indexOf("\n---", 3);
+ const c = text.slice(index + 4, text.length);
+ return (
+
+ );
}
export default MarkdownRenderer;
diff --git a/islands/UpList.tsx b/islands/UpList.tsx
index 8dd5a76..218745b 100644
--- a/islands/UpList.tsx
+++ b/islands/UpList.tsx
@@ -1,40 +1,44 @@
-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("/");
- let current = ".";
- const stairs = [];
- for (const up of uplist){
- current = join(current, up);
- stairs.push([current, up]);
- }
- return stairs;
+function stairs(path: string) {
+ if (path === ".") return [];
+ const uplist = path.split("/");
+ let current = ".";
+ const stairs = [];
+ for (const up of uplist) {
+ current = join(current, up);
+ stairs.push([current, up]);
+ }
+ return stairs;
}
-export default function UpList(props:{path: string}) {
- const data = props;
- const uplist = stairs(data.path);
-
- return ()
-}
\ No newline at end of file
+export default function UpList(props: { path: string }) {
+ const data = props;
+ const uplist = stairs(data.path);
+
+ return (
+
+ );
+}
diff --git a/keyout.ts b/keyout.ts
index 93b6281..c4eb8dd 100644
--- a/keyout.ts
+++ b/keyout.ts
@@ -4,24 +4,27 @@ 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 ", "The file to output the key to. (default: stdout)")
-.option("--dotenv", "Output the key in dotenv format.")
-.action(async ({file, dotenv}) => {
+ .description("Output the secret key.")
+ .option(
+ "-f, --file ",
+ "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();
const keyout = await crypto.subtle.exportKey("jwk", key);
let out = JSON.stringify(keyout);
- if (dotenv){
- out = [`SECRET_KEY='${out}'`].join("\n");
+ if (dotenv) {
+ out = [`SECRET_KEY='${out}'`].join("\n");
}
- if (file){
- await Deno.writeTextFile(file, out);
+ if (file) {
+ await Deno.writeTextFile(file, out);
} else {
- console.log(out);
+ console.log(out);
}
-});
+ });
-if ( import.meta.main ){
- await key_out_cmd.parse(Deno.args);
-}
\ No newline at end of file
+if (import.meta.main) {
+ await key_out_cmd.parse(Deno.args);
+}
diff --git a/main.ts b/main.ts
index 32ad6ad..421eb5d 100644
--- a/main.ts
+++ b/main.ts
@@ -4,14 +4,19 @@
///
///
-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,71 +25,80 @@ 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",
- // deno-lint-ignore require-await
- render(ctx): PluginRenderResult{
- ctx.render();
- return {
- styles: [{
- cssText: github_markdown,
- }]
- }
- }
+ name: "css plugin",
+ // deno-lint-ignore require-await
+ render(ctx): PluginRenderResult {
+ ctx.render();
+ return {
+ styles: [{
+ cssText: github_markdown,
+ }],
+ };
+ },
};
await prepareSecretKey();
-async function startServer(manifest: Manifest, options: StartOptions = {}){
- const ctx = await ServerContext.fromManifest(manifest, options);
-
- options.port ??= 8000;
- if (options.experimentalDenoServe === true) {
- // @ts-ignore as `Deno.serve` is still unstable.
- await Deno.serve(ctx.handler() as Deno.ServeHandler, options);
- } else {
- await serve(ctx.handler(), options);
- }
+async function startServer(manifest: Manifest, options: StartOptions = {}) {
+ const ctx = await ServerContext.fromManifest(manifest, options);
+
+ options.port ??= 8000;
+ if (options.experimentalDenoServe === true) {
+ // @ts-ignore as `Deno.serve` is still unstable.
+ await Deno.serve(ctx.handler() as Deno.ServeHandler, options);
+ } else {
+ await serve(ctx.handler(), options);
+ }
}
-async function start({port = 8000, hostname = "localhost"}: {port?: number, hostname?: string} = {}){
- await startServer(manifest, { plugins: [twindPlugin(twindConfig), CSSPlugin],
- port: port,
- hostname: hostname,
- });
+async function start(
+ { port = 8000, hostname = "localhost" }: {
+ port?: number;
+ hostname?: string;
+ } = {},
+) {
+ await startServer(manifest, {
+ plugins: [twindPlugin(twindConfig), CSSPlugin],
+ port: port,
+ hostname: hostname,
+ });
}
-if (import.meta.main){
- const cmd = new Command();
- cmd.name("fs-server")
- .description("A simple file server that supports search, upload, and download.")
+if (import.meta.main) {
+ const cmd = new Command();
+ cmd.name("fs-server")
+ .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 ", "The port to listen on.",
- { default: 8000 })
+ .option("-p, --port ", "The port to listen on.", {
+ default: 8000,
+ })
.option("--auth", "Enable authentication.")
.arguments("[hostname:string]")
- .action(async ({debug, port, auth }, hostname) => {
- hostname ??= "localhost";
- if (auth){
- Deno.env.set("AUTH_REQUIRED", "true");
- }
- if (debug){
- console.log("Debug mode enabled.");
- }
- await start({
- port: port,
- hostname: hostname,
- });
- }
- )
+ .action(async ({ debug, port, auth }, hostname) => {
+ hostname ??= "localhost";
+ if (auth) {
+ Deno.env.set("AUTH_REQUIRED", "true");
+ }
+ if (debug) {
+ console.log("Debug mode enabled.");
+ }
+ await start({
+ port: port,
+ hostname: hostname,
+ });
+ })
.command("user", user_command)
.command("keyout", key_out_cmd);
- await cmd.parse(Deno.args);
+ await cmd.parse(Deno.args);
+} else {
+ await start();
}
-else {
- await start();
-}
\ No newline at end of file
diff --git a/routes/_middleware.ts b/routes/_middleware.ts
index ef75ac3..8184a8f 100644
--- a/routes/_middleware.ts
+++ b/routes/_middleware.ts
@@ -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>) => {
- const cookies = getCookies(req.headers);
- const jwt = cookies["auth"];
- try{
- const payload = await verify(jwt, secret_key);
- ctx.state["login"] = payload;
- }
- catch (e){
- ctx.state["login"] = null;
- }
- return await ctx.next();
-}
\ No newline at end of file
+export const handler = async (
+ req: Request,
+ ctx: MiddlewareHandlerContext>,
+) => {
+ const cookies = getCookies(req.headers);
+ const jwt = cookies["auth"];
+ try {
+ const payload = await verify(jwt, secret_key);
+ ctx.state["login"] = payload;
+ } catch (e) {
+ ctx.state["login"] = null;
+ }
+ return await ctx.next();
+};
diff --git a/routes/api/login.ts b/routes/api/login.ts
index ef06151..8a8a0a6 100644
--- a/routes/api/login.ts
+++ b/routes/api/login.ts
@@ -6,44 +6,44 @@ 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();
+const SECRET_KEY = await prepareSecretKey();
async function POST(req: Request, ctx: HandlerContext): Promise {
- const url = new URL(req.url);
- const form = await req.formData();
- const username = form.get("username");
- const password = form.get("password");
- if (username && password){
- const DB = connectDB();
- const user = await getUser(DB, username.toString());
- if (user){
- if (await verifyUser(user, password.toString())){
- const headers = new Headers();
- const jwt = await createJWT({alg:"HS512", typ: "JWT"},{
- username: user.name
- }, SECRET_KEY);
- setCookie(headers, {
- name: "auth",
- value: jwt,
- httpOnly: true,
- sameSite: "Strict",
- maxAge: 60 * 60 * 24 * 7,
- domain: url.hostname,
- path: "/",
- secure: url.protocol === "https:"
- });
+ const url = new URL(req.url);
+ const form = await req.formData();
+ const username = form.get("username");
+ const password = form.get("password");
+ if (username && password) {
+ const DB = connectDB();
+ const user = await getUser(DB, username.toString());
+ if (user) {
+ if (await verifyUser(user, password.toString())) {
+ const headers = new Headers();
+ const jwt = await createJWT({ alg: "HS512", typ: "JWT" }, {
+ username: user.name,
+ }, SECRET_KEY);
+ setCookie(headers, {
+ name: "auth",
+ value: jwt,
+ httpOnly: true,
+ sameSite: "Strict",
+ maxAge: 60 * 60 * 24 * 7,
+ domain: url.hostname,
+ path: "/",
+ secure: url.protocol === "https:",
+ });
- headers.set("Location", "/");
- return new Response(null,{
- status: Status.SeeOther, // See Other
- headers: headers
- });
- }
- }
+ headers.set("Location", "/");
+ return new Response(null, {
+ status: Status.SeeOther, // See Other
+ headers: headers,
+ });
+ }
}
+ }
- return new Response(`
+ return new Response(
+ `
Login Failed
Login Failed
@@ -52,14 +52,16 @@ async function POST(req: Request, ctx: HandlerContext): Promise {
document.location.href = "/login";
-`, {
- headers:{
- "Content-Type": "text/html"
- },
- status: Status.Forbidden,
- });
+`,
+ {
+ headers: {
+ "Content-Type": "text/html",
+ },
+ status: Status.Forbidden,
+ },
+ );
}
export const handler = {
- POST
-};
\ No newline at end of file
+ POST,
+};
diff --git a/routes/api/logout.ts b/routes/api/logout.ts
index 0df7450..a6db41f 100644
--- a/routes/api/logout.ts
+++ b/routes/api/logout.ts
@@ -14,4 +14,4 @@ export const handler: Handlers = {
headers,
});
},
-};
\ No newline at end of file
+};
diff --git a/routes/dir/[...path].tsx b/routes/dir/[...path].tsx
index c8d8a4b..ac0e5c4 100644
--- a/routes/dir/[...path].tsx
+++ b/routes/dir/[...path].tsx
@@ -1,6 +1,6 @@
-import { PageProps, Handlers, HandlerContext } from "$fresh/server.ts";
-import { Head, asset } from "$fresh/runtime.ts";
-import {removePrefixFromPathname} from "../../util/util.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";
import FileViewer from "../../islands/FileViewer.tsx";
@@ -10,74 +10,78 @@ type DirProps = {
path: string;
stat: Deno.FileInfo;
files: EntryInfo[];
-}
+};
type FileProps = {
type: "file";
path: string;
stat: Deno.FileInfo;
-}
+};
type DirOrFileProps = DirProps | FileProps;
-async function GET(req: Request, ctx: HandlerContext): Promise{
+async function GET(req: Request, ctx: HandlerContext): Promise {
const authRequired = Deno.env.get("AUTH_REQUIRED") === "true";
- if (authRequired) {
- const login = ctx.state["login"];
- if (!login) {
- return new Response(null, {
- status: 401,
- headers: {
- "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"
- }
- });
- }
+ 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 url = new URL(req.url);
const path = removePrefixFromPathname(decodeURI(url.pathname), "/dir");
const stat = await Deno.stat(path);
- if (stat.isDirectory){
+ if (stat.isDirectory) {
const filesIter = await Deno.readDir(path);
- const files: EntryInfo[] = []
- for await (const file of filesIter){
+ 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{
+ stat,
+ 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) {
const data = props.data;
- return (<>
-
- Simple file server : {data.path}
-
-
- {data.type === "dir" ? () :
- ()}
-
- >
+ return (
+ <>
+
+ Simple file server : {data.path}
+
+
+ {data.type === "dir"
+ ?
+ : }
+
+ >
);
}
diff --git a/routes/doc/index.tsx b/routes/doc/index.tsx
index 4435b08..256406e 100644
--- a/routes/doc/index.tsx
+++ b/routes/doc/index.tsx
@@ -1,20 +1,37 @@
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 {
- const docs = docCollector.getDocs();
- return await ctx.render({docs});
+ 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,
-}
+ GET,
+};
-export default function Docs(props: PageProps<{docs:Doc[]}>) {
- const {docs} = props.data;
+export default function Docs(props: PageProps<{ docs: Doc[] }>) {
+ const { docs } = props.data;
return (
<>
@@ -25,4 +42,4 @@ export default function Docs(props: PageProps<{docs:Doc[]}>) {
>
);
-}
\ No newline at end of file
+}
diff --git a/routes/fs/[...path].ts b/routes/fs/[...path].ts
index fdd91c7..941b9ac 100644
--- a/routes/fs/[...path].ts
+++ b/routes/fs/[...path].ts
@@ -1,74 +1,82 @@
import { HandlerContext, Handlers } from "$fresh/server.ts";
import { serveFile } from "http/file_server.ts";
-import {removePrefixFromPathname} from "../../util/util.ts";
+import { removePrefixFromPathname } from "../../util/util.ts";
-export async function GET(req: Request, ctx: HandlerContext): Promise {
- const url = new URL(req.url);
- const path = removePrefixFromPathname(decodeURI(url.pathname), "/fs");
- // if auth is required, check if the user is logged in.
- // if not, return a 401.
- const authRequired = Deno.env.get("AUTH_REQUIRED") === "true";
- if (authRequired) {
- const login = ctx.state["login"];
- if (!login) {
- return new Response(null, {
- status: 401,
- headers: {
- "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"
- }
- });
- }
+export async function GET(
+ req: Request,
+ ctx: HandlerContext,
+): Promise {
+ const url = new URL(req.url);
+ const path = removePrefixFromPathname(decodeURI(url.pathname), "/fs");
+ // if auth is required, check if the user is logged in.
+ // if not, return a 401.
+ 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",
+ },
+ });
}
+ }
- try{
-
- const fileInfo = await Deno.stat(path);
- if (fileInfo.isDirectory) {
- // if index.html exists, serve it.
- // otherwise, serve a directory listing.
- const indexPath = path + "/index.html";
- try {
- await Deno.stat(indexPath);
- const res = await serveFile(req, indexPath)
- return res;
- } catch (e) {
- if (e instanceof Deno.errors.NotFound) {
- 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",
- "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"},
- status: 200,
- });
- }
- }
- }
- const res = await serveFile(req, path, {
- fileInfo
- });
+ try {
+ const fileInfo = await Deno.stat(path);
+ if (fileInfo.isDirectory) {
+ // if index.html exists, serve it.
+ // otherwise, serve a directory listing.
+ const indexPath = path + "/index.html";
+ try {
+ await Deno.stat(indexPath);
+ const res = await serveFile(req, indexPath);
return res;
- }
- catch (e) {
+ } catch (e) {
if (e instanceof Deno.errors.NotFound) {
- return new Response("Not Found", {
- status: 404,
- });
+ 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",
+ "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",
+ },
+ status: 200,
+ },
+ );
}
- throw e;
+ }
}
+ const res = await serveFile(req, path, {
+ fileInfo,
+ });
+ return res;
+ } catch (e) {
+ if (e instanceof Deno.errors.NotFound) {
+ return new Response("Not Found", {
+ status: 404,
+ });
+ }
+ throw e;
+ }
}
-
-
export const handler: Handlers = {
- GET
-}
+ GET,
+};
diff --git a/routes/index.tsx b/routes/index.tsx
index b280231..5b0506d 100644
--- a/routes/index.tsx
+++ b/routes/index.tsx
@@ -13,7 +13,8 @@ export default function Home() {
alt="the fresh logo: a sliced lemon dripping with juice"
/>
- This is a simple file server. It serves files from the CWD
.
+ This is a simple file server. It serves files from the{" "}
+ CWD
.
Go To CWD | Doc
diff --git a/routes/login.tsx b/routes/login.tsx
index a0a8c04..d0d181b 100644
--- a/routes/login.tsx
+++ b/routes/login.tsx
@@ -1,38 +1,55 @@
import { Head } from "$fresh/runtime.ts";
export default function Login() {
- return (
- <>
-
- Simple file server - Login
-
-
-
+
-
-
-
+
+
+
+
+ >
+ );
}
diff --git a/search.ts b/search.ts
index 0197536..c576163 100644
--- a/search.ts
+++ b/search.ts
@@ -2,84 +2,90 @@ import { Command } from "https://deno.land/x/cliffy@v0.25.6/mod.ts";
import { Index } from "./src/client_search.ts";
import { Doc, DocCollector, loadDocuments } from "./src/collect.ts";
-export async function collectDocuments(path: string, out: string){
- const collector = new DocCollector({ summaryOnly: true, dropContent: true });
- await collector.walkDir(path);
- const docs = collector.getDocs();
- await Deno.writeTextFile(out, JSON.stringify(docs));
+export async function collectDocuments(path: string, out: string) {
+ const collector = new DocCollector({ summaryOnly: true, dropContent: true });
+ await collector.walkDir(path);
+ const docs = collector.getDocs();
+ await Deno.writeTextFile(out, JSON.stringify(docs));
}
-export async function watchDocuments(doc: string , options?:{
- abort?: AbortSignal,
-}){
- const doc_dump = await loadDocuments(doc);
- const collector = new DocCollector({ summaryOnly: true, dropContent: true });
- collector.setDocs(doc_dump);
- const index = Index.createIndex(doc_dump);
- async function update(){
- index.setDocs(collector.getDocs());
- await Deno.writeTextFile(doc, JSON.stringify(collector.getDocs()));
- }
- collector.watchDir(".", {
- async onAdd(doc){
- console.log("onAdd :", doc.path);
- await update();
- },
- async onRemove(path){
- console.log("onRemove :", path);
- await update();
- },
- async onChange(doc){
- console.log("onModify :", doc.path);
- await update();
- },
- abort: options?.abort,
- });
- return index;
+export async function watchDocuments(doc: string, options?: {
+ abort?: AbortSignal;
+}) {
+ const doc_dump = await loadDocuments(doc);
+ const collector = new DocCollector({ summaryOnly: true, dropContent: true });
+ collector.setDocs(doc_dump);
+ const index = Index.createIndex(doc_dump);
+ async function update() {
+ index.setDocs(collector.getDocs());
+ await Deno.writeTextFile(doc, JSON.stringify(collector.getDocs()));
+ }
+ collector.watchDir(".", {
+ async onAdd(doc) {
+ console.log("onAdd :", doc.path);
+ await update();
+ },
+ async onRemove(path) {
+ console.log("onRemove :", path);
+ await update();
+ },
+ async onChange(doc) {
+ console.log("onModify :", doc.path);
+ await update();
+ },
+ abort: options?.abort,
+ });
+ return index;
}
export const search = new Command();
search.name("search")
-.description("Search for a document.")
-.command("collect", "Collect a document.")
- .description("Collect documents and index documents.")
- .arguments("")
- .option("-o, --out ", "out file to write the index to.", {default: "collected_docs.json"})
- .action(async ({out} ,path: string) => {
- console.log("collecting", path);
- await collectDocuments(path, out);
- })
-.command("search", "Search for a document.")
- .description("Search for a document.")
- .arguments("")
- .option("-d, --docs ", "collected document file to search", {default: "collected_docs.json"})
- .action(async ({docs}, query: string) => {
- console.log("searching", query);
- const doc_dump = await loadDocuments(docs);
- const results = Index.createIndex(doc_dump).search(query);
- console.log(results);
- })
-.command("interact", "watch the index and search for a document.")
- .description("watch the index and search for a document.")
- .option("-d, --doc ", "doc file to read the docs from.", {default: "collected_docs.json"})
- .action(async ({doc}) => {
- console.log("interacting");
- const index = await watchDocuments(doc);
+ .description("Search for a document.")
+ .command("collect", "Collect a document.")
+ .description("Collect documents and index documents.")
+ .arguments("")
+ .option("-o, --out ", "out file to write the index to.", {
+ default: "collected_docs.json",
+ })
+ .action(async ({ out }, path: string) => {
+ console.log("collecting", path);
+ await collectDocuments(path, out);
+ })
+ .command("search", "Search for a document.")
+ .description("Search for a document.")
+ .arguments("")
+ .option("-d, --docs ", "collected document file to search", {
+ default: "collected_docs.json",
+ })
+ .action(async ({ docs }, query: string) => {
+ console.log("searching", query);
+ const doc_dump = await loadDocuments(docs);
+ const results = Index.createIndex(doc_dump).search(query);
+ console.log(results);
+ })
+ .command("interact", "watch the index and search for a document.")
+ .description("watch the index and search for a document.")
+ .option("-d, --doc ", "doc file to read the docs from.", {
+ default: "collected_docs.json",
+ })
+ .action(async ({ doc }) => {
+ console.log("interacting");
+ const index = await watchDocuments(doc);
- // interact
- while (true){
- const query = await prompt("query: ");
- if (query === null){
- continue;
- }
- if (query === ":exit"){
- break;
- }
- const results = index.search(query);
- console.log(results);
- }
- });
+ // interact
+ while (true) {
+ const query = await prompt("query: ");
+ if (query === null) {
+ continue;
+ }
+ if (query === ":exit") {
+ break;
+ }
+ const results = index.search(query);
+ console.log(results);
+ }
+ });
-if ( import.meta.main ){
- await search.parse(Deno.args);
-}
\ No newline at end of file
+if (import.meta.main) {
+ await search.parse(Deno.args);
+}
diff --git a/src/client_search.ts b/src/client_search.ts
index 7a43fe1..92b8442 100644
--- a/src/client_search.ts
+++ b/src/client_search.ts
@@ -1,31 +1,38 @@
//// @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{
- private index: Fuse;
+export class Index {
+ private index: Fuse;
- private constructor(index: Fuse){
- this.index = index;
- }
+ private constructor(index: Fuse) {
+ this.index = index;
+ }
- public setDocs(docs: Doc[]){
- this.index.setCollection(docs);
- }
+ public setDocs(docs: Doc[]) {
+ this.index.setCollection(docs);
+ }
- public search(query: string): Doc[]{
- return this.index.search(query, {limit: 10}).map((
- result) =>
- result.item as Doc);
- }
+ public search(query: string): Doc[] {
+ return this.index.search(query, { limit: 10 }).map((
+ 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"],
- includeScore: true,
- includeMatches: true,
- })
+ public static createIndex(docs: Doc[]) {
+ const index = new Fuse(docs, {
+ keys: [
+ "attributes.title",
+ "attributes.japanese_title",
+ "attributes.tags",
+ "attributes.rjcode",
+ "attributes.author",
+ "path",
+ ],
+ includeScore: true,
+ includeMatches: true,
+ });
- return new Index(index);
- }
+ return new Index(index);
+ }
}
diff --git a/src/collect.ts b/src/collect.ts
index 4e4f0e9..3bc42be 100644
--- a/src/collect.ts
+++ b/src/collect.ts
@@ -1,210 +1,208 @@
-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";
-export interface Doc{
- path: string;
- content: string;
- attributes: {
- title?: string;
- japanese_title?: string;
- tags?: string[];
- rjcode?: string;
- author?: string;
- };
+export interface Doc {
+ path: string;
+ content: string;
+ attributes: {
+ title?: string;
+ japanese_title?: string;
+ tags?: string[];
+ rjcode?: string;
+ author?: string;
+ };
}
-export async function loadDocuments(path: string): Promise{
- const doc_json = await Deno.readTextFile(path);
- return JSON.parse(doc_json) as Doc[];
+export async function loadDocuments(path: string): Promise {
+ const doc_json = await Deno.readTextFile(path);
+ return JSON.parse(doc_json) as Doc[];
}
-export interface DocCollectorOptions{
- summaryOnly?: boolean;
- dropContent?: boolean;
+export interface DocCollectorOptions {
+ summaryOnly?: boolean;
+ dropContent?: boolean;
}
export class DocCollector {
- private doc_map: Map;
- private options: DocCollectorOptions;
+ private doc_map: Map;
+ private options: DocCollectorOptions;
- constructor(options: DocCollectorOptions = {}){
- this.doc_map = new Map();
- this.options = options;
+ constructor(options: DocCollectorOptions = {}) {
+ this.doc_map = new Map();
+ this.options = options;
+ }
+
+ public getDocs(): Doc[] {
+ return [...this.doc_map.values()];
+ }
+
+ public setDoc(doc: Doc) {
+ if (this.options.dropContent) {
+ doc.content = "";
+ }
+ this.doc_map.set(doc.path, doc);
+ }
+ public setDocs(docs: Doc[]) {
+ for (const doc of docs) {
+ this.setDoc(doc);
+ }
+ }
+ public removeDoc(path: string) {
+ this.doc_map.delete(path);
+ }
+
+ public async walkDir(path: string) {
+ const dir = Deno.readDir(path);
+ const fileList = [];
+ for await (const entry of dir) {
+ fileList.push(entry);
}
- public getDocs(): Doc[]{
- return [...this.doc_map.values()];
- }
-
- public setDoc(doc: Doc){
- if (this.options.dropContent){
- doc.content = "";
+ if (fileList.some((entry) => entry.name === "SUMMARY.md")) {
+ const { content, metadata } = await readMarkdownDoc(
+ join(path, "SUMMARY.md"),
+ );
+ this.setDoc({
+ path: join(path, "SUMMARY.md"),
+ content: content,
+ attributes: metadata,
+ });
+ } else {
+ for (const entry of fileList) {
+ if (entry.isDirectory) {
+ await this.walkDir(join(path, entry.name));
+ } else if (entry.isFile && !this.options.summaryOnly) {
+ const doc = await this.readDoc(join(path, entry.name));
+ this.setDoc(doc);
}
- this.doc_map.set(doc.path,doc);
+ }
}
- public setDocs(docs: Doc[]){
- for (const doc of docs){
+ }
+
+ public async readDoc(path: string): Promise {
+ const ext = extname(path);
+ if (ext === ".md") {
+ return await this.readMarkdown(path);
+ } else if (ext === ".html" || ext === ".htm" || ext === ".xhtml") {
+ return await this.readHTML(path);
+ } else if (ext === ".txt") {
+ return await this.readText(path);
+ } else {
+ return {
+ path: path,
+ content: "",
+ attributes: {},
+ };
+ }
+ }
+
+ public async readHTML(path: string): Promise {
+ const content = await Deno.readTextFile(path);
+ return {
+ path: path,
+ content: content,
+ attributes: {},
+ };
+ }
+ public async readText(path: string): Promise {
+ const content = await Deno.readTextFile(path);
+ return {
+ path: path,
+ content: content,
+ attributes: {},
+ };
+ }
+ public async readMarkdown(path: string): Promise {
+ const { content, metadata } = await readMarkdownDoc(path);
+
+ return {
+ path: path,
+ content: content,
+ attributes: metadata,
+ };
+ }
+ async watchDir(path: string, {
+ onRemove = (_path: string) => {},
+ onAdd = (_doc: Doc) => {},
+ onChange = (_doc: Doc) => {},
+ abort = undefined,
+ }: {
+ onRemove?: (path: string) => void | Promise;
+ onAdd?: (doc: Doc) => void | Promise;
+ onChange?: (doc: Doc) => void | Promise;
+ abort?: AbortSignal;
+ }) {
+ const watcher = Deno.watchFs(path);
+ if (abort) {
+ abort.addEventListener("abort", () => {
+ watcher.close();
+ });
+ }
+ for await (const event of watcher) {
+ if (
+ event.kind === "access" || event.kind === "other" ||
+ event.kind === "any"
+ ) {
+ continue;
+ }
+ if (event.paths.length === 0) {
+ continue;
+ }
+ for (const path of event.paths) {
+ const relpath = relative(Deno.cwd(), path);
+ const filename = basename(relpath);
+ if (filename === "SUMMARY.md") {
+ if (event.kind === "remove") {
+ this.doc_map.delete(relpath);
+ await onRemove(relpath);
+ } else if (event.kind === "create" || event.kind === "modify") {
+ const { content, metadata } = await readMarkdownDoc(relpath);
+ const doc = {
+ path: relpath,
+ content: content,
+ attributes: metadata,
+ };
this.setDoc(doc);
- }
- }
- public removeDoc(path: string){
- this.doc_map.delete(path);
- }
-
- public async walkDir(path: string){
- const dir = Deno.readDir(path);
- const fileList = [];
- for await (const entry of dir){
- fileList.push(entry);
- }
-
- if (fileList.some((entry) => entry.name === "SUMMARY.md")){
- const {content, metadata} = await readMarkdownDoc(join(path, "SUMMARY.md"));
- this.setDoc({
- path: join(path, "SUMMARY.md"),
- content: content,
- attributes: metadata,
- });
- }
- else {
- for (const entry of fileList){
- if (entry.isDirectory){
- await this.walkDir(join(path, entry.name));
- }
- else if (entry.isFile && !this.options.summaryOnly){
- const doc = await this.readDoc(join(path, entry.name));
- this.setDoc(doc);
- }
+ if (event.kind === "create") {
+ await onAdd(doc);
+ } else if (event.kind === "modify") {
+ await onChange(doc);
}
+ }
}
+ }
}
+ }
- public async readDoc(path: string): Promise{
- const ext = extname(path);
- if (ext === ".md"){
- return await this.readMarkdown(path);
- }
- else if (ext === ".html" || ext === ".htm" || ext === ".xhtml"){
- return await this.readHTML(path);
- }
- else if (ext === ".txt"){
- return await this.readText(path);
- }
- else {
- return {
- path: path,
- content: "",
- attributes: {}
- }
- }
+ makeIndex(options?: {
+ onUpdate?: (() => void) | (() => Promise);
+ abort?: AbortSignal;
+ watch?: boolean;
+ }) {
+ const opt = options ?? {};
+ const index = Index.createIndex(this.getDocs());
+ if (!opt.watch) {
+ return index;
}
-
- public async readHTML(path: string): Promise{
- const content = await Deno.readTextFile(path);
- return {
- path: path,
- content: content,
- attributes: {},
- }
- }
- public async readText(path: string): Promise{
- const content = await Deno.readTextFile(path);
- return {
- path: path,
- content: content,
- attributes: {},
- }
- }
- public async readMarkdown(path: string): Promise{
- const {content, metadata} = await readMarkdownDoc(path);
-
- return {
- path: path,
- content: content,
- attributes: metadata,
- }
- }
- async watchDir(path: string, {
- onRemove = (_path: string) => {},
- onAdd = (_doc: Doc) => {},
- onChange = (_doc: Doc) => {},
- abort = undefined,
- }:{
- onRemove?: (path: string) => void | Promise,
- onAdd?: (doc: Doc) => void | Promise,
- onChange?: (doc: Doc) => void | Promise,
- abort?: AbortSignal,
- }){
- const watcher = Deno.watchFs(path);
- if (abort){
- abort.addEventListener("abort", () => {
- watcher.close();
- });
- }
- for await (const event of watcher){
- if (event.kind === "access" || event.kind === "other" || event.kind === "any"){
- continue;
- }
- if (event.paths.length === 0){
- continue;
- }
- for (const path of event.paths){
- const relpath = relative(Deno.cwd(), path);
- const filename = basename(relpath);
- if (filename === "SUMMARY.md"){
- if( event.kind === "remove"){
- this.doc_map.delete(relpath);
- await onRemove(relpath);
- }
- else if (event.kind === "create" || event.kind === "modify"){
- const {content, metadata} = await readMarkdownDoc(relpath);
- const doc = {
- path: relpath,
- content: content,
- attributes: metadata,
- };
- this.setDoc(doc);
- if (event.kind === "create"){
- await onAdd(doc);
- }
- else if (event.kind === "modify"){
- await onChange(doc);
- }
- }
- }
- }
- }
- }
-
- makeIndex(options? : {
- onUpdate?: (() => void) | (() => Promise),
- abort?: AbortSignal,
- watch?: boolean,
- }){
- const opt = options ?? {};
- const index = Index.createIndex(this.getDocs());
- if (!opt.watch){
- return index;
- }
- const update = async () => {
- index.setDocs(this.getDocs());
- if (opt.onUpdate){
- await opt.onUpdate();
- }
- }
- this.watchDir(".", {
- async onAdd(_doc){
- await update();
- },
- async onRemove(_path){
- await update();
- },
- async onChange(_doc){
- await update();
- },
- abort: opt.abort,
- });
- return index;
- }
-}
\ No newline at end of file
+ const update = async () => {
+ index.setDocs(this.getDocs());
+ if (opt.onUpdate) {
+ await opt.onUpdate();
+ }
+ };
+ this.watchDir(".", {
+ async onAdd(_doc) {
+ await update();
+ },
+ async onRemove(_path) {
+ await update();
+ },
+ async onChange(_doc) {
+ await update();
+ },
+ abort: opt.abort,
+ });
+ return index;
+ }
+}
diff --git a/src/media.ts b/src/media.ts
index 04a7e2b..687a86e 100644
--- a/src/media.ts
+++ b/src/media.ts
@@ -1,60 +1,59 @@
-
const ICON_MAP: Record = {
- ".pdf": "file-pdf",
- ".zip": "file-zip",
- ".rar": "file-zip",
- ".7z": "file-zip",
- ".tar": "file-zip",
- ".gz": "file-zip",
- ".bz2": "file-zip",
- ".xz": "file-zip",
- ".doc": "file-word",
- ".docx": "file-word",
- ".xls": "file-excel",
- ".xlsx": "file-excel",
- ".ppt": "file-ppt",
- ".pptx": "file-ppt",
- ".txt": "file-text",
- ".md": "filetype-md",
- ".html": "filetype-html",
- ".ts": "file-code",
- ".tsx": "filetype-tsx",
- ".js": "filetype-js",
- ".json": "filetype-json",
- ".jsx": "filetype-jsx",
- ".css": "filetype-css",
- ".scss": "filetype-scss",
- ".csv": "filetype-csv",
- ".xml": "filetype-xml",
- ".svg": "filetype-svg",
- ".mp3": "file-music",
- ".wav": "file-music",
- ".ogg": "file-music",
- ".flac": "file-music",
- ".mp4": "file-play",
- ".mkv": "file-play",
- ".avi": "file-play",
- ".mov": "file-play",
- ".wmv": "file-play",
- ".webm": "file-play",
- ".mpg": "file-play",
- ".mpeg": "file-play",
- ".flv": "file-play",
- ".m4v": "file-play",
- ".m4a": "file-play",
- ".aac": "file-play",
- ".jpg": "file-image",
- ".jpeg": "file-image",
- ".png": "file-image",
- ".gif": "file-image",
- ".bmp": "file-image",
- ".ico": "file-image",
- ".tiff": "file-image",
- ".tif": "file-image",
- ".webp": "file-image",
- ".psd": "file-image",
-}
+ ".pdf": "file-pdf",
+ ".zip": "file-zip",
+ ".rar": "file-zip",
+ ".7z": "file-zip",
+ ".tar": "file-zip",
+ ".gz": "file-zip",
+ ".bz2": "file-zip",
+ ".xz": "file-zip",
+ ".doc": "file-word",
+ ".docx": "file-word",
+ ".xls": "file-excel",
+ ".xlsx": "file-excel",
+ ".ppt": "file-ppt",
+ ".pptx": "file-ppt",
+ ".txt": "file-text",
+ ".md": "filetype-md",
+ ".html": "filetype-html",
+ ".ts": "file-code",
+ ".tsx": "filetype-tsx",
+ ".js": "filetype-js",
+ ".json": "filetype-json",
+ ".jsx": "filetype-jsx",
+ ".css": "filetype-css",
+ ".scss": "filetype-scss",
+ ".csv": "filetype-csv",
+ ".xml": "filetype-xml",
+ ".svg": "filetype-svg",
+ ".mp3": "file-music",
+ ".wav": "file-music",
+ ".ogg": "file-music",
+ ".flac": "file-music",
+ ".mp4": "file-play",
+ ".mkv": "file-play",
+ ".avi": "file-play",
+ ".mov": "file-play",
+ ".wmv": "file-play",
+ ".webm": "file-play",
+ ".mpg": "file-play",
+ ".mpeg": "file-play",
+ ".flv": "file-play",
+ ".m4v": "file-play",
+ ".m4a": "file-play",
+ ".aac": "file-play",
+ ".jpg": "file-image",
+ ".jpeg": "file-image",
+ ".png": "file-image",
+ ".gif": "file-image",
+ ".bmp": "file-image",
+ ".ico": "file-image",
+ ".tiff": "file-image",
+ ".tif": "file-image",
+ ".webp": "file-image",
+ ".psd": "file-image",
+};
-export function extToIcon(s: string): string{
- return `/icon/${(ICON_MAP[s] ?? "file")}.svg`;
-}
\ No newline at end of file
+export function extToIcon(s: string): string {
+ return `/icon/${(ICON_MAP[s] ?? "file")}.svg`;
+}
diff --git a/src/readDoc.ts b/src/readDoc.ts
index 1ea9baa..67889cc 100644
--- a/src/readDoc.ts
+++ b/src/readDoc.ts
@@ -1,36 +1,41 @@
-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') {
- start++;
- }
- return str.substring(start, end);
+ while (
+ str[start] === " " || str[start] === "\t" || str[start] === "\r" ||
+ str[start] === "\n"
+ ) {
+ start++;
+ }
+ return str.substring(start, end);
}
interface Doc {
- metadata: Record;
- content: string;
+ metadata: Record;
+ content: string;
}
export function parse(content: string): Doc {
- if(!content.startsWith('---')){
- return {
- metadata: {},
- content: content,
- }
- }
- 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;
+ if (!content.startsWith("---")) {
return {
- metadata: metadata,
- content: c,
- }
+ metadata: {},
+ content: content,
+ };
+ }
+ 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;
+ return {
+ metadata: metadata,
+ content: c,
+ };
}
export async function readMarkdownDoc(path: string): Promise {
- const doc = await Deno.readTextFile(path);
- return parse(doc);
+ const doc = await Deno.readTextFile(path);
+ return parse(doc);
}
-
diff --git a/src/store/doc.ts b/src/store/doc.ts
index 991b87d..a2c7c23 100644
--- a/src/store/doc.ts
+++ b/src/store/doc.ts
@@ -1,44 +1,43 @@
import { Index } from "../client_search.ts";
import { Doc, DocCollector } from "../collect.ts";
-
export const docCollector = new DocCollector(
- {
- dropContent: true,
- summaryOnly: true,
- });
+ {
+ dropContent: true,
+ summaryOnly: true,
+ },
+);
export let docIndex: Index | undefined = undefined;
export async function prepareDocs() {
- const docPath = Deno.env.get("COLLECT_DOC_PATH");
- if (!docPath) {
- await docCollector.walkDir(".");
- docIndex = docCollector.makeIndex({
- watch: true
- });
- return docIndex;
- }
-
- try {
- const doc_dump = await Deno.readTextFile(docPath);
- const docs = JSON.parse(doc_dump) as Doc[];
- docCollector.setDocs(docs);
- } catch (error) {
- if (error instanceof Deno.errors.NotFound) {
- await docCollector.walkDir(".");
- await Deno.writeTextFile(docPath, JSON.stringify(docCollector.getDocs()));
- }
- else {
- throw error;
- }
- }
-
- docIndex = docCollector.makeIndex({
- watch: true,
- onUpdate: async () => {
- await Deno.writeTextFile(docPath, JSON.stringify(docCollector.getDocs()));
- }
+ const docPath = Deno.env.get("COLLECT_DOC_PATH");
+ if (!docPath) {
+ await docCollector.walkDir(".");
+ docIndex = docCollector.makeIndex({
+ watch: true,
});
return docIndex;
+ }
+
+ try {
+ const doc_dump = await Deno.readTextFile(docPath);
+ const docs = JSON.parse(doc_dump) as Doc[];
+ docCollector.setDocs(docs);
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ await docCollector.walkDir(".");
+ await Deno.writeTextFile(docPath, JSON.stringify(docCollector.getDocs()));
+ } else {
+ throw error;
+ }
+ }
+
+ docIndex = docCollector.makeIndex({
+ watch: true,
+ onUpdate: async () => {
+ await Deno.writeTextFile(docPath, JSON.stringify(docCollector.getDocs()));
+ },
+ });
+ return docIndex;
}
diff --git a/src/user/db.ts b/src/user/db.ts
index f043681..4dec24d 100644
--- a/src/user/db.ts
+++ b/src/user/db.ts
@@ -1,13 +1,13 @@
-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");
- if (DB_path === undefined){
- Deno.env.set("DB_PATH", "./db.sqlite");
- DB_path = "./db.sqlite";
- }
- let db = new DB(DB_path);
- createSchema(db);
- return db;
-}
\ No newline at end of file
+ let DB_path = Deno.env.get("DB_PATH");
+ if (DB_path === undefined) {
+ Deno.env.set("DB_PATH", "./db.sqlite");
+ DB_path = "./db.sqlite";
+ }
+ let db = new DB(DB_path);
+ createSchema(db);
+ return db;
+}
diff --git a/src/user/user.ts b/src/user/user.ts
index 96ca7b1..8cc0ac6 100644
--- a/src/user/user.ts
+++ b/src/user/user.ts
@@ -1,65 +1,74 @@
-import {genSalt, hash, compare} from "bcrypt";
+import { compare, genSalt, hash } from "bcrypt";
import { DB } from "sqlite";
-interface User{
- name: string;
- salted_password: string;
- salt: string;
+interface User {
+ name: string;
+ salted_password: string;
+ salt: string;
}
-export async function createUser(name: string, password: string){
- const salt = await genSalt(10);
- const salted_password = await hash(password, salt);
- const user: User = {
- name: name,
- salted_password: salted_password,
- salt: salt
- }
- return user;
+export async function createUser(name: string, password: string) {
+ const salt = await genSalt(10);
+ const salted_password = await hash(password, salt);
+ const user: User = {
+ name: name,
+ salted_password: salted_password,
+ salt: salt,
+ };
+ return user;
}
-export async function verifyUser(user: User, password: string){
- return await compare(password, user.salted_password);
+export async function verifyUser(user: User, password: string) {
+ return await compare(password, user.salted_password);
}
-export async function getAllUsers(db :DB): Promise{
- 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,
- salt,
- }));
+export async function getAllUsers(db: DB): Promise {
+ 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,
+ salt,
+ }));
}
-export async function getUser(db: DB, name: string): Promise{
- 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;
- }
- const user = users[0];
- return {
- name: user[0],
- salted_password: user[1],
- salt: user[2],
- }
+export async function getUser(db: DB, name: string): Promise {
+ 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;
+ }
+ const user = users[0];
+ return {
+ 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]);
+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],
+ );
}
-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]);
+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],
+ );
}
-export async function deleteUser(db: DB, name: string){
- await db.query("DELETE FROM users WHERE name = ?", [name]);
+export async function deleteUser(db: DB, name: string) {
+ await db.query("DELETE FROM users WHERE name = ?", [name]);
}
-export async function createSchema(db: DB){
- await db.query("CREATE TABLE IF NOT EXISTS users (name TEXT PRIMARY KEY, salted_password TEXT, salt TEXT)");
-}
\ No newline at end of file
+export async function createSchema(db: DB) {
+ await db.query(
+ "CREATE TABLE IF NOT EXISTS users (name TEXT PRIMARY KEY, salted_password TEXT, salt TEXT)",
+ );
+}
diff --git a/static/icon/bootstrap-icons.json b/static/icon/bootstrap-icons.json
index 01e6f90..d6599d3 100644
--- a/static/icon/bootstrap-icons.json
+++ b/static/icon/bootstrap-icons.json
@@ -1995,4 +1995,4 @@
"sina-weibo": 63690,
"tencent-qq": 63691,
"wikipedia": 63692
-}
\ No newline at end of file
+}
diff --git a/tailwind.config.cjs b/tailwind.config.cjs
index 49d598a..15b41b8 100644
--- a/tailwind.config.cjs
+++ b/tailwind.config.cjs
@@ -5,4 +5,4 @@ module.exports = {
extend: {},
},
plugins: [],
-}
+};
diff --git a/test_data/d/SUMMARY.md b/test_data/d/SUMMARY.md
index 5fe0650..dd9d7ea 100644
--- a/test_data/d/SUMMARY.md
+++ b/test_data/d/SUMMARY.md
@@ -2,6 +2,7 @@
title: "hello"
tags: ["asdf","wer"]
---
+
# hello
- [hello](hello.md)
@@ -9,4 +10,4 @@ tags: ["asdf","wer"]
asdf
-File: test_data\d\hello.md
\ No newline at end of file
+File: test_data\d\hello.md
diff --git a/test_data/e/SUMMARY.md b/test_data/e/SUMMARY.md
index a50dded..ec4a489 100644
--- a/test_data/e/SUMMARY.md
+++ b/test_data/e/SUMMARY.md
@@ -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.
\ No newline at end of file
+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.
diff --git a/test_data/f/SUMMARY.md b/test_data/f/SUMMARY.md
index 38ca134..36508d2 100644
--- a/test_data/f/SUMMARY.md
+++ b/test_data/f/SUMMARY.md
@@ -3,4 +3,4 @@ title: "한글 테스트. 띄어쓰기없이도되나?"
tags: ["한글", "테스트"]
---
-# 한글 테스트. 띄어쓰기없이도되나?
\ No newline at end of file
+# 한글 테스트. 띄어쓰기없이도되나?
diff --git a/user.ts b/user.ts
index 53bc0c1..907527f 100644
--- a/user.ts
+++ b/user.ts
@@ -1,63 +1,66 @@
-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";
export const user_command = new Command()
- .description("Manage users.")
- .command("add", "add a user")
- .arguments("[username:string]")
- .option("-p, --password ", "The password for the user.")
- .option("-q, --quiet", "quiet output.")
- .action(async ({quiet, password}
- , username) => {
- if(username === undefined){
- username = await Input.prompt("Username: ");
- }
- if(password === undefined){
- password = await Secret.prompt("Password: ");
- const password2 = await Secret.prompt("Confirm password: ");
- if (password !== password2){
- console.error("Passwords do not match.");
- Deno.exit(1);
- }
- }
- const db = connectDB();
- const new_user = await users.createUser( username, password);
- await users.addUser(db, new_user);
- if (!quiet){
- console.log(`Added user ${username}`);
- }
- })
- .command("delete", "delete a user")
- .arguments("")
- .option("-q, --quiet", "Quiet output.")
- .action(async ({quiet}, username) => {
- const db = connectDB();
- await users.deleteUser(db, username);
- if (!quiet){
- console.log(`Deleting user ${username}`);
- }
- })
- .command("list", "list all users")
- .action(async () => {
- const db = connectDB();
- const all_users = await users.getAllUsers(db);
- for (const user of all_users){
- console.log(`${user.name}`);
- }
- })
- .command("reset", "reset a user's password")
- .arguments(" ")
- .option("-q, --quiet", "quiet output.")
- .action(async ({quiet}, [username, password]) => {
- const db = connectDB();
- const new_user = await users.createUser( username, password);
- await users.updateUser(db, new_user);
- if (!quiet){
- console.log(`Resetting password for user ${username}`);
- }
- });
+ .description("Manage users.")
+ .command("add", "add a user")
+ .arguments("[username:string]")
+ .option("-p, --password ", "The password for the user.")
+ .option("-q, --quiet", "quiet output.")
+ .action(async ({ quiet, password }, username) => {
+ if (username === undefined) {
+ username = await Input.prompt("Username: ");
+ }
+ if (password === undefined) {
+ password = await Secret.prompt("Password: ");
+ const password2 = await Secret.prompt("Confirm password: ");
+ if (password !== password2) {
+ console.error("Passwords do not match.");
+ Deno.exit(1);
+ }
+ }
+ const db = connectDB();
+ const new_user = await users.createUser(username, password);
+ await users.addUser(db, new_user);
+ if (!quiet) {
+ console.log(`Added user ${username}`);
+ }
+ })
+ .command("delete", "delete a user")
+ .arguments("")
+ .option("-q, --quiet", "Quiet output.")
+ .action(async ({ quiet }, username) => {
+ const db = connectDB();
+ await users.deleteUser(db, username);
+ if (!quiet) {
+ console.log(`Deleting user ${username}`);
+ }
+ })
+ .command("list", "list all users")
+ .action(async () => {
+ const db = connectDB();
+ const all_users = await users.getAllUsers(db);
+ for (const user of all_users) {
+ console.log(`${user.name}`);
+ }
+ })
+ .command("reset", "reset a user's password")
+ .arguments(" ")
+ .option("-q, --quiet", "quiet output.")
+ .action(async ({ quiet }, [username, password]) => {
+ const db = connectDB();
+ const new_user = await users.createUser(username, password);
+ await users.updateUser(db, new_user);
+ if (!quiet) {
+ console.log(`Resetting password for user ${username}`);
+ }
+ });
-if (import.meta.main){
- await user_command.parse(Deno.args);
-}
\ No newline at end of file
+if (import.meta.main) {
+ await user_command.parse(Deno.args);
+}
diff --git a/util/secret.ts b/util/secret.ts
index 870e656..5292c9d 100644
--- a/util/secret.ts
+++ b/util/secret.ts
@@ -1,28 +1,31 @@
-
export async function generateSecretKey() {
- const key = await crypto.subtle.generateKey(
+ const key = await crypto.subtle.generateKey(
+ { name: "HMAC", hash: "SHA-512" },
+ true,
+ ["sign", "verify"],
+ );
+
+ return key;
+}
+
+export async function prepareSecretKey() {
+ const key = Deno.env.get("SECRET_KEY");
+ 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"],
);
-
+ return key;
+ }
+ } else {
+ const key = await generateSecretKey();
+ const out = await crypto.subtle.exportKey("jwk", key);
+ Deno.env.set("SECRET_KEY", JSON.stringify(out));
return key;
+ }
}
-
-export async function prepareSecretKey(){
- const key = Deno.env.get("SECRET_KEY");
- 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"]);
- return key;
- }
- }
- else {
- const key = await generateSecretKey();
- const out = await crypto.subtle.exportKey("jwk", key);
- Deno.env.set("SECRET_KEY", JSON.stringify(out));
- return key;
- }
-}
\ No newline at end of file
diff --git a/util/util.ts b/util/util.ts
index ceb49fb..bb58035 100644
--- a/util/util.ts
+++ b/util/util.ts
@@ -1,15 +1,18 @@
-export function removePrefixFromPathname(pathname: string, prefix: string): string {
- let ret = pathname;
- ret = ret.slice(prefix.length);
- if (ret.startsWith("/")) {
- ret = ret.slice(1);
- }
- if (ret === "") {
- ret = ".";
- }
- return ret;
+export function removePrefixFromPathname(
+ pathname: string,
+ prefix: string,
+): string {
+ let ret = pathname;
+ ret = ret.slice(prefix.length);
+ if (ret.startsWith("/")) {
+ ret = ret.slice(1);
+ }
+ if (ret === "") {
+ ret = ".";
+ }
+ return ret;
}
export function encodePath(path: string): string {
- return path.split("/").map(encodeURIComponent).join("/");
-}
\ No newline at end of file
+ return path.split("/").map(encodeURIComponent).join("/");
+}