diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index bffb357..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "next/core-web-vitals"
-}
diff --git a/.gitignore b/.gitignore
index 6a47554..d9a26d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,6 @@
-node_modules/
-dist/
-.next/
.DS_Store
-.cache/
-*.bun
**/.*.md
.env
-public/posts.json
-public/notes.json
-public/sitemap.json
+build/
+*.pdb
+*.exe
\ No newline at end of file
diff --git a/README.md b/README.md
index d280373..af80c4c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
# PaulW.XYZ
-A [Next.js](https://nextjs.com) website that mainly involves generating content out of a bunch of markdown files contained in `notes/` and `posts/` which contain rough, unorganized yet useful information and thought-out articles respectively.
-
## Third-Party Licenses
Any trademarks listed on this site are the property of their respective owners. This site neither endorses nor is affiliated with any of the owners. Any source code available on any of the pages is without warranty of any kind and the use of such is at your own risk.
diff --git a/bun.lockb b/bun.lockb
deleted file mode 100644
index 3146796..0000000
Binary files a/bun.lockb and /dev/null differ
diff --git a/public/external.json b/external.json
similarity index 100%
rename from public/external.json
rename to external.json
diff --git a/generator.odin b/generator.odin
new file mode 100644
index 0000000..3420b96
--- /dev/null
+++ b/generator.odin
@@ -0,0 +1,838 @@
+package www
+
+import "core:sort"
+import "base:runtime"
+import "core:bufio"
+import "core:c"
+import "core:encoding/json"
+import "core:fmt"
+import "core:os"
+import "core:strings"
+import md "vendor:commonmark"
+
+SITE_NAME :: "PaulW.XYZ"
+
+COMMON_STYLESHEETS :: []string {
+ "/assets/global.css",
+ "https://cdn.jsdelivr.net/npm/prismjs@1.30.0/themes/prism-tomorrow.min.css",
+}
+COMMON_SCRIPTS :: []string {
+ "https://cdn.jsdelivr.net/npm/prismjs@1.30.0/components/prism-core.min.js",
+ "https://cdn.jsdelivr.net/npm/prismjs@1.30.0/plugins/autoloader/prism-autoloader.min.js",
+}
+
+OUTPUT_ROOT_NAME :: "build"
+
+markdown_paths :: []string{"notes", "posts"}
+
+main :: proc() {
+ create_output_directories(markdown_paths)
+ copy_public_assets()
+
+ home_page := generate_sitemap()
+ dump_page(home_page)
+
+ sb := strings.builder_make()
+
+ process_markdown(&sb, home_page)
+ generate_home(&sb, home_page)
+}
+
+Page :: struct {
+ title: string,
+ pages: map[string]Page,
+}
+
+add_page :: proc(parent: ^Page, key: string, value: Page) {
+ parent.pages[key] = value
+}
+
+dump_page :: proc(page: Page, indent_level := 0) {
+ for i in 0 ..< indent_level {
+ fmt.print("\t")
+ }
+ fmt.println(page.title)
+ for name, page in page.pages {
+ for i in 0 ..= indent_level {
+ fmt.print("\t")
+ }
+ fmt.println("/", name)
+ dump_page(page, indent_level + 1)
+ }
+}
+
+generate_sitemap :: proc() -> Page {
+ home_page: Page = {
+ title = SITE_NAME,
+ pages = make(map[string]Page),
+ }
+
+ process_markdown_parent(&home_page, "notes", "Notes")
+ process_markdown_parent(&home_page, "posts", "Posts")
+
+ return home_page
+}
+
+process_markdown_parent :: proc(parent: ^Page, dir: string, title: string) {
+ page := Page {
+ title = title,
+ pages = make(map[string]Page),
+ }
+
+ files, os_err := os.read_directory_by_path(dir, 0, context.temp_allocator)
+ if os_err != nil {
+ fmt.eprintfln("failed to read files from directory '%s' (%s)", dir, os_err)
+ return
+ }
+ for file_info in files {
+ if len(file_info.name) < 1 || file_info.name[0] == '.' do continue
+
+ file, os_err := os.open(file_info.fullpath, {.Read})
+ if os_err != nil {
+ panic("something funny happened lol; TODO of course!!!")
+ }
+ defer os.close(file)
+
+ reader: bufio.Reader
+ bufio.reader_init(&reader, os.to_reader(file))
+ defer bufio.reader_destroy(&reader)
+
+ line := bufio.reader_read_string(&reader, '\n', context.temp_allocator) or_continue
+ trimmed := strings.trim(line, "\n\r \t")
+
+ if len(trimmed) < 5 || trimmed[0] != '#' || trimmed[1] != ' ' do continue
+
+ title := strings.substring_from(trimmed, 2) or_continue
+
+ ext := os.ext(file_info.name)
+ if ext != ".md" do continue
+ output_name := strings.trim_suffix(file_info.name, ext)
+
+ md_page := Page {
+ title = strings.clone(title),
+ }
+ add_page(&page, output_name, md_page)
+
+ }
+ add_page(parent, dir, page)
+}
+
+copy_public_assets :: proc() {
+ err := os.copy_directory_all(OUTPUT_ROOT_NAME, "public")
+ if err != nil {
+ fmt.eprintln("error copying public assets", err)
+ }
+}
+
+create_output_directories :: proc(dir_names: []string) {
+ os_err := os.make_directory(OUTPUT_ROOT_NAME)
+ if os_err != nil && os_err != .Exist {
+ fmt.eprintfln("cannot create output root directory '%s': %s", OUTPUT_ROOT_NAME, os_err)
+ return
+ }
+
+ for dir_name in dir_names {
+ dir_path, alloc_err := os.join_path({OUTPUT_ROOT_NAME, dir_name}, context.temp_allocator)
+ if alloc_err != nil {
+ fmt.eprintln("allocator error:", alloc_err)
+ continue
+ }
+
+ os_err = os.make_directory(dir_path)
+ if os_err != nil && os_err != .Exist {
+ fmt.eprintln("failed to create directory:", dir_path)
+ continue
+ }
+ }
+}
+
+html_escape :: proc(sb: ^strings.Builder, html: string) {
+ strings.write_string(sb, html)
+}
+
+render_html :: proc(sb: ^strings.Builder, root: ^md.Node) {
+ iter := md.iter_new(root)
+ defer md.iter_free(iter)
+ should_write_slug: i32 = 0
+ parent_is_li := false
+
+ for md.iter_next(iter) != .Done {
+ node := md.iter_get_node(iter)
+ event := md.iter_get_event_type(iter)
+
+ #partial switch node.type {
+ case .Heading:
+ level := md.node_get_heading_level(node)
+
+ if event == .Enter {
+ should_write_slug = level
+ } else {
+ fmt.sbprintf(sb, "\n", level)
+ }
+
+ case .Paragraph:
+ if parent_is_li {
+ if event != .Enter do parent_is_li = false
+ } else {
+ if event == .Enter {
+ strings.write_string(sb, "
")
+ } else if event == .Exit {
+ strings.write_string(sb, "
\n")
+ }
+ }
+
+ case .Emph:
+ if event == .Enter {
+ strings.write_string(sb, "")
+ } else {
+ strings.write_string(sb, " ")
+ }
+
+ case .Strong:
+ if event == .Enter {
+ strings.write_string(sb, "")
+ } else {
+ strings.write_string(sb, " ")
+ }
+
+ case .Code:
+ strings.write_string(sb, "")
+ lit := string(md.node_get_literal(node))
+ html_escape(sb, lit)
+ strings.write_string(sb, "")
+
+ case .Text:
+ lit := string(md.node_get_literal(node))
+ if should_write_slug > 0 {
+ fmt.sbprintf(sb, "")
+ should_write_slug = 0
+ }
+ html_escape(sb, lit)
+
+ case .Soft_Break:
+ strings.write_byte(sb, ' ')
+
+ case .Line_Break:
+ strings.write_string(sb, " \n")
+
+ case .Code_Block:
+ fence_info := string(md.node_get_fence_info(node))
+ i := strings.index_any(fence_info, " \t\r\n")
+
+ lang := fence_info
+ if i != -1 {
+ lang = fence_info[:i]
+ }
+
+ strings.write_string(sb, "")
+ html_escape(sb, string(md.node_get_literal(node)))
+ strings.write_string(sb, " \n")
+
+ case .HTML_Inline:
+ if event == .Enter {
+ strings.write_string(sb, string(md.node_get_literal(node)))
+ }
+
+ case .HTML_Block:
+ if event == .Enter {
+ strings.write_string(sb, string(md.node_get_literal(node)))
+ }
+
+ case .Thematic_Break:
+ if event == .Enter {
+ strings.write_string(sb, " \n")
+ }
+
+ case .List:
+ if event == .Enter {
+ if md.node_get_list_type(node) == .Bullet {
+ strings.write_string(sb, "\n")
+ } else {
+ strings.write_string(sb, "\n")
+ }
+ } else {
+ if md.node_get_list_type(node) == .Bullet {
+ strings.write_string(sb, " \n")
+ } else {
+ strings.write_string(sb, "\n")
+ }
+ }
+
+ case .Item:
+ if event == .Enter {
+ parent_is_li = true
+ strings.write_string(sb, "")
+ } else {
+ strings.write_string(sb, " \n")
+ }
+
+ case .Link:
+ if event == .Enter {
+ fmt.sbprintf(sb,
+ "",
+ string(md.node_get_url(node)))
+ } else {
+ strings.write_string(sb, " ")
+ }
+
+ case .Image:
+ if event == .Enter {
+ fmt.sbprintf(sb,
+ " ")
+ }
+ }
+ }
+}
+
+remove_title :: proc(root: ^md.Node) {
+ heading_state: enum {
+ None,
+ Entered,
+ //Copied,
+ Exited,
+ } = .None
+ //title: string
+ iter := md.iter_new(root)
+ defer md.iter_free(iter)
+ for md.iter_next(iter) != .Done {
+ event_type := md.iter_get_event_type(iter)
+ node := md.iter_get_node(iter)
+
+ #partial switch node.type {
+ case .Heading:
+ level := md.node_get_heading_level(node)
+ if level == 1 && heading_state != .Exited {
+ if event_type == .Enter {
+ heading_state = .Entered
+ } else if event_type == .Exit {
+ heading_state = .Exited
+ md.node_unlink(node)
+ md.node_free(node)
+ }
+ }
+ // case .Text:
+ // if heading_state == .Entered {
+ // heading_state = .Copied
+ // title = strings.clone_from_cstring(md.node_get_literal(node))
+ // }
+ }
+ }
+ //return title
+}
+
+sb_indent :: proc(sb: ^strings.Builder, level: i32) {
+ for i in 0..= 'A' && c <= 'Z':
+ if pending_dash {
+ strings.write_byte(sb, '-')
+ pending_dash = false
+ }
+ strings.write_byte(sb, byte(c + ('a' - 'A')))
+ wrote_any = true
+
+ case (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'):
+ if pending_dash {
+ strings.write_byte(sb, '-')
+ pending_dash = false
+ }
+ strings.write_byte(sb, byte(c))
+ wrote_any = true
+
+ case:
+ if wrote_any {
+ pending_dash = true
+ }
+ }
+ }
+}
+
+generate_toc :: proc(sb: ^strings.Builder, root: ^md.Node) {
+ entered_heading := false
+ started := false
+ iter := md.iter_new(root)
+ defer md.iter_free(iter)
+ current_level: i32 = 0
+ for md.iter_next(iter) != .Done {
+ event_type := md.iter_get_event_type(iter)
+ node := md.iter_get_node(iter)
+
+ #partial switch node.type {
+ case .Heading:
+ level := md.node_get_heading_level(node)
+ event_type := md.iter_get_event_type(iter)
+
+ if event_type == .Enter {
+ entered_heading = true
+ if !started {
+ for i in 1..")
+ started = true
+ } else {
+ for i in current_level..")
+ if current_level > level do strings.write_string(sb, "")
+ for i in level..")
+ if current_level == level do strings.write_string(sb, "")
+ }
+
+ strings.write_string(sb, "")
+ current_level = level
+ }
+ case .Text:
+ if entered_heading {
+ entered_heading = false
+ text := string(md.node_get_literal(node))
+ strings.write_string(sb, "")
+ strings.write_string(sb, text)
+ strings.write_string(sb, " ")
+ }
+ }
+ }
+
+ if started {
+ strings.write_string(sb, " ")
+ for i in 1..")
+ }
+}
+
+// must free
+parse_json :: proc(file_path: string) -> json.Value {
+ json_file_content, os_err := os.read_entire_file_from_path(file_path, context.temp_allocator)
+ if os_err != nil {
+ fmt.eprintfln("failed to read files from directory '%s' (%s)", file_path, os_err)
+ return nil
+ }
+ json_value, json_err := json.parse(
+ json_file_content,
+ json.DEFAULT_SPECIFICATION,
+ true,
+ context.allocator,
+ )
+ if json_err != .None {
+ fmt.eprintfln("error parsing json file '%s' (%s)", file_path, json_err)
+ return nil
+ }
+ return json_value
+}
+
+generate_home :: proc(sb: ^strings.Builder, site: Page) {
+ strings.builder_reset(sb)
+ EXTERNAL_JSON_FILE :: "external.json"
+
+ external_json := parse_json(EXTERNAL_JSON_FILE)
+ defer json.destroy_value(external_json)
+
+ output_file_path, alloc_err := os.join_path(
+ {OUTPUT_ROOT_NAME, "index.html"},
+ context.temp_allocator,
+ )
+ if alloc_err != nil {
+ fmt.eprintln("allocator error:", alloc_err)
+ return
+ }
+
+ output_file, os_err2 := os.open(output_file_path, {.Write, .Create, .Trunc})
+ if os_err2 != nil {
+ fmt.eprintfln("failed to open output file '%s' (%s)", output_file_path, os_err2)
+ return
+ }
+ defer os.close(output_file)
+
+ {
+ html_begin(sb)
+ defer html_end(sb, {})
+
+ heading_title(sb, SITE_NAME)
+
+ container_begin(sb)
+ defer container_end(sb)
+
+ {
+ block_begin(sb)
+ defer block_end(sb)
+
+ obj := external_json.(json.Object)
+ for text, href in obj {
+ fmt.sbprintf(
+ sb,
+ "%s ",
+ href.(json.String),
+ text,
+ )
+ }
+ }
+
+ {
+ block_begin(sb)
+ defer block_end(sb)
+
+ fmt.sbprintf(sb, "%s ", "Navigation")
+
+ for name, page in site.pages {
+ fmt.sbprintf(sb, "%s ", name, page.title)
+ }
+ }
+ }
+
+ os.write_string(output_file, strings.to_string(sb^))
+}
+
+set_pages_to_process :: proc(page: Page, pages: ^map[string]^Page, parent_path := "") {
+ for name, &page in page.pages {
+ current_path := strings.trim(fmt.tprintf("%s/%s", parent_path, name), "/")
+ for path in markdown_paths {
+ if strings.trim(path, "/") == current_path do map_insert(pages, path, &page)
+ }
+ set_pages_to_process(page, pages, current_path)
+ }
+}
+
+process_markdown :: proc(sb: ^strings.Builder, site: Page) {
+ pages_to_process := make(map[string]^Page)
+ defer delete(pages_to_process)
+ set_pages_to_process(site, &pages_to_process)
+
+ for markdown_dir, page in pages_to_process {
+ markdown_dir_toc := make([dynamic][3]string) // path, title, date
+ defer delete(markdown_dir_toc)
+ defer create_toc_page(page.title, markdown_dir, &markdown_dir_toc, site)
+
+ for md_name, md_page in page.pages {
+ current_path := fmt.tprintf("/%s/%s", markdown_dir, md_name)
+
+ input_file_name := fmt.tprint(md_name, ".md", sep = "")
+ input_file_path, alloc_err := os.join_path(
+ {markdown_dir, input_file_name},
+ context.temp_allocator,
+ )
+
+ git_state, git_out, git_err, os_err := os.process_exec(
+ os.Process_Desc {
+ command = {
+ "git",
+ "log",
+ "-1",
+ "--date=format-local:%Y-%m-%dT%H:%M:%SZ",
+ "--format=%ad",
+ "--",
+ input_file_path,
+ },
+ env = {"TZ=UTC"},
+ },
+ context.temp_allocator,
+ )
+ if os_err != nil {
+ fmt.eprintln("failed to start git process", os_err, git_err)
+ return
+ }
+ date := strings.trim(string(git_out), " \t\n\r")
+
+ // @leak
+ append(&markdown_dir_toc, [3]string{strings.clone(current_path), md_page.title, date})
+
+ file, os_err1 := os.read_entire_file(input_file_path, context.temp_allocator)
+
+ strings.builder_reset(sb)
+ cmark_root := md.parse_document(
+ cast([^]u8)raw_data(file),
+ len(file),
+ {.Validate_UTF8, .Unsafe},
+ )
+ remove_title(cmark_root)
+
+
+ dir, alloc_err3 := os.join_path(
+ {OUTPUT_ROOT_NAME, markdown_dir, md_name},
+ context.temp_allocator
+ )
+ if alloc_err3 != nil {
+ fmt.println("allocator error:", alloc_err3)
+ continue
+ }
+ os.make_directory(dir)
+
+ output_file_path, alloc_err2 := os.join_path(
+ {OUTPUT_ROOT_NAME, markdown_dir, md_name, "index.html"},
+ context.temp_allocator,
+ )
+
+ if alloc_err2 != nil {
+ fmt.println("allocator error:", alloc_err3)
+ continue
+ }
+
+
+ out_file, os_err2 := os.open(output_file_path, {.Write, .Create, .Trunc})
+ if os_err2 != nil {
+ fmt.eprintfln("failed to open output file '%s' (%s)", output_file_path, os_err2)
+ continue
+ }
+ defer os.close(out_file)
+
+ {
+ html_begin(sb, md_page.title)
+ defer html_end(sb, site, current_path)
+
+ heading_title(sb, md_page.title)
+
+
+ container_begin(sb)
+ defer container_end(sb)
+
+ last_updated_date(sb, date)
+
+ {
+ block_begin(sb)
+ defer block_end(sb)
+
+ strings.write_string(sb, "Contents ")
+
+ generate_toc(sb, cmark_root)
+ }
+ strings.write_string(sb, " ")
+ {
+ block_begin(sb)
+ defer block_end(sb)
+
+ render_html(sb, cmark_root)
+ }
+ }
+
+ write_len: int
+ write_len, os_err = os.write_strings(out_file, strings.to_string(sb^))
+ if os_err != nil {
+ fmt.eprintfln("failed to write to output file '%s' (%s)", output_file_path, os_err)
+ continue
+ }
+ }
+ }
+}
+
+create_toc_page :: proc(title, toc_path: string, toc_info: ^[dynamic][3]string, site: Page) {
+ _sb: strings.Builder = strings.builder_make()
+ sb := &_sb
+ defer strings.builder_destroy(sb)
+ toc_file_path, alloc_err := os.join_path(
+ {OUTPUT_ROOT_NAME, toc_path, "index.html"},
+ context.temp_allocator,
+ )
+ if alloc_err != nil {
+ fmt.eprintln("allocator error:", alloc_err)
+ return
+ }
+
+ sort_toc :: proc(a: [3]string, b: [3]string) -> int {
+ return -strings.compare(a[2], b[2])
+ }
+
+ sort.quick_sort_proc(toc_info[:], sort_toc)
+
+ fmt.println(toc_file_path)
+
+ {
+ html_begin(sb, title)
+ defer html_end(sb, site, toc_path)
+
+ heading_title(sb, title)
+
+ container_begin(sb)
+ defer container_end(sb)
+
+ block_begin(sb)
+ defer block_end(sb)
+
+ if len(toc_info) > 0 {
+ table_begin(sb)
+ defer table_end(sb)
+
+ for item in toc_info {
+ tr_begin(sb)
+ fmt.sbprintf(
+ sb,
+ "%s %s ",
+ item[0],
+ item[1],
+ item[2],
+ item[2],
+ )
+ tr_end(sb)
+ }
+
+ fmt.sbprint(sb,
+ `
+ `
+ )
+ } else {
+ fmt.sbprint(
+ sb,
+ "",
+ "
**crickets**
",
+ "
Nothing to list...
",
+ "
Go Home ",
+ "
",
+ )
+ }
+ }
+
+
+ out_file, os_err2 := os.open(toc_file_path, {.Write, .Create, .Trunc})
+ if os_err2 != nil {
+ fmt.eprintfln("failed to open output file '%s' (%s)", toc_file_path, os_err2)
+ return
+ }
+ defer os.close(out_file)
+ write_len, os_err := os.write_strings(out_file, strings.to_string(_sb))
+ if os_err != nil {
+ fmt.eprintfln("failed to write to output file '%s' (%s)", toc_file_path, os_err)
+ return
+ }
+}
+
+last_updated_date :: proc(sb: ^strings.Builder, date: string) {
+ strings.write_string(sb, "Last updated: ")
+ strings.write_string(sb, date)
+ strings.write_string(sb, " ")
+
+ strings.write_string(sb, "")
+}
+
+nav :: proc(sb: ^strings.Builder, current_path: string, site: Page) {
+ fmt.sbprint(sb, "")
+ current_page := site
+ if current_path == "/" {
+ fmt.sbprint(sb, SITE_NAME)
+ } else {
+ fmt.sbprintf(sb, "%s ", "/", SITE_NAME)
+ res, ok := strings.split(current_path, "/", context.temp_allocator)
+
+ temp_sb := strings.builder_make(context.temp_allocator)
+ for i in 1 ..< (len(res) - 1) {
+ current_page = current_page.pages[res[i]]
+ fmt.sbprintf(&temp_sb, "/%s", res[i])
+ fmt.sbprintf(
+ sb,
+ " / %s ",
+ strings.to_string(temp_sb),
+ current_page.title,
+ )
+ }
+
+ if len(res) > 0 {
+ fmt.sbprint(sb, " /", current_page.pages[res[len(res) - 1]].title)
+ }
+ }
+
+ fmt.sbprint(sb, " ")
+}
+
+has_called_html_begin := 0
+html_begin :: proc(sb: ^strings.Builder, title: string = "") {
+ assert(has_called_html_begin >= 0)
+ has_called_html_begin += 1
+ fmt.sbprint(sb, "")
+ fmt.sbprint(sb, "")
+ fmt.sbprint(sb, "")
+ fmt.sbprint(sb, " ")
+ if title == "" do fmt.sbprintf(sb, "%s ", SITE_NAME)
+ else do fmt.sbprintf(sb, "%s | %s ", title, SITE_NAME)
+ for s in COMMON_STYLESHEETS {
+ fmt.sbprintf(sb, " ", s)
+ }
+ for s in COMMON_SCRIPTS {
+ fmt.sbprintf(sb, "", s)
+ }
+ fmt.sbprint(sb, "")
+ fmt.sbprint(sb, "")
+}
+
+html_end :: proc(sb: ^strings.Builder, site: Page, current_path := "/") {
+ assert(has_called_html_begin > 0)
+ has_called_html_begin -= 1
+ nav(sb, current_path, site)
+ fmt.sbprint(sb, "")
+ fmt.sbprint(sb, "")
+}
+
+has_called_container_begin := 0
+container_begin :: proc(sb: ^strings.Builder, element := "main") {
+ assert(has_called_container_begin >= 0)
+ has_called_container_begin += 1
+ fmt.sbprintf(sb, "<%s class='container'>", element)
+}
+
+container_end :: proc(sb: ^strings.Builder, element := "main") {
+ assert(has_called_container_begin > 0)
+ has_called_container_begin -= 1
+ fmt.sbprintf(sb, "%s>", element)
+}
+
+has_called_block_begin := 0
+block_begin :: proc(sb: ^strings.Builder, element := "section") {
+ assert(has_called_block_begin >= 0)
+ has_called_block_begin += 1
+ fmt.sbprintf(sb, "<%s class='block'>", element)
+}
+
+block_end :: proc(sb: ^strings.Builder, element := "section") {
+ assert(has_called_block_begin > 0)
+ has_called_block_begin -= 1
+ fmt.sbprintf(sb, "%s>", element)
+}
+
+heading_title :: proc(sb: ^strings.Builder, title: string) {
+ fmt.sbprintf(sb, "%s ", title)
+}
+
+has_called_table_begin := 0
+table_begin :: proc(sb: ^strings.Builder) {
+ assert(has_called_table_begin >= 0)
+ has_called_table_begin += 1
+ fmt.sbprint(sb, "")
+}
+
+table_end :: proc(sb: ^strings.Builder) {
+ assert(has_called_table_begin > 0)
+ has_called_table_begin -= 1
+ fmt.sbprintf(sb, "
")
+}
+
+
+has_called_tr_begin := 0
+tr_begin :: proc(sb: ^strings.Builder) {
+ assert(has_called_tr_begin >= 0)
+ has_called_tr_begin += 1
+ fmt.sbprint(sb, "")
+}
+
+tr_end :: proc(sb: ^strings.Builder) {
+ assert(has_called_tr_begin > 0)
+ has_called_tr_begin -= 1
+ fmt.sbprintf(sb, " ")
+}
diff --git a/next-env.d.ts b/next-env.d.ts
deleted file mode 100644
index 1b3be08..0000000
--- a/next-env.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-///
-///
-
-// NOTE: This file should not be edited
-// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/next.config.ts b/next.config.ts
deleted file mode 100644
index 1f9905d..0000000
--- a/next.config.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type {NextConfig } from 'next';
-import NextBundleAnalyzer from '@next/bundle-analyzer';
-
-let config: NextConfig = {
- reactStrictMode: true,
- turbopack: {
- rules: {
- '*.txt': {
- as: '*.js',
- loaders: ['raw-loader'],
- },
- '*.md': {
- as: '*.js',
- loaders: ['raw-loader'],
- }
- },
- resolveExtensions: ['.txt', '.md', '.tsx', '.ts', '.js']
- },
- webpack: (config, _options) => {
- config.module.rules.push(
- {
- test: /\.svg$/,
- use: [{ loader: '@svgr/webpack' }],
- },
- {
- test: /\.md$/,
- type: 'asset/source',
- },
- {
- test: /\.otf$/,
- type: 'asset/resource',
- },
- {
- test: /\.txt$/,
- type: 'asset/source',
- },
- );
-
- return config;
- },
-};
-
-if (process.env.ANALYZE) {
- config = NextBundleAnalyzer({
- enabled: true
- })(config);
-}
-
-export default config;
diff --git a/notes/references.md b/notes/references.md
index 9c5ff5f..35e238c 100644
--- a/notes/references.md
+++ b/notes/references.md
@@ -1,7 +1,10 @@
# References
-## [Intel® 64 and IA-32 Architectures Software Developer’s Manual](https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4)
+## Intel® 64 and IA-32 Architectures Software Developer’s Manual
+ -
-## [CUDA C++ Programming Guide](https://docs.nvidia.com/cuda/pdf/CUDA_C_Programming_Guide.pdf)
+## Windows Internals Book
+ -
-## [Windows Internals Book](https://learn.microsoft.com/en-us/sysinternals/resources/windows-internals)
\ No newline at end of file
+## CUDA C++ Programming Guide
+ -
diff --git a/notes/resources.md b/notes/resources.md
index b09a9a8..e8a2e97 100644
--- a/notes/resources.md
+++ b/notes/resources.md
@@ -18,33 +18,33 @@ refer to someone or within something.
### Memory Allocation Strategies
-- https://www.gingerbill.org/series/memory-allocation-strategies/
+-
### Immediate-Mode Graphical User Interfaces (2005)
-- https://caseymuratori.com/blog_0001
+-
-- https://www.youtube.com/watch?v=Z1qyvQsjK5Y
+-
### What Color is Your Function?
-- https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
+-
### Real-time audio programming 101: time waits for nothing
-- http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
+-
### Triangulation
-- https://www.humus.name/index.php?ID=228
+-
### Quantifying the Performance of Garbage Collection vs. Explicit Memory Management
-- https://people.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf
+-
### Typing is Hard
-- https://3fx.ch/typing-is-hard.html
+-
### Easy Scalable Text Rendering on the GPU
@@ -56,40 +56,40 @@ refer to someone or within something.
### The Aggregate Magic Algorithms
-- http://aggregate.org/MAGIC/
+-
### You Could Have Invented Monads! (And Maybe You Already Have.)
-- http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
+-
### Fix Your Timestep!
-- https://gafferongames.com/post/fix_your_timestep/
+-
### UTF-8 Everywhere
-- http://utf8everywhere.org
+-
### Parsing Gigabytes of JSON per Second
-- https://arxiv.org/abs/1902.08318 [[PDF](https://arxiv.org/pdf/1902.08318)]
+- [[PDF](https://arxiv.org/pdf/1902.08318)]
### What are OKLCH colors?
-- https://jakub.kr/components/oklch-colors
+-
### Software Foundations series
-- https://softwarefoundations.cis.upenn.edu/
+-
### Software Rendering Alpha-Blending Tricks
-- https://gist.github.com/mattiasgustavsson/c11e824e3d603d0c86e5e0dde4ecf839
+-
### A Relational Model of Data for Large Shared Data Banks
-- https://fermatslibrary.com/s/a-relational-model-of-data-for-large-shared-data-banks
+-
### Powersort
-- https://www.wild-inter.net/publications/munro-wild-2018.pdf
\ No newline at end of file
+-
diff --git a/notes/retro-gaming.md b/notes/retro-gaming.md
index 833a215..751337e 100644
--- a/notes/retro-gaming.md
+++ b/notes/retro-gaming.md
@@ -5,29 +5,27 @@ The use of the term retro is debatable here as the term is used quite inconsiste
## Recompilations
### Zelda64Recomp
-- https://github.com/Zelda64Recomp/Zelda64Recomp
+-
## Open-source Recreations
### OpenMW
-- https://github.com/OpenMW/openmw
+-
### OpenTTD
-- https://github.com/OpenTTD/OpenTTD
+-
### NFSIISE
-- https://github.com/zaps166/NFSIISE
+-
### RE3
-- https://github.com/github/dmca/blob/master/2021/02/2021-02-19-take-two.md
+-
- :(
## WidescreenFixesPack
-- https://thirteenag.github.io/wfp
-- https://github.com/ThirteenAG/WidescreenFixesPack
+-
+-
## Linux Arm Handhelds
-- https://portmaster.games/
+-
- native ports of the reimplementations of many old-skool games
-
-
diff --git a/package.json b/package.json
deleted file mode 100644
index 3673ec8..0000000
--- a/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "private": true,
- "scripts": {
- "prebuild": "node ./scripts/generate-metadata.js",
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "next lint"
- },
- "dependencies": {
- "dotenv": "^16.3.1",
- "highlight.js": "^11.10.0",
- "next": "^15.3.1",
- "normalize.css": "^8.0.1",
- "raw-loader": "^4.0.2",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "react-markdown": "^9.0.1",
- "rehype-autolink-headings": "^7.1.0",
- "rehype-highlight": "^7.0.0",
- "rehype-highlight-code-lines": "^1.0.4",
- "rehype-katex": "^7.0.1",
- "rehype-raw": "^7.0.0",
- "rehype-slug": "^6.0.0",
- "remark-gfm": "^4.0.0",
- "remark-loader": "^6.0.0",
- "remark-math": "^6.0.0",
- "uri-js": "^4.4.1"
- },
- "devDependencies": {
- "@next/bundle-analyzer": "^15.0.4",
- "@svgr/webpack": "^8.1.0",
- "@types/node": "^22.7.4",
- "@types/react": "^19.0.1",
- "@types/react-dom": "^19.0.1",
- "@types/react-syntax-highlighter": "^15.5.7",
- "eslint": "^9.11.1",
- "eslint-config-next": "^15.0.4",
- "typescript": "^5.6.2"
- }
-}
diff --git a/src/app/global.css b/public/assets/global.css
similarity index 90%
rename from src/app/global.css
rename to public/assets/global.css
index e052f11..2ca7770 100644
--- a/src/app/global.css
+++ b/public/assets/global.css
@@ -9,7 +9,7 @@
--secondary-blue: #05455f;
--tertiary-blue: #05232f;
--table-odd-color: rgba(255, 255, 255, 0.05);
- --table-even-color: rgba(255, 255, 255, 0.025);
+ --table-even-color: rgba(255, 255, 255, 0.025);
}
@font-face {
@@ -412,3 +412,42 @@ ul li {
background-color: #222222;
padding: 1rem;
}
+
+heading.container {
+ text-align: center;
+ margin: auto;
+ background-color: var(--main-background-color);
+}
+
+heading .title {
+ border-bottom: 1px solid #FFFFFF;
+ max-width: 95%;
+ margin: auto;
+}
+
+.last-updated {
+ text-align: right;
+ display: block;
+ font-style: italic;
+ font-size: 1rem;
+ margin: 0.5rem 0.75rem;
+}
+
+nav {
+ padding: 0.25rem 0.75rem;
+ font-size: 1.25rem;
+ box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4);
+ position: fixed;
+ bottom: 0;
+ z-index: 1000;
+ margin: 0;
+ height: 2.5rem;
+ max-height: 2.5rem;
+ min-height: 2.5rem;
+ min-width: 100%;
+ width: 100%;
+ background: linear-gradient(to bottom right, #1a3a15, #09351b) no-repeat center center fixed;
+ overflow-y: hidden;
+ overflow-x: auto;
+ white-space: nowrap;
+}
diff --git a/public/home.json b/public/home.json
deleted file mode 100644
index fb91a0f..0000000
--- a/public/home.json
+++ /dev/null
@@ -1,14 +0,0 @@
- {
- "posts": {
- "title": "Posts"
- },
- "notes": {
- "title": "Notes"
- },
- "about": {
- "title": "About"
- },
- "sitemap": {
- "title": "Site Map"
- }
- }
diff --git a/scripts/generate-metadata.js b/scripts/generate-metadata.js
deleted file mode 100644
index e99f830..0000000
--- a/scripts/generate-metadata.js
+++ /dev/null
@@ -1,131 +0,0 @@
-const path = require('path')
-const fs = require('fs/promises')
-
-const gitRef = process.env.WWW_GIT_REF ?? 'master'
-const giteaApiRepo = `https://git.paulw.xyz/api/v1/repos/xyz/www/`
-
-async function readFirstLines(filePath, lineCount = 1) {
- const gitFileFetch = await fetch(`${giteaApiRepo}raw/${filePath}?ref=${gitRef}`)
- if (!gitFileFetch.ok) return null
- const file = await gitFileFetch.text()
- const lines = file.split('\n')
- const out = []
- for (let i = 0; i < lineCount && i < lines.length; i++) {
- out.push(lines[i])
- }
- return out
-}
-
-async function getTitle(filePath) {
- const firstLines = await readFirstLines(filePath)
- if (firstLines === null || firstLines === undefined || firstLines.length === 0) return null
- let title = firstLines[0]
-
- if (title.substring(0, 2) !== '# ') return null
- title = title
- .substring(1, firstLines[0].length)
- .trim()
- if (title.length < 3)
- return null
- return title
-}
-
-async function getMarkdownMetadata(dir) {
- const dirGitInfoFetch = await fetch(`${giteaApiRepo}contents/${dir}/?ref=${gitRef}`)
- if (!dirGitInfoFetch.ok) return {}
-
- const commits = {}
- const out = {}
-
- const dirGitInfo = await dirGitInfoFetch.json()
- for (const file of dirGitInfo) {
- if (file.name.startsWith('.') || !file.name.endsWith('.md')) continue
- const title = await getTitle(file.path)
- if (title === null) continue
-
- const slug = file.name.replace(/\.md$/, '')
- let mtime = new Date(); // better to have an incorrect recent date than the more incorrect unix time 0 (assuming the host doesn't have messed up clock)
-
-
- if (!(file.last_commit_sha in commits)) {
- const lastCommitSha = await fetch(`${giteaApiRepo}/git/commits/${file.last_commit_sha}`)
- if (lastCommitSha.ok) {
- const commitJson = await lastCommitSha.json()
- commits[commitJson.sha] = (new Date(commitJson.created))
- }
- }
-
- mtime = commits[file.last_commit_sha]
-
- out[slug] = {
- title: title,
- mtime: mtime.toISOString(),
- }
- }
- return out
-}
-
-async function readFilesMetadata(dir) {
- const filePath = jsonFilePath(dir)
- try {
- const fileContent = await fs.readFile(filePath, 'utf-8')
- const metadata = JSON.parse(fileContent)
- return metadata
- } catch {
- return []
- }
-}
-
-async function writeFilesMetadata(filePath, metadata) {
- try {
- await fs.writeFile(filePath, JSON.stringify(metadata, null, 4), 'utf-8')
- } catch (error) {
- console.error(error)
- }
-}
-
-function jsonFilePath(dir) {
- return path.join(process.cwd(), 'public', `${dir}.json`); // ehh
-}
-
-async function generateNotesMetadata() {
- const dir = 'notes'
- await writeFilesMetadata(jsonFilePath(dir), await getMarkdownMetadata(dir))
-}
-
-async function generatePostsMetadata() {
- const dir = 'posts'
- const currMetadata = await readFilesMetadata(dir)
- const generatedMetadata = await getMarkdownMetadata(dir)
- const newMetadata = {}
-
- for (const [name, data] of Object.entries(generatedMetadata)) {
- let otime = new Date()
- if (currMetadata[name]?.otime !== undefined && currMetadata[name]?.otime !== null)
- otime = currMetadata[name].otime ?? otime
- else
- otime = data.mtime ?? otime
-
- newMetadata[name] = { ...data, otime }
- }
- await writeFilesMetadata(jsonFilePath(dir), newMetadata)
-}
-
-async function generateSiteMap() {
- await generateNotesMetadata()
- await generatePostsMetadata()
-
- const sitemap = {
- title: 'PaulW.XYZ',
- pages: await readFilesMetadata('home')
- }
-
- const pages = ['posts', 'notes']
- for (const page of pages) {
- sitemap.pages[page].pages = await readFilesMetadata(page)
- }
-
- await writeFilesMetadata(jsonFilePath('sitemap'), sitemap)
-}
-
-generateSiteMap()
diff --git a/shims.d.ts b/shims.d.ts
deleted file mode 100644
index 5feba8e..0000000
--- a/shims.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-declare module '*.md' {
- const rawmd: string;
- export default rawmd;
-}
-
-declare module '*.txt' {
- const content: string;
- export default content;
-}
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
deleted file mode 100644
index 68b1338..0000000
--- a/src/app/about/page.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import ReactMarkdown from 'react-markdown';
-
-import ReadmeMd from '../../../README.md';
-import License from '../../../LICENSE.txt';
-
-function AboutPage() {
- return (
- <>
-
- Paul's Personal Website.
- You can find me on the following platforms:
-
-
- The original motivation was to just play with Next.js as it pretty much did the things I wanted web pages to do. But it came at the cost of needless complexity. As I use the JavaScript/ECMAScript/Whatever-you-want-to-call-it-script more and more, I am convinced that it is not a platform worth pursuing because the more complex it gets, the less control I have over what it does and this platform and its users seems to be okay with that sort of loss. I have been instead pivoting toward things that impressed and got me interested in working with computers.
- Most services/products are keen on going against the file over app philosophy which entails prioritizing data over software and anticipate and embrace the eventual death of software. People instead want subscription services that barely support open formats and sometimes do not support exporting data to commonly used formats. The goal here is to avoid storing artifacts under locations that are easily not accessible, not under my control, and does not lock me out of using it with other software. The only reason I have not completely abandoned this is thanks to my decision to rely on Markdown files alone. Had it been reliant on any cloud software, I would have started over.
-
- Got any questions, concerns, or issues? Contact me via email: contact [at] paulw [dot] xyz.
-
-
-
- Source for this site is available on GitHub: paulwxyz/www and git.paulw.xyz/xyz/www
- Relevant information regarding the source is available on the repo and is also provided below.
-
-
- README
-
- {ReadmeMd.replace(/^#{6}\s+(.*)\s+$/gm, (s: string, a) => `**${a}**\n`).replace(/^#{1,5} /gm, (s: string) => { return `##${s}` })}
-
-
-
- >
- );
-}
-
-export default AboutPage;
diff --git a/src/app/components/container.tsx b/src/app/components/container.tsx
deleted file mode 100644
index bb8869c..0000000
--- a/src/app/components/container.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-export default function Container(props: { children?: React.ReactNode, ignore?: boolean }) {
- if (props.ignore)
- return <>{props.children}>;
- return (
-
- {props.children}
-
- );
-}
diff --git a/src/app/components/note-entry.tsx b/src/app/components/note-entry.tsx
deleted file mode 100644
index 64dd56d..0000000
--- a/src/app/components/note-entry.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-'use client'
-import Link from 'next/link';
-import { toRelativeDate } from '../lib/date';
-export function NoteEntry({ note }: { note: { title: string, mtime: string, slug: string } }) {
- return (
-
-
-
- {note.title}
-
-
-
- {note.mtime && toRelativeDate(note.mtime)}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/components/quick-links.tsx b/src/app/components/quick-links.tsx
deleted file mode 100644
index bf49a0c..0000000
--- a/src/app/components/quick-links.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import Link from 'next/link';
-import Pages from '../../../public/external.json';
-
-function QuickLinks() {
- return (
-
- {
- Object.entries(Pages).map(([title, link]) => {
- const extern = link.match(/^http/) && `blue extern` || '';
- return (
-
- {title}
-
- );
- })
- }
-
- );
-}
-
-export default QuickLinks;
diff --git a/src/app/components/recent-notes.tsx b/src/app/components/recent-notes.tsx
deleted file mode 100644
index e5ed52e..0000000
--- a/src/app/components/recent-notes.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import Link from "next/link";
-import NotesInfo from '../../../public/notes.json';
-
-function RecentNotes() {
- const notes = Object.entries(NotesInfo)
- .map(([slug, note]) => {
- return {
- slug,
- title: note.title,
- mtime: new Date(note.mtime)
- }
- })
- .sort(
- (a, b) => {
- return b.mtime.getTime() - a.mtime.getTime();
- }
- );
- return (
-
-
Recent Notes
-
- {notes?.slice(0, 5)
- .map(({slug, title, mtime}) => {
- return (
-
- {title}
-
- );
- })
- }
- {
- notes.length > 5 &&
- More...
- }
-
-
- );
-}
-
-export default RecentNotes;
diff --git a/src/app/components/recent-posts.module.css b/src/app/components/recent-posts.module.css
deleted file mode 100644
index 78dbe4b..0000000
--- a/src/app/components/recent-posts.module.css
+++ /dev/null
@@ -1,52 +0,0 @@
-.container {
- border-bottom-left-radius: 0.5rem;
- border-bottom: 1px dashed var(--main-border-color);
- border-left: 1px dashed var(--main-border-color);
- padding-top: 1.25rem;
- margin-left: 0.5rem;
-}
-
-.block {
- margin: 0;
- position: relative;
- display: flex;
- align-items: center;
-}
-
-.block+.block {
- border-top: 1px dashed var(--main-border-color);
-}
-
-.block:first-of-type {
- border-top-right-radius: 0.5rem;
-}
-
-.block:nth-of-type(2n) {
- background-color: var(--table-even-color);
-}
-
-.block:nth-of-type(2n+1) {
- background-color: var(--table-odd-color);
-}
-
-.postTitle {
- flex: 1 1 60%;
- padding: .25rem 0.75rem;
-}
-
-.postDate {
- flex: 1 1;
- display: inline-block;
- text-align: right;
- font-style: italic;
- font-size: 1rem;
- padding: .25rem 0.50rem;
-}
-
-.more {
- text-align: right;
-}
-
-.more a {
- text-decoration: none;
-}
diff --git a/src/app/components/recent-posts.tsx b/src/app/components/recent-posts.tsx
deleted file mode 100644
index dadbd09..0000000
--- a/src/app/components/recent-posts.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import Link from "next/link";
-import { toRelativeDate } from "../lib/date";
-import style from './recent-posts.module.css';
-import PostsInfo from '../../../public/posts.json';
-
-function PostBlock({ slug, otime, title }: { slug: string, otime: string, title: string }) {
- return (
-
-
- {toRelativeDate(new Date(otime))}
-
-
-
- {title}
-
-
-
- );
-}
-
-function RecentPosts() {
- const posts = Object.entries(PostsInfo).reverse();
- if (!posts.length)
- return <>>;
- return (
-
-
Recent Posts
-
- {posts?.slice(0, 10)
- .map(([slug, post]: any, i: number) => {
- return (
-
- );
- })}
-
- {
- posts.length > 10 &&
-
- More...
-
- }
-
- );
-}
-
-export default RecentPosts;
diff --git a/src/app/components/title.module.css b/src/app/components/title.module.css
deleted file mode 100644
index 300b3e1..0000000
--- a/src/app/components/title.module.css
+++ /dev/null
@@ -1,30 +0,0 @@
-.container {
- text-align: center;
- margin: auto;
- background-color: var(--main-background-color);
-}
-
-.container .title {
- border-bottom: 1px solid #FFFFFF;
- max-width: 95%;
- margin: auto;
-}
-
-.nav {
- padding: 0.25rem 0.75rem;
- font-size: 1.25rem;
- box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4);
- position: fixed;
- bottom: 0;
- z-index: 1000;
- margin: 0;
- height: 2.5rem;
- max-height: 2.5rem;
- min-height: 2.5rem;
- min-width: 100%;
- width: 100%;
- background: linear-gradient(to bottom right, #1a3a15, #09351b) no-repeat center center fixed;
- overflow-y: hidden;
- overflow-x: auto;
- white-space: nowrap;
-}
diff --git a/src/app/components/title.tsx b/src/app/components/title.tsx
deleted file mode 100644
index 23d6a2c..0000000
--- a/src/app/components/title.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-'use client'
-import Link from 'next/link';
-import { usePathname } from 'next/navigation';
-import { Fragment } from 'react';
-
-import style from './title.module.css';
-import SiteMap from '../../../public/sitemap.json';
-import { Sites } from '../lib/site';
-
-function createPathElements(ancestors: Array<{ name: string, path: string }>) {
- let currentPath = '';
- return ancestors.map((ancestor, id) => {
- currentPath += `/${ancestor.path}`
- return (
-
- {ancestor.name}
- <> / >
-
- );
- });
-}
-
-export default function Title() {
- const pagePath = usePathname();
- const splitPath: Array<{ name: string, path: string }> = [];
-
- // TODO(Paul): clean this up
- let currRoot: Sites = SiteMap.pages;
- let title: string | null = null;
- if (pagePath && pagePath !== '/') {
- const subPaths = pagePath.split('?')[0].split('#')[0].split('/');
- for (const p of subPaths.slice(1, subPaths.length)) {
- if (!p || !currRoot[p])
- continue;
- splitPath.push({ name: currRoot[p].title, path: p });
-
- if (currRoot === undefined
- || currRoot[p] === undefined
- || currRoot[p].pages === undefined)
- break;
- currRoot = currRoot[p].pages!;
- }
- if (splitPath !== undefined && splitPath.length > 0)
- title = splitPath.pop()!.name;
-
- }
-
- const pathElements = splitPath && createPathElements(splitPath) || <>>;
- return (
- <>
- {/*
- {title && `${title} | PaulW.XYZ` || 'PaulW.XYZ'}
- */}
-
-
- {title || 'PaulW.XYZ'}
-
-
-
- {
- title
- ? <> PaulW.XYZ / {pathElements}{title}>
- : <>PaulW.XYZ />
- }
-
- >
- );
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
deleted file mode 100644
index b2e1b5e..0000000
--- a/src/app/layout.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type {Metadata} from 'next'
-import 'normalize.css'
-import './global.css'
-import Container from './components/container'
-import Title from './components/title'
-
-export default function RootLayout({children,}: Readonly<{children: React.ReactNode}>) {
- return (
-
-
-
-
- {children}
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/app/lib/date.ts b/src/app/lib/date.ts
deleted file mode 100644
index 3400c1d..0000000
--- a/src/app/lib/date.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-// getMonth() method ranges from 0-11 so no reason to account for it
-const months = [
- 'January',
- 'February',
- 'March',
- 'April',
- 'May',
- 'June',
- 'July',
- 'August',
- 'September',
- 'October',
- 'November',
- 'December'
-];
-
-function get12HourTime(pdate: Date | string): string {
- const date = (typeof pdate === 'string') ? new Date(pdate) : pdate;
- let hours = date.getHours();
- const minutes = date.getMinutes();
- let meridiem = 'A.M.';
-
- let strhours = ''
-
- if (hours > 12) {
- hours -= 12;
- meridiem = 'P.M.';
- }
-
- if (hours === 0)
- hours = 12;
-
- return `${hours}:${minutes < 10 ? '0' : ''}${minutes} ${meridiem}`;
-
-}
-
-function toHumanReadableDate(date: Date | string, disable?: { year?: boolean, month?: boolean, day?: boolean }) {
- const oDate = (typeof date === 'string') ? new Date(date) : date;
-
- const year = oDate.getFullYear();
- const month = months[oDate.getMonth()];
- const day = oDate.getDate();
- const suffix = getOrdinalDaySuffix(day)
- let out = '';
- out = !disable?.month ? `${month}` : '';
- out = !disable?.day ? `${out} ${day}${suffix}` : out;
- out = !disable?.year ? `${out}, ${year}` : out;
-
- return out;
-}
-
-
-export function getOrdinalDaySuffix(day: number): string {
- switch (day) {
- case 1:
- case 21:
- case 31:
- return 'st';
- case 2:
- case 22:
- return 'nd';
- case 3:
- case 23:
- return 'rd';
- default:
- return 'th';
- }
-}
-
-export function toLocaleString(pdate: Date | string): string {
- const date = (typeof pdate === 'string') ? new Date(pdate) : pdate;
- return `${toHumanReadableDate(date)} at ${get12HourTime(date)}`;
-}
-
-export function toRelativeDate(date: Date | string): string {
- const oDate = (typeof date === 'string') ? new Date(date) : date;
-
-
- if (!isValid(oDate)) {
- return 'Invalid Date';
- }
-
- const now = new Date();
- const diff = now.getTime() - oDate.getTime();
-
- let tdiff = Math.floor(diff / 1000);
-
- if (tdiff < 0) {
- return toHumanReadableDate(oDate);
- }
-
- if (tdiff < 60) {
- return `${tdiff} seconds ago`;
- }
-
- tdiff = Math.floor(tdiff / 60);
- if (tdiff < 60) {
- return `${tdiff} minute${tdiff === 1 ? '' : 's'} ago`;
- }
-
- tdiff = Math.floor(tdiff / 60);
- if (tdiff < 24) {
- return `${tdiff} hour${tdiff === 1 ? '' : 's'} ago`;
- }
- if (tdiff < 48) {
- return `Yesterday`;
- }
-
- if (oDate.getFullYear() != now.getFullYear())
- return toHumanReadableDate(oDate);
- return toHumanReadableDate(oDate, { year: true });
-}
-
-export function getFullMonth(month: number) {
- if (month >= 1 && month <= 12)
- return months[month];
- return 'Invalid Month';
-}
-
-export function isValid(date: any) {
- return (new Date(date)).toString() !== 'Invalid Date';
-}
-
-const DateTool = {
- toRelativeDate,
- getFullMonth,
- isValid,
- getOrdinalDaySuffix,
- toLocaleString,
-};
-
-export default DateTool;
diff --git a/src/app/lib/read-markdown.ts b/src/app/lib/read-markdown.ts
deleted file mode 100644
index be6443a..0000000
--- a/src/app/lib/read-markdown.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { readFile } from 'fs/promises';
-import path from 'path';
-
-export default async function readMarkdown(directory: string, slug: string, withoutTitle: boolean = false): Promise {
- const content = await readFile(path.join(process.cwd(), directory, `${slug}.md`), 'utf-8');
- if (withoutTitle)
- return content.substring(content.indexOf('\n') + 1, content.length);
- return content;
-}
diff --git a/src/app/lib/site.ts b/src/app/lib/site.ts
deleted file mode 100644
index 9dc0c87..0000000
--- a/src/app/lib/site.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-
-export interface Site {
- title: string;
- pages?: Sites;
- mtime?: string;
- otime?: string;
-}
-
-export interface Sites {
- [slug: string]: Site;
-}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
deleted file mode 100644
index 7e66d40..0000000
--- a/src/app/not-found.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import Link from 'next/link';
-
-import style from '../components/title.module.css';
-
-function NotFoundPage() {
- // TODO: figure out a way to somehow get next to ignore layout in special cases. tried /not-found/page.tsx but it doesn't work :X
- return (
- <>
-{/*
- 404: Not Found | PaulW.XYZ
-
-
-
- Page Not Found
-
-
- PaulW.XYZ / ... ??? / 404: Not Found
- */}
- >
-
- );
-}
-
-export default NotFoundPage;
diff --git a/src/app/notes/[note]/note.module.css b/src/app/notes/[note]/note.module.css
deleted file mode 100644
index 5555c37..0000000
--- a/src/app/notes/[note]/note.module.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.last-updated {
- text-align: right;
- display: block;
- font-style: italic;
- font-size: 1rem;
- margin: 0.5rem 0.75rem;
-}
diff --git a/src/app/notes/[note]/page.tsx b/src/app/notes/[note]/page.tsx
deleted file mode 100644
index 7a79e7b..0000000
--- a/src/app/notes/[note]/page.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import ReactMarkdown from 'react-markdown';
-import { PluggableList } from 'unified';
-
-import remarkGfm from 'remark-gfm';
-import remarkMath from 'remark-math';
-
-import rehypeKatex from 'rehype-katex';
-import rehypeRaw from 'rehype-raw';
-import rehypeSlug from 'rehype-slug';
-import rehypeAutolinkHeadings from 'rehype-autolink-headings';
-import rehypeHighlight from 'rehype-highlight';
-import rehypeHighlightCodeLines, { type HighlightLinesOptions } from 'rehype-highlight-code-lines';
-
-import readMarkdown from '../../lib/read-markdown';
-import { toLocaleString } from '../../lib/date';
-import NotesInfo from '../../../../public/notes.json';
-
-import style from './note.module.css';
-import 'highlight.js/styles/monokai-sublime.css';
-import 'katex/dist/katex.min.css';
-
-interface Note {
- title: string,
- mtime: string,
- content?: string,
-}
-
-interface Notes {
- [slug: string]: Note;
-}
-
-function Markdown({ content }: any) {
- const remarkPlugins: PluggableList = [
- remarkGfm,
- remarkMath,
- ];
- const rehypePlugins: PluggableList = [
- rehypeSlug,
- rehypeAutolinkHeadings,
- rehypeRaw,
- rehypeHighlight,
- rehypeKatex,
- ];
- return
- {content}
-
-}
-
-export default async function Note({params}: {params: Promise<{note: string}>}) {
- const note = (await params).note
- const n = await getNotes(note)
- return (<>
-
- Last updated: {toLocaleString(n.mtime)}
-
-
- >
- );
-}
-
-async function getNotes(name: string) {
- const notesInfo: Notes = NotesInfo;
- return {...notesInfo[name], content: await readMarkdown('notes', name, true)}
-}
diff --git a/src/app/notes/page.tsx b/src/app/notes/page.tsx
deleted file mode 100644
index fbd4ddc..0000000
--- a/src/app/notes/page.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import NotesInfo from '../../../public/notes.json';
-import { NoteEntry } from '../components/note-entry';
-
-function NotesPage() {
- const notes = Object.entries(NotesInfo)
- .map(([slug, note]) => {
- return {
- slug,
- title: note.title,
- mtime: new Date(note.mtime)
- }
- })
- .sort(
- (a, b) => {
- return b.mtime.getTime() - a.mtime.getTime();
- }
- );
-
- return (
- <>
- {
- !notes || notes.length === 0
- && <>No notes found>
- ||
-
- {notes.map(
- (note: any) => {
- return ( );
- }
- )}
-
-
- }
- >
- )
-}
-
-
-export default NotesPage;
diff --git a/src/app/page.tsx b/src/app/page.tsx
deleted file mode 100644
index f7b01ca..0000000
--- a/src/app/page.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import Link from 'next/link';
-import QuickLinks from './components/quick-links';
-import RecentNotes from './components/recent-notes';
-import RecentPosts from './components/recent-posts';
-import RootInfo from '../../public/home.json';
-
-function Nav() {
- const nav = Object.entries(RootInfo);
- return (
-
-
Navigation
- {
- nav.map(([slug, info]) => {
- return {info.title}
- })
- }
-
- )
-}
-
-function HomePage() {
- return (
- <>
-
-
-
-
- >
- )
-}
-
-export default HomePage;
diff --git a/src/app/posts/[post]/page.tsx b/src/app/posts/[post]/page.tsx
deleted file mode 100644
index 35fddb3..0000000
--- a/src/app/posts/[post]/page.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import ReactMarkdown from 'react-markdown';
-import style from '../../styles/post.module.css';
-import PostsInfo from '../../../../public/posts.json';
-import readMarkdown from '../../lib/read-markdown';
-import DateTool, { toLocaleString } from '../../lib/date';
-
-interface IPost {
- title: string;
- mtime: string;
- otime?: string;
-}
-
-function TimeBlock({ mtime, otime }: { mtime: string, otime: string }) {
- const ampm = (h: number) => { if (h >= 12) return 'p.m.'; return 'a.m.'; };
-
- const mdate = new Date(mtime);
- const odate = new Date(otime);
-
- const format = (date: Date) => {
- const day = date.getDay();
- const ord = {DateTool.getOrdinalDaySuffix(date.getDay())} ;
- const month = DateTool.getFullMonth(date.getMonth());
- const year = date.getFullYear();
- const hours = date.getHours() > 12 ? date.getHours() - 12 : date.getHours();
- const minPrefix = date.getMinutes() < 10 ? '0' : '';
- const minutes = date.getMinutes();
- const twelveSfx = ampm(date.getHours());
- return <>{day}{ord} {month} {year} at {hours}:{minPrefix}{minutes} {twelveSfx}>
- };
-
- return (
-
- {
- mtime ?
-
- Last updated: {format(mdate)}
-
- :
- <>>
- }
-
- {format(odate)}
-
-
- );
-}
-// post: IPost & { content: string, cover?: string, otime: string, mtime?: string }
-export default async function Post({ params }: {params: Promise<{post: string}>}) {
- const post = await getPost((await params).post);
- if (!post)
- return <>>;
- return (<>
-
- { post.otime !== post.mtime && post.mtime &&
-
- Last updated: {toLocaleString(post.mtime)}
-
- }
-
- {toLocaleString(post.otime)}
-
-
- {
}
-
-
-
-
- >
- );
-}
-
-async function getPost(n: string) {
- const postsInfo: Record = PostsInfo;
- return {...postsInfo[n], content: await readMarkdown('posts', n, true)};
-}
diff --git a/src/app/posts/page.tsx b/src/app/posts/page.tsx
deleted file mode 100644
index 9607bd6..0000000
--- a/src/app/posts/page.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import Link from 'next/link';
-import date from '../lib/date';
-import PostsInfo from '../../../public/posts.json';
-
-function PostsPage() {
- return (<>
- {Object.keys(PostsInfo).length && || }
- >
- )
-}
-
-function NoPosts() {
- return (
-
**crickets**
-
No posts found...
-
Go Home
-
);
-}
-
-function Posts() {
- const posts = Object.entries(PostsInfo);
- return (
-
-
- {
- posts.map(([slug, post]: [string, any]) => {
- return (
-
- {
- post.mtime && (post.mtime != post.otime) && `Updated ${date.toRelativeDate(new Date(post.mtime))}`
- }
- {date.toRelativeDate(new Date(post.otime))}
-
-
- {post.title}
-
- )
- })
- }
-
-
- )
-}
-
-
-export default PostsPage;
diff --git a/src/app/sitemap/page.tsx b/src/app/sitemap/page.tsx
deleted file mode 100644
index 530f39e..0000000
--- a/src/app/sitemap/page.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import Link from 'next/link';
-import { Sites } from '../lib/site';
-import SiteMap from '../../../public/sitemap.json';
-
-function Desc(props: any) {
- return (
-
- {props.term}
- {props.details}
- {props.children}
-
- );
-}
-
-function traverseMap(head?: Sites, cwd = '', depth = 0) {
- if (!head) return [];
- let elements = [];
- for (const [slug, site] of Object.entries(head)) {
- if (slug === 'sitemap')
- continue;
-
- let details;
- let list;
-
- const path = `${cwd}/${slug}`;
- details = paulw.xyz{path};
- list = traverseMap(site.pages, path, depth + 1);
-
- elements.push({list} )
- }
- return elements;
-}
-
-function SiteMapPage() {
- return <>
- {traverseMap(SiteMap.pages)}
- >;
-}
-
-export default SiteMapPage;
-
diff --git a/src/app/styles/post.module.css b/src/app/styles/post.module.css
deleted file mode 100644
index 230cd6d..0000000
--- a/src/app/styles/post.module.css
+++ /dev/null
@@ -1,41 +0,0 @@
-.imageBlock {
- position: fixed;
- z-index: -1;
- right: 0;
- left: 0;
- top: 0;
- bottom: 0;
- margin: 0;
- padding: 0;
- background-size: cover;
- background-repeat: no-repeat;
- background-position: top center;
- min-height: 100%;
- background-attachment: fixed;
-}
-
-.block {
- font-family: 'EB Garamond', 'Garamond', 'Times New Roman', Times, serif;
- background-color: rgba(13, 17, 23, 0.97);
- margin: 0 auto;
- border-radius: 0;
- font-size: 1.4rem;
- line-height: 2.5rem;
- padding: 2rem 1rem;
-}
-
-.spacer {
- height: 6.25rem;
-}
-
-.background.spacer {
- height: 25rem;
-}
-
-.time {
- text-align: center;
- display: block;
- font-style: italic;
- font-size: 1rem;
- margin: 0.5rem 0.75rem;
-}
diff --git a/tsconfig.json b/tsconfig.json
deleted file mode 100644
index 4c0c1f9..0000000
--- a/tsconfig.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "compilerOptions": {
- "target": "es2023",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "importHelpers": true,
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": ["./src/*"]
- }
- },
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- "lib/slug.js",
- ".next/types/**/*.ts"
- ],
- "exclude": [
- "node_modules"
- ]
-}