diff options
Diffstat (limited to 'http')
63 files changed, 0 insertions, 4010 deletions
diff --git a/http/.gitignore b/http/.gitignore deleted file mode 100644 index ab75dc7..0000000 --- a/http/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -out -vendor -src/microlog/*.microlog.upphtml -src/microlog/*.microlog.upphtml.args -src/blog/*.blog.upphtml -src/blog/*.blog.upphtml.args -bin/pp -share/man/man1/pp.1 -config.ini diff --git a/http/Makefile b/http/Makefile deleted file mode 100644 index 4c50da8..0000000 --- a/http/Makefile +++ /dev/null @@ -1,150 +0,0 @@ -include config.mk - - -############# -# VARIABLES # -############# - -src_files := $(filter-out %.args,$(wildcard src/*.upp*)) $(filter-out %.args,$(wildcard src/**/*.upp*)) -src_files_ext := $(suffix $(src_files)) -out_deps_ext := $(src_files_ext:.upp%=%) - -component_files := $(shell find -L share/components -type f) -layout_files := $(wildcard share/layouts/*.layout.upp*) -layouts := $(notdir $(basename $(basename $(layout_files)))) - -remove_all_layouts = $(call remove_layouts,$(layouts),$(1)) -remove_layouts = $(let first rest,$(1),$(call remove_layout,$(first),$(if $(rest),$(call remove_layouts,$(rest),$(2)),$(2)))) -remove_layout = $(subst .$(1).upp,.,$(2)) -out_deps := $(subst .upp,.,$(call remove_all_layouts,$(addprefix out/,$(src_files:src/%=%)))) -out_deps_without_src := $(filter-out $(out_deps),$(shell find -L out -type f)) -out_deps += $(out_deps_without_src) - -blogs := $(subst blogs/,,$(wildcard blogs/*)) - - -############# -# FUNCTIONS # -############# - -blog_post_target = src/$(blog)/%$(if $(BLOG_LAYOUT_$(blog)),.$(BLOG_LAYOUT_$(blog))).upphtml -blog_post_src = blogs/$(blog)/%.$(BLOG_EXT_$(blog)) $$(wildcard bin/*) -blog_src = $(patsubst \ - blogs/$(blog)/%.$(BLOG_EXT_$(blog)),\ - src/$(blog)/%$(if $(BLOG_LAYOUT_$(blog)),.$(BLOG_LAYOUT_$(blog))).upphtml,\ - $(shell find -L "blogs/$(blog)" -type f -and -name *.$(BLOG_EXT_$(blog)))) -blog_src_without_post_src = $(filter-out $(blog_src),$(shell find -L "src/$(blog)" -type f -and -name *.$(BLOG_LAYOUT_$(blog)).upphtml)) -default_layout_file = $(if \ - $(shell [ -f "share/layouts/default.layout.$(ext)" ] && echo 1),\ - share/layouts/default.layout.$(ext)) -layout_file = $(if $(layout),share/layouts/$(layout).layout.upp$(ext),$(default_layout_file)) - -define out_rule = -out/%.$(ext): src/%$(if $(layout),.$(layout)).upp$(ext) $(layout_file) $(component_files) bin/pp - ./bin/mkws https://joeac.net "$$$$(realpath $$<)" -endef - -define blog_rss_feed_rule = -out/$(blog)/rss.xml: src/$(blog)/rss.uppxml $(blog_src) $(wildcard bin/*) bin/pp - ./bin/mkws https://joeac.net "$$(realpath src/$(blog)/rss.uppxml)" -endef - -define blog_rule = -.PHONY: $(blog) -$(blog): $(blog_src_without_post_src) $(blog_src) out/$(blog).html -endef - -define blog_post_rule = -$(blog_post_target): $(blog_post_src) $(let ext,html,$(layout_file) $(component_files)) - ./bin/blog \ - -i $(blog) \ - $(if $(BLOG_LAYOUT_$(blog)),-l $(BLOG_LAYOUT_$(blog))) \ - $(BLOG_FLAGS_$(blog)) \ - -f $$*.$(BLOG_EXT_$(blog)) -endef - -define blog_index_rule = -out/$(blog).html: src/$(blog).upphtml $(blog_src) $(wildcard bin/*) bin/pp - ./bin/mkws https://joeac.net "$$(realpath src/$(blog).upphtml)" -endef - -define prune_blog_src_file_rule = -$(let rel_basename,$(patsubst src/$(blog)/%.$(BLOG_LAYOUT_$(blog)).upphtml,%,$(file)), -blogs/$(blog)/$(rel_basename).$(BLOG_EXT_$(blog)): - rm $(file) -) -endef - -define prune_blog_out_file_rule = -$(let rel_basename,$(patsubst src/$(blog)/%.$(BLOG_LAYOUT_$(blog)).upphtml,%,$(file)), -$(file): - rm out/$(rel_basename).html -) -endef - -define prune_out_file_rule = -$(let orig_ext,$(patsubst .%,%,$(suffix $(file))), -$(let ext,$(patsubst upp%,%,$(orig_ext)), -$(let rel_basename,$(patsubst out/%.$(orig_ext),%,$(file)), -$(let src_file,src/$(rel_basename).upp$(ext), -$(let src_files_with_layouts,$(foreach layout,$(layouts),src/$(rel_basename).$(layout).upp$(ext)), -$(let public_file,public/$(rel_basename).$(orig_ext), -$(src_file) $(src_files_with_layouts) $(public_file): - rm $(file) -)))))) -endef - - -######### -# RULES # -######### - -all: $(blogs) public out vendor - $(MAKE) out # if blog posts have been pruned, outfiles must be re-calculated - -share/man/man1/pp.1: - cp pp/pp.1 share/man/man1/pp.1 - -.PHONY: out -out: $(out_deps) - -bin/pp: - $(MAKE) --directory=pp && chmod +x pp/pp && cp pp/pp bin/pp - -$(foreach ext,$(sort $(out_deps_ext)),\ - $(eval $(out_rule))) -$(foreach layout,$(layouts),\ - $(foreach ext,$(sort $(out_deps_ext)),\ - $(eval $(out_rule)))) - -$(foreach blog,$(blogs),$(eval $(blog_rule))) -$(foreach blog,$(blogs),$(eval $(blog_index_rule))) - -$(foreach blog,$(blogs),\ - $(foreach layout,$(layouts),\ - $(eval $(call blog_post_rule,$(blog),$(layout))))) -$(foreach blog,$(blogs),\ - $(eval $(call blog_post_rule,$(blog),))) - -$(foreach blog,$(blogs),$(eval $(blog_rss_feed_rule))) - -.PHONY: public -public: $(subst public/,out/,$(shell find -L public/ -type f)) - -out/%: public/% - mkdir -p $(dir $@) && cp $< $@ - -$(foreach file,$(out_deps_without_src),$(eval $(prune_out_file_rule))) -$(foreach blog,$(blogs),$(foreach file,$(blog_src_without_post_src),$(eval $(prune_blog_src_file_rule)))) - -vendor: composer.json composer.lock - composer install - -.PHONY: clean -clean: - rm -rf \ - vendor \ - out \ - $(foreach blog,$(blogs), \ - $(foreach ext,upphtml upphtml.args,\ - src/$(blog)/*$(if $(BLOG_LAYOUT_$(blog)),.$(BLOG_LAYOUT_$(blog))).$(ext))) diff --git a/http/bin/blog b/http/bin/blog deleted file mode 100755 index 46be82f..0000000 --- a/http/bin/blog +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/sh -e -export PATH="$(dirname $(realpath $0)):$PATH" -export BLOGS="$(realpath blogs)" -export SRC="$(realpath src)" -export LAYOUTS="$(realpath share/layouts)" - -usage() -{ - >&2 cat << USAGE -usage: blog -i LOG [-p POSTPROCESS_CMD] [-a EXTRACT_ARGS_CMD] [-l LAYOUT] [-t] [-f FILE_PATTERN] - -options: - - -a EXTRACT_ARGS_CMD - If provided, a shell command to extract arguments to the layout from the - source files. The shell will be provided with a variable, POST_TITLE. - - -f FILE - If provided, a pattern for the files to process. Default: *. - - -i LOG - The name of the source directory under \$BLOGS where log posts live. - - -l LAYOUT - If provided, the layout under \$LAYOUTS to use for the generated - upphtml files. If not provided, uses the default layout. - - -p POSTPROCESS_CMD - If provided, a shell command to post-process the source files into - upphtml. The shell will be provided with a variable, POST_TITLE. - - -t - If enabled, use filenames as post titles -USAGE -} - -extract_args_cmd="head -n 0 | echo \"POST_TITLE=\\\"\${POST_TITLE}\\\"\"" -file_pattern="*" -log_name="" -layout_name="default" -postprocess_cmd="echo" -use_filenames_for_titles=0 - -while getopts "a:f:i:l:p:t" opt -do - case $opt in - a) - extract_args_cmd="$OPTARG" - ;; - f) - file_pattern="$OPTARG" - ;; - i) - log_name="$OPTARG" - ;; - l) - layout_name="$OPTARG" - ;; - p) - postprocess_cmd="$OPTARG" - ;; - t) - use_filenames_for_titles=1 - ;; - esac -done - -if [ -z "${log_name}" ] -then - usage - exit 1 -fi - -if ! [ -d "${BLOGS}/${log_name}" ] -then - >&2 echo "there is no directory '${input_log}' under ${BLOGS}. options: $(ls common)" - usage - exit 1 -fi - -layout="${LAYOUTS}/${layout_name}.layout.upphtml" -if [ "${layout_name}" = "default" ] -then - layout_ext="" -else - layout_ext=".${layout_name}" -fi -if ! [ -f "${layout}" ] -then - echo >&2 "Warning: no layout found at $(basename "${LAYOUTS}")/${layout_name}.layout.upphtml" -fi - -mkdir -p "${SRC}/${log_name}" - -for entry in $(find -L "${BLOGS}/${log_name}" -type f -name "${file_pattern}") -do - basename="$(basename "$entry")" - name="${basename%\.*}" - dest="${SRC}/${log_name}/${name}${layout_ext}.upphtml" - if [ $use_filenames_for_titles -eq 1 ] - then - export POST_TITLE="$name" - cat "$entry" | eval "${postprocess_cmd}" > "$dest" - else - export POST_TITLE="$(grep -e "^# " -m 1 "$entry" | cut -c3-)" - cat "$entry" | grep -e "^# " -v | eval "$postprocess_cmd" > "$dest" - fi - cat "$entry" | eval "$extract_args_cmd" > "${dest}.args" - echo "Processed ${entry#$(dirname ${BLOGS})/} to ${dest#$(dirname ${SRC})/}" -done diff --git a/http/bin/cut_longlog_meta b/http/bin/cut_longlog_meta deleted file mode 100755 index 914f455..0000000 --- a/http/bin/cut_longlog_meta +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -e - -meta="" -read_published_on=0 -while read line -do - meta="${meta} -${line}" - if [ "$(echo "$line" | cut -c1-14)" = "Published on: " ] - then - read_published_on=1 - break - fi -done - -if [ "$read_published_on" -eq 0 ] -then - echo "$meta" - exit 0 -fi - -read line -if [ "$(echo "$line" | cut -c1-14)" = "Updated: " ] -then - meta="${meta} -${line}" -else - echo "$line" -fi - -while read line -do - echo "$line" -done diff --git a/http/bin/extract_longlog_meta b/http/bin/extract_longlog_meta deleted file mode 100755 index 054c994..0000000 --- a/http/bin/extract_longlog_meta +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -e - -POST_TITLE="${POST_TITLE:-}" -DATE_PUBLISHED="" -DATE_UPDATED="" - -while read line -do - if [ "$(echo "$line" | cut -c1-2)" = "# " ] - then - POST_TITLE="$(echo "$line" | cut -c3-)" - elif [ "$(echo "$line" | cut -c1-14)" = "Published on: " ] - then - DATE_PUBLISHED="$(date --date="$(echo "$line" | cut -c15-)" +%Y-%m-%d)" - elif [ "$(echo "$line" | cut -c1-12)" = "Updated on: " ] - then - DATE_UPDATED="$(date --date="$(echo "$line" | cut -c13-)" +%Y-%m-%d)" - fi -done - -if [ -n "$POST_TITLE" ] -then - echo "POST_TITLE=\"${POST_TITLE}\"" -fi - -if [ -n "$DATE_PUBLISHED" ] -then - echo "DATE_PUBLISHED=\"${DATE_PUBLISHED}\"" -fi - -if [ -n "$DATE_UPDATED" ] -then - echo "DATE_UPDATED=\"${DATE_UPDATED}\"" -fi diff --git a/http/bin/gmi2upphtml b/http/bin/gmi2upphtml deleted file mode 100755 index 1631037..0000000 --- a/http/bin/gmi2upphtml +++ /dev/null @@ -1,184 +0,0 @@ -#!/bin/sh -e - -post_title="${POST_TITLE:-}" - -is_in_list=0 -is_in_link_block=0 -is_in_preformatted_block=0 -is_in_blockquote=0 - -if_in_list_then_close() -{ - if [ $is_in_list -ne 0 ] - then - echo "</ul>" - is_in_list=0 - fi -} - -if_in_link_block_then_close() -{ - if [ $is_in_link_block -ne 0 ] - then - echo "</p>" - is_in_link_block=0 - fi -} - -if_in_preformatted_block_then_close() -{ - if [ $is_in_preformatted_block -ne 0 ] - then - echo "</pre>" - is_in_preformatted_block=0 - fi -} - -if_in_blockquote_then_close() -{ - if [ $is_in_blockquote -ne 0 ] - then - echo "</blockquote>" - is_in_blockquote=0 - fi -} - -close_all_open_blocks() -{ - if_in_list_then_close - if_in_link_block_then_close - if_in_preformatted_block_then_close - if_in_blockquote_then_close -} - -escape_upphtml() -{ - echo "$1" \ - | sed "s/&/\\&/g" \ - | sed "s/</\\</g" \ - | sed "s/>/\\>/g" \ - | sed "s/\"/\\"/g" \ - | sed "s/'/\\'/g" \ - | sed "s/\`/\\`/g" \ - | sed "s/\\\$/\\\\\$/g" -} - -close_all_open_blocks_except() -{ - except="$1" - if [ "$except" != "list" ] - then - if_in_list_then_close - fi - if [ "$except" != "link" ] - then - if_in_link_block_then_close - fi - if [ "$except" != "pre" ] - then - if_in_preformatted_block_then_close - fi - if [ "$except" != "blockquote" ] - then - if_in_blockquote_then_close - fi -} - -if [ -z "$post_title" ] -then - read first_line - if [ "$(echo "$first_line" | cut -c1-2)" != "# " ] - then - echo >&2 "Error: no [post_title] argument provided and no top-level heading at top of the document." - exit 1 - fi - post_title="${first_line### }" -fi - -echo "#! -export TITLE=\"${post_title} | joeac's blog\" -export DESCRIPTION=\"${post_title} | joeac's blog\" -pp \"\${SHARE}\"/components/meta.upphtml -#!" - -while read line -do - if [ "$(echo $line | sed "s/[[:space:]]//g")" = "" ] - then - continue - fi - - if [ "$(echo $line | cut -c1)" = "#" ] - then - title="$(escape_upphtml "${line#*# }")" - heading="${line%%$title}" - heading="${heading%% }" - heading_level="${#heading}" - if [ "$(echo "$line" | cut -c$(( $heading_level + 1 )) )" = " " ] - then - close_all_open_blocks - echo "<h${heading_level}>${title}</h${heading_level}>" - continue - fi - fi - - if [ "$(echo "$line" | cut -c1-2)" = "=>" ] - then - close_all_open_blocks_except link - href="$(escape_upphtml "$(echo "$line" | sed "s/^=>[[:space:]]\+//" | sed "s/[[:space:]]\+.*//")")" - title="$(escape_upphtml "${line#=> *${href}}")" - title="${title:-${href}}" - if [ $is_in_link_block -eq 0 ] - then - echo "<p>" - else - echo "<br/>" - fi - is_in_link_block=1 - echo "=> <a href='${href}'>${title}</a>" - continue - fi - - if [ "$( echo "$line" | cut -c1-2)" = "* " ] - then - close_all_open_blocks_except list - if [ $is_in_list -eq 0 ] - then - echo "<ul>" - fi - is_in_list=1 - echo "<li>$(escape_upphtml "$(echo "$line" | cut -c3-)")</li>" - continue - fi - - if [ "$(echo "$line" | cut -c1)" = ">" ] - then - close_all_open_blocks_except blockquote - if [ $is_in_blockquote -eq 0 ] - then - echo "<blockquote>" - fi - is_in_blockquote=1 - echo "<p>$(escape_upphtml "$(echo "$line" | cut -c2-)")</p>" - continue - fi - - if [ "$(echo "$line" | cut -c1-3)" = "\`\`\`" ] - then - close_all_open_blocks_except pre - if [ $is_in_preformatted_block -eq 0 ] - then - echo "<pre>" - else - echo -e "\n" - fi - is_in_preformatted_block=1 - echo "$(escape_upphtml "$(echo "$line" | cut -c4-)")" - continue - fi - - close_all_open_blocks - echo "<p>$(escape_upphtml "${line}")</p>" -done - -close_all_open_blocks diff --git a/http/bin/lmt b/http/bin/lmt Binary files differdeleted file mode 100755 index e0e19ea..0000000 --- a/http/bin/lmt +++ /dev/null diff --git a/http/bin/mkws b/http/bin/mkws deleted file mode 100755 index 228109c..0000000 --- a/http/bin/mkws +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/sh -e -export PATH="$(dirname $(realpath $0)):$PATH" - -usage() { - >&2 printf "usage: %s url [pattern]\\n" "$(basename "$0")" - exit 1 -} - -test $# -lt 1 && usage - -export SRC="$(realpath src)" -export PUBLIC="$(realpath public)" -export SHARE="$(realpath share)" -export LAYOUTS="$(realpath share/layouts)" -export DEFAULT_LAYOUT="${LAYOUTS}/default.layout.upphtml" -export OUT="$(realpath out)" - -if ! test -f "${SRC}"/index.upphtml -then - >&2 printf "no index.upphtml file found\\n" - exit 1 -fi - -PATH_PATTERN="${2:-*}" -for t in $(find -L "${SRC}/" -type f -and -wholename "${PATH_PATTERN}" -and -name "*\.upp*" -and -not -name "*\.upp*\.*") -do - basename="$(basename "$t")" - ext="${basename##*upp}" - - basename_without_ext="${basename%\.upp${ext}}" - penultimate_segment="${basename_without_ext##*\.}" - if [ "$basename_without_ext" != "$penultimate_segment" ] && [ -f "${LAYOUTS}/${penultimate_segment}.layout.upphtml" ] - then - layout="${LAYOUTS}/${penultimate_segment}.layout.upphtml" - name="${basename%\.${penultimate_segment}\.upp${ext}}" - else - layout="${DEFAULT_LAYOUT}" - name="${basename%\.upp${ext}}" - fi - - in_dir="$(dirname "$t")/" - rel_dir="${in_dir#${SRC}/}" - mkdir -p "${OUT}/${rel_dir}/" - - args="$( [ -f "${t}.args" ] && cat "${t}.args" || echo "" )" - if [ "$(head -n 1 "${layout}")" = "#!" ] - then - >"${layout}.tmp" echo " -#! -${args} -$(tail -n +2 "${layout}") -" - else - >"${layout}.tmp" echo " -#! -${args} -#! -$(cat "${layout}") -" - fi - if [ "$ext" = "html" ] - then - pp "${layout}.tmp" "${t}" "${1}" > \ - "${OUT}/${rel_dir}/${name}.${ext}" - else - pp "${t}" "${1}" > \ - "${OUT}/${rel_dir}/${name}.${ext}" - fi - rm "${layout}.tmp" - - echo "Written ${OUT}/${rel_dir}${name}.${ext}" -done diff --git a/http/blogs/blog b/http/blogs/blog deleted file mode 120000 index 908e98e..0000000 --- a/http/blogs/blog +++ /dev/null @@ -1 +0,0 @@ -../../common/longlog/
\ No newline at end of file diff --git a/http/blogs/microlog b/http/blogs/microlog deleted file mode 120000 index 4f95064..0000000 --- a/http/blogs/microlog +++ /dev/null @@ -1 +0,0 @@ -../../common/microlog/
\ No newline at end of file diff --git a/http/composer.json b/http/composer.json deleted file mode 100644 index 98e8628..0000000 --- a/http/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "joeacnet/http", - "require": { - "phpmailer/phpmailer": "^7.1" - }, - "autoload": { - "psr-4": { - "JoeacNet\\Http\\": "php/" - } - }, - "authors": [ - { - "name": "Joe Carstairs", - "email": "me@joeac.net" - } - ] -} diff --git a/http/composer.lock b/http/composer.lock deleted file mode 100644 index 4d72ec7..0000000 --- a/http/composer.lock +++ /dev/null @@ -1,101 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "5145609ea1f80d6d838d0f9649acd36a", - "packages": [ - { - "name": "phpmailer/phpmailer", - "version": "v7.1.1", - "source": { - "type": "git", - "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/1bc1716a507a65e039d4ac9d9adebbbd0d346e15", - "reference": "1bc1716a507a65e039d4ac9d9adebbbd0d346e15", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-filter": "*", - "ext-hash": "*", - "php": ">=5.5.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "doctrine/annotations": "^1.2.6 || ^1.13.3", - "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "phpcompatibility/php-compatibility": "^10.0.0@dev", - "squizlabs/php_codesniffer": "^3.13.5", - "yoast/phpunit-polyfills": "^1.0.4" - }, - "suggest": { - "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", - "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example", - "ext-imap": "Needed to support advanced email address parsing according to RFC822", - "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", - "ext-openssl": "Needed for secure SMTP sending and DKIM signing", - "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", - "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", - "league/oauth2-google": "Needed for Google XOAUTH2 authentication", - "psr/log": "For optional PSR-3 debug logging", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", - "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPMailer\\PHPMailer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-only" - ], - "authors": [ - { - "name": "Marcus Bointon", - "email": "phpmailer@synchromedia.co.uk" - }, - { - "name": "Jim Jagielski", - "email": "jimjag@gmail.com" - }, - { - "name": "Andy Prevost", - "email": "codeworxtech@users.sourceforge.net" - }, - { - "name": "Brent R. Matzelle" - } - ], - "description": "PHPMailer is a full-featured email creation and transfer class for PHP", - "support": { - "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v7.1.1" - }, - "funding": [ - { - "url": "https://github.com/Synchro", - "type": "github" - } - ], - "time": "2026-05-18T08:06:14+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": {}, - "platform-dev": {}, - "plugin-api-version": "2.9.0" -} diff --git a/http/config.mk b/http/config.mk deleted file mode 100644 index 3b42bd7..0000000 --- a/http/config.mk +++ /dev/null @@ -1,7 +0,0 @@ -BLOG_LAYOUT_blog := blog -BLOG_EXT_blog := gmi -BLOG_FLAGS_blog = -p 'cut_longlog_meta | gmi2upphtml' -a extract_longlog_meta - -BLOG_LAYOUT_microlog := microlog -BLOG_EXT_microlog := gmi -BLOG_FLAGS_microlog := -t -p gmi2upphtml diff --git a/http/php/config.php b/http/php/config.php deleted file mode 100644 index cfe91d3..0000000 --- a/http/php/config.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php namespace JoeacNet\Http\Config; - -define("CONFIG", parse_ini_file(dirname(__FILE__, 2) . "/config.ini")); - -function maxDailyEmails(): int|null -{ - $maxDailyEmails = CONFIG["MAX_DAILY_EMAILS"]; - if (!is_numeric($maxDailyEmails)) { - return null; - } - return $maxDailyEmails; -} - -function contactMailbox(): string|null -{ - return (string) CONFIG["CONTACT_MAILBOX"]; -} - -function contactMailboxName(): string|null -{ - return (string) CONFIG["CONTACT_MAILBOX_NAME"]; -} - -function localSmtpFrom(): string|null -{ - return (string) CONFIG["LOCAL_SMTP_FROM"]; -} - -function localSmtpFromName(): string|null -{ - return (string) CONFIG["LOCAL_SMTP_FROM_NAME"]; -} - -function localSmtpHost(): string|null -{ - return (string) CONFIG["LOCAL_SMTP_HOST"]; -} - -function localSmtpPort(): string|null -{ - return (string) CONFIG["LOCAL_SMTP_PORT"]; -} - -function localSmtpUser(): string|null -{ - return (string) CONFIG["LOCAL_SMTP_USER"]; -} - -function localSmtpPass(): string|null -{ - return (string) CONFIG["LOCAL_SMTP_PASSWORD"]; -} - -function dbScheme(): string|null -{ - return (string) CONFIG["DB_SCHEME"]; -} - -function dbPath(): string|null -{ - return (string) CONFIG["DB_PATH"]; -} diff --git a/http/php/db.php b/http/php/db.php deleted file mode 100644 index a60d6c4..0000000 --- a/http/php/db.php +++ /dev/null @@ -1,205 +0,0 @@ -<?php namespace JoeacNet\Http\Db; - -use JoeacNet\Http\Config; - -use PDO; - -require_once __DIR__ . "/config.php"; - -function connectDb(): PDO -{ - $pdo = new PDO(Config\dbScheme() . ":" . Config\dbPath()); - $pdo = createTables($pdo); - return $pdo; -} - -function createTables(PDO $pdo): PDO -{ - $pdo->exec( - <<<SQL - CREATE TABLE IF NOT EXISTS otp ( - userId TEXT NOT NULL, - value TEXT NOT NULL, - createdAtUnixSecs INTEGER NOT NULL, - validUntilUnixSecs INTEGER NOT NULL - ); - SQL - , - ); - - $pdo->exec( - <<<SQL - CREATE TABLE IF NOT EXISTS send_email_token ( - userId TEXT NOT NULL, - value TEXT NOT NULL, - createdAtUnixSecs INTEGER NOT NULL, - validUntilUnixSecs INTEGER NOT NULL - ); - SQL - , - ); - - $pdo->exec( - <<<SQL - CREATE TABLE IF NOT EXISTS sent_emails ( - messageId TEXT NOT NULL, - sentAtUnixSecs INTEGER NOT NULL - ); - SQL - , - ); - - return $pdo; -} - -function countEmailsSentLast24Hours(PDO $db): int -{ - $twentyFourHoursAgo = time() - 24 * 60 * 60; - return (int) $db - ->query( - <<<SQL - SELECT COUNT(*) as count - FROM sent_emails - WHERE sentAtUnixSecs >= $twentyFourHoursAgo; - SQL - , - ) - ->fetchColumn(0); -} - -function insertSendEmailToken(PDO $db, string $userId, string $token) -{ - $createdAtUnixSecs = time(); - $validUntilUnixSecs = $createdAtUnixSecs + 60; - $db - ->prepare( - <<<SQL - INSERT - INTO send_email_token - (userId, value, createdAtUnixSecs, validUntilUnixSecs) - VALUES - (:userId, :token, :createdAtUnixSecs, :validUntilUnixSecs); - SQL - , - ) - ->execute([ - "userId" => $userId, - "token" => $token, - "createdAtUnixSecs" => $createdAtUnixSecs, - "validUntilUnixSecs" => $validUntilUnixSecs, - ]); -} - -function isTokenValid(PDO $db, string $userId, string $token): int -{ - $now = time(); - $stmt = $db->prepare( - <<<SQL - SELECT COUNT(*) AS count - FROM send_email_token - WHERE userId = :userId - AND value = :token - AND validUntilUnixSecs >= :validUntilUnixSecs; - SQL - , - ); - $stmt->execute([ - "userId" => $userId, - "token" => $token, - "validUntilUnixSecs" => $now, - ]); - $validTokenCount = (int) $stmt->fetchColumn(0); - return $validTokenCount > 0; -} - -function deleteAllTokensForUser(PDO $db, string $userId) -{ - $db - ->prepare( - <<<SQL - DELETE - FROM send_email_token - WHERE userId = :userId; - SQL - , - ) - ->execute(["userId" => $userId]); -} - -function deleteAllOtpsForUser(PDO $db, string $userId) -{ - $db - ->prepare( - <<<SQL - DELETE - FROM otp - WHERE userId = :userId; - SQL - , - ) - ->execute(["userId" => $userId]); -} - -function recordSentEmail(PDO $db, string $messageId) -{ - $now = time(); - $stmt = $db->prepare( - <<<SQL - INSERT - INTO sent_emails - (messageId, sentAtUnixSecs) - VALUES - (:messageId, :now); - SQL - , - ); - $stmt->execute(["messageId" => $messageId, "now" => $now]); -} - -function insertOtp(PDO $db, string $userId, string $otp) -{ - $createdAtUnixSecs = time(); - $validUntilUnixSecs = $createdAtUnixSecs + 60 * 5; - $db - ->prepare( - <<<SQL - INSERT - INTO otp - (userId, value, createdAtUnixSecs, validUntilUnixSecs) - VALUES - (:userId, :otp, :createdAtUnixSecs, :validUntilUnixSecs) - SQL - , - ) - ->execute([ - "userId" => $userId, - "otp" => $otp, - "createdAtUnixSecs" => $createdAtUnixSecs, - "validUntilUnixSecs" => $validUntilUnixSecs, - ]); -} - -function isOtpCorrect( - PDO $db, - string $userId, - string $guess, - int $leniencySecs, -): bool { - $stmt = $db->prepare( - <<<SQL - SELECT - COUNT(*) as count - FROM otp - WHERE userId = :userId - AND value = :guess - AND validUntilUnixSecs >= :validUntilUnixSecs - SQL - , - ); - $stmt->execute([ - "userId" => $userId, - "guess" => $guess, - "validUntilUnixSecs" => time() - $leniencySecs, - ]); - return $stmt->fetchColumn(0); -} diff --git a/http/php/mail.php b/http/php/mail.php deleted file mode 100644 index d9c4125..0000000 --- a/http/php/mail.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php namespace JoeacNet\Http\Mail; - -use PHPMailer\PHPMailer\PHPMailer; - -use JoeacNet\Http\Config; - -require_once __DIR__ . "/../vendor/autoload.php"; - -function sendEmail( - string $toEmail, - string $toName, - string $subject, - string $body, - string $replyToEmail = null, - string $replyToName = null, -): string { - $mail = new PHPMailer(); - $mail->isSMTP(); - $mail->Host = Config\localSmtpHost(); - $mail->Port = Config\localSmtpPort(); - $mail->SMTPAuth = true; - $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; - $mail->Username = Config\localSmtpUser(); - $mail->Password = Config\localSmtpPass(); - $mail->setFrom(Config\localSmtpFrom(), Config\localSmtpFromName()); - if (!is_null($replyToEmail)) { - $mail->addReplyTo($replyToEmail, $replyToName); - } - $mail->addAddress($toEmail, $toName); - $mail->isHTML(false); - $mail->Subject = "joeac.net: $subject"; - $mail->Body = "$body"; - $mail->send(); - return $mail->getLastMessageID(); -} diff --git a/http/pp/.gitignore b/http/pp/.gitignore deleted file mode 100644 index c0d72fa..0000000 --- a/http/pp/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -pp -*.o diff --git a/http/pp/LICENSE b/http/pp/LICENSE deleted file mode 100644 index 648cf93..0000000 --- a/http/pp/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -ISC License - -Copyright (c) 2021, Adrian Emil Grigore <adrian.emil.grigore@gmail.com> - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/http/pp/Makefile b/http/pp/Makefile deleted file mode 100644 index afc122f..0000000 --- a/http/pp/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -CC = cc -LD = ${CC} - -PREFIX = /usr/local -MANPREFIX = ${PREFIX}/share/man - -CFLAGS = -Wall -Wextra -pedantic - -pp: pp.o -pp.o: pp.c - -.c.o: - $(CC) $(CPPFLAGS) $< $(CFLAGS) -c -o $@ -.o: - $(LD) $< $(LDFLAGS) -o $@ - -clean: - rm -rf pp *.o - -install: pp pp.1 - mkdir -p $(DESTDIR)$(PREFIX)/bin - mkdir -p $(DESTDIR)$(MANPREFIX)/man1 - cp pp $(DESTDIR)$(PREFIX)/bin/pp - cp pp.1 $(DESTDIR)$(MANPREFIX)/man1/ - chmod 755 $(DESTDIR)$(PREFIX)/bin/pp - -uninstall: - rm $(DESTDIR)$(PREFIX)/bin/pp - rm $(DESTDIR)$(MANPREFIX)/man1/pp.1 diff --git a/http/pp/README.md b/http/pp/README.md deleted file mode 100644 index f69faf1..0000000 --- a/http/pp/README.md +++ /dev/null @@ -1,188 +0,0 @@ -# `pp(1)` - -a preprocessor - -`pp(1)` allows embedding -[`sh(1)`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html) -code in files of any type by nesting it inside the `#!\n` token, where -`\n` is a new line. That means that if you'd like a simple loop or an -`if` inside an HTML file for instance, -you could use `pp(1)`. - -## Basic usage - -For the following code: - - <!doctype html> - <title>pp(1) example</title> - <ul> - #! - i=1 - while test $i -le 10 - do - if test $((i % 2)) -eq 0 - then - #! - <li class=even>$i</li> - #! - else - #! - <li class=odd>$i</li> - #! - fi - i=$((i + 1)) - done - #! - </ul> - -`pp(1)` outputs: - - <!doctype html> - <title>pp(1) example</title> - <ul> - <li class=odd>1</li> - <li class=even>2</li> - <li class=odd>3</li> - <li class=even>4</li> - <li class=odd>5</li> - <li class=even>6</li> - <li class=odd>7</li> - <li class=even>8</li> - <li class=odd>9</li> - <li class=even>10</li> - </ul> - -## Arguments - -You can also pass arguments to it by appending them to the command call: - - <!doctype html> - <title>$1</title> - <p> - #! - echo $2 - #! - </p> - -Calling the above code with -`pp tmp.upphtml 'pp(1) example' 'hello, world'` -will result in: - - <!doctype html> - <title>pp(1) example</title> - <p> - hello world - </p> - -## Pipes - -`pp(1)`'s `stdin` is sent to the child -[`sh(1)`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html) - - <!doctype html> - <title>pp(1) example</title> - <p> - #! - cat - #! - </p> - -`echo 'hello, world' | pp tmp.upphtml` will output: - - <!doctype html> - <title>pp(1) example</title> - <p> - hello, world - </p> - -## Debugging - -`pp(1)` also takes an optional `-d` flag. If passed, `pp(1)` will dump -the generated -[`sh(1)`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html) -code instead of executing it: - - <!doctype html> - <title>pp(1) example</title> - <ul> - #! - i=1 - while test $i -le 10 - do - if test $((i % 2)) -eq 0 - then - #! - <li class=even>$i</li> - #! - else - #! - <li class=odd>$i</li> - #! - fi - i=$((i + 1)) - done - #! - </ul> - -`pp -d tmp.upphtml` will output: - - echo "<!doctype html> - <title>pp(1) example</title> - <ul>" - i=1 - while test $i -le 10 - do - if test $((i % 2)) -eq 0 - then - echo " <li class=even>$i</li>" - else - echo " <li class=odd>$i</li>" - fi - i=$((i + 1)) - done - echo "</ul>" - -## Example - - <!doctype html> - <html lang=en> - <title>pp(1)</title> - <meta charset=UTF-8> - <meta name=viewport content='width=device-width'> - <link rel=stylesheet href=../theme.css> - <body> - <a href=../#myprojects>Home</a> - #! - addcls() { - sed "s/<code>/<code class=language-bash>/g" - } - smu README | addcls - #! - <h2>Files</h2> - <ul> - #! - for f in * - do - case $f in - *.upp*|*.html|*.tgz) - continue - ;; - esac - #! - <li><a href=$f>$f</a></li> - #! - done - #! - </ul> - <h2>Download</h2> - <a href=pp.tgz>pp.tgz</a> - <script src=../prism.js data-manual></script> - <script src=../enhance.js></script> - </body> - </html> - -## See also - -* <https://github.com/radare/spp> -* <https://werc.cat-v.org/docs/rc-template-lang> -* <https://code.9front.org/hg/werc/file/92f7463dac1a/bin/template.awk> diff --git a/http/pp/pp.1 b/http/pp/pp.1 deleted file mode 100644 index a552c97..0000000 --- a/http/pp/pp.1 +++ /dev/null @@ -1,46 +0,0 @@ -.Dd Apr 25, 2019 -.Dt pp 1 -.Os -.Sh NAME -.Nm pp -.Nd preprocessor -.Sh SYNOPSIS -.Nm pp -.Op Fl d -.Ar file -.Op args ... -.Sh DESCRIPTION -The -.Nm -utility processes -.Ar file -to produce output that is used as input to another -program. -.Pp -It executes code enclosed in -.Dq #!\en -delimiters using -.Xr sh 1 . -.Op args ... -are passed to the embbeded script and are available -via the usual -.Em $1, $2, ... -positional parameters. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl d -.Nm -will dump the generated -.Xr sh 1 -code instead of executing it. This is useful for debugging. -.El -.Sh EXIT STATUS -.Ex -std -.Sh EXAMPLES -Preprocess file with arguments: -.Pp -.Dl $ pp file.upphtml a b -.Pp -.Sh SEE ALSO -.Xr sh 1 diff --git a/http/pp/pp.c b/http/pp/pp.c deleted file mode 100644 index be96596..0000000 --- a/http/pp/pp.c +++ /dev/null @@ -1,369 +0,0 @@ -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/wait.h> -#include <unistd.h> - -#if !__APPLE__ -/* - * Appends src to string dst of size dsize (unlike strncat, dsize is the - * full size of dst, not space left). At most dsize-1 characters - * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). - * Returns strlen(src) + MIN(dsize, strlen(initial dst)). - * If retval >= dsize, truncation occurred. - */ -size_t -strlcat(char *dst, const char *src, size_t dsize) -{ - const char *odst = dst; - const char *osrc = src; - size_t n = dsize; - size_t dlen; - - /* Find the end of dst and adjust bytes left but don't go past end. */ - while (n-- != 0 && *dst != '\0') - dst++; - dlen = dst - odst; - n = dsize - dlen; - - if (n-- == 0) - return(dlen + strlen(src)); - while (*src != '\0') { - if (n != 0) { - *dst++ = *src; - n--; - } - src++; - } - *dst = '\0'; - - return(dlen + (src - osrc)); /* count does not include NUL */ -} -#endif /* !__APPLE__ */ - -#define MAXSIZE 64000 -#define MAXINDENTSIZE 80 -#define MAXARGSIZE 20 - -char *argv0; - -int -efgetc(FILE *fp) -{ - int c; - - c = fgetc(fp); - - if (c == EOF && - ferror(fp) != 0) { - perror(argv0); - exit(1); - } - - return c; -} - -int -efseek(FILE *fp, long offset, int whence) -{ - int s; - - s = fseek(fp, offset, whence); - - if (s == -1) { - perror(argv0); - exit(1); - } - - return s; -} - -int -eungetc(int c, FILE *fp) -{ - int uc; - - uc = ungetc(c, fp); - - if (uc == EOF) { - perror(argv0); - exit(1); - } - - return uc; -} - -size_t -estrlcat(char *dst, const char *src, size_t dstsize) -{ - size_t n; - - n = strlcat(dst, src, dstsize); - - if (n >= dstsize) { - fprintf(stderr, "%s: Buffer overflow\n", argv0); - exit(1); - } - - return n; -} - -int -eclose(int d) -{ - int s; - - s = close(d); - - if (s == -1) { - perror(argv0); - exit(1); - } - - return s; -} - -char * -strappend(char *s, char c) -{ - size_t len; - - len = strlen(s); - - s[len] = c; - s[len + 1] = '\0'; - - return s ; -} - -int -shebang(FILE *fp) -{ - int c; - - c = efgetc(fp); - - if (c != '#') { - eungetc(c, fp); - return 0; - } - - c = efgetc(fp); - - if (c != '!') { - eungetc(c, fp); - eungetc('#', fp); - return 0; - } - - c = efgetc(fp); - - if (c == '\n') { - eungetc(c, fp); - eungetc('!', fp); - eungetc('#', fp); - return 0; - } - - for (;;) { - c = efgetc(fp); - if (c == '\n') - break; - } - - return 1; -} - -int -token(FILE *fp, int consume) -{ - int c; - - c = efgetc(fp); - - if (c != '#') { - eungetc(c, fp); - return 0; - } - - c = efgetc(fp); - if (c != '!') { - eungetc(c, fp); - eungetc('#', fp); - return 0; - }; - - c = efgetc(fp); - - if (c != '\n') { - eungetc(c, fp); - eungetc('!', fp); - eungetc('#', fp); - return 0; - } - - if (consume == 0) { - eungetc(c, fp); - eungetc('!', fp); - eungetc('#', fp); - } - - return 1; -} - -size_t -output(char *s) -{ - return estrlcat(s, "echo \"", MAXSIZE); -} - -size_t -outputend(char *s) -{ - return estrlcat(s, "\"\n", MAXSIZE); -} - -int main(int argc, char **argv) -{ - char s[MAXSIZE] = "", indent[MAXINDENTSIZE] = "", - *eargv[MAXARGSIZE] = {"sh", "-c", s, "sh", NULL}; - FILE *fp; - int size, c, i, fd[2], o = 0, status = 0, d = 0; - - - argv0 = argv[0]; - if (argc == 1) { - fprintf(stderr, "usage: %s file [args ...]\n", argv[0]); - return 1; - - } - - if (strcmp(argv[1], "-d") == 0) { - if (argc == 2) { - fprintf(stderr, "usage: %s file [args ...]\n", argv[0]); - return 1; - - } - d = 1; - fp = fopen(argv[2], "r"); - } else { - fp = fopen(argv[1], "r"); - } - - - if (!fp) { - fprintf(stderr, "%s: %s: %s\n", - argv[0], d ? argv[2] : argv[1], strerror(errno)); - return 1; - } - - efseek (fp, 0, SEEK_END); - size = ftell(fp); - - if(size == 0) return 0; - - efseek(fp, -1, SEEK_END); - c = efgetc(fp); - - if (c != '\n') { - fprintf(stderr, "%s: %s: No newline at end of file\n", - argv[0], d ? argv[2] : argv[1]); - exit(1); - } - - efseek(fp, 0, SEEK_SET); - - shebang(fp); - - if (!token(fp, 1)) { - output(s); - o = 1; - } - - for(;;) { - if (token(fp, 1)) { - if (!o) { - c = efgetc(fp); - if (c == EOF) - goto done; - eungetc(c, fp); - output(s); - } else - outputend(s); - o = !o; - } - - c = efgetc(fp); - if (c == EOF) - break; - - if (c == '\n') { - indent[0] = 0; - c = efgetc(fp); - if (c == EOF) - break; - - for (;;) { - if (c != ' ' && c != '\t') - break; - strappend(indent, c); - c = efgetc(fp); - if (c == EOF) - break; - } - - eungetc(c, fp); - - if (token(fp, 0) && o) - continue; - - strappend(s, '\n'); - estrlcat(s, indent, MAXSIZE); - continue; - } - - strappend(s, c); - } - if (o) { - outputend(s); - o = 0; - } - done: - - if(d) { - puts(s); - return 0; - } - - if (pipe(fd) == -1) { - perror(argv0); - return 1; - } - - switch (fork()) { - case -1: - perror(argv0); - return 1; - case 0: - eclose(fd[1]); - eclose(fd[0]); - for (i = 2; i < argc; i++) - eargv[i+2] = argv[i]; - - if (execv("/bin/sh", eargv) == -1) { - perror(argv0); - return 1; - } - break; - default: - eclose(fd[1]); - eclose(fd[0]); - if (wait(&status) == -1) { - perror(argv0); - return 1; - } - } - - return status; -} diff --git a/http/public/do/send_email.php b/http/public/do/send_email.php deleted file mode 100644 index 51d8b19..0000000 --- a/http/public/do/send_email.php +++ /dev/null @@ -1,124 +0,0 @@ -<?php - -use JoeacNet\Http\Config; -use JoeacNet\Http\Db; -use JoeacNet\Http\Mail; - -require_once __DIR__ . "/../../vendor/autoload.php"; -require_once __DIR__ . "/../../php/config.php"; -require_once __DIR__ . "/../../php/db.php"; -require_once __DIR__ . "/../../php/mail.php"; - -$db = Db\connectDb(); - -$payload = json_decode(file_get_contents("php://input"), true); -$name = getNameFromPayloadAndValidate($payload); -$email = getEmailFromPayloadAndValidate($payload); -$message = getMessageFromPayloadAndValidate($payload); -$token = getTokenFromPayloadAndValidate($payload); -$userId = getUserIdFromPayloadAndValidate($payload); - -limitMaxDailyEmails($db); -verifyToken(db: $db, userId: $userId, token: $token); -Db\deleteAllTokensForUser($db, $userId); - -$messageId = Mail\sendEmail( - subject: "joeac.net: $name left a message", - replyToEmail: $email, - replyToName: $name, - toEmail: Config\contactMailbox(), - toName: Config\contactMailboxName(), - body: "$name <$email> sent you a message:\n\n\n$message", -); -Db\recordSentEmail($db, $messageId); -syslog(LOG_INFO, "Sent an email to Joe. Message ID: $messageId"); - -http_response_code(200); -echo $messageId; -exit(); - -/// functions /// - -function verifyToken(PDO $db, string $userId, string $token) -{ - if (!Db\isTokenValid($db, $userId, $token)) { - http_response_code(400); - echo "token is not valid"; - exit(); - } -} - -function limitMaxDailyEmails(PDO $db) -{ - $countEmailsSentLast24Hours = Db\countEmailsSentLast24Hours($db); - if ($countEmailsSentLast24Hours > Config\maxDailyEmails()) { - $msg = - "$countEmailsSentLast24Hours emails have been sent in the last 24 hours, but the max daily load is " . - Config\maxDailyEmails() . - "."; - syslog(LOG_WARNING, $msg); - http_response_code(500); - echo $msg; - exit(); - } -} - -function getNameFromPayloadAndValidate(array $payload): string -{ - $name = (string) $payload["name"]; - if (is_null($name) || $name == "") { - http_response_code(400); - echo "name must not be empty"; - exit(); - } - return $name; -} - -function getEmailFromPayloadAndValidate(array $payload): string -{ - $email = (string) $payload["email"]; - if (is_null($email) || $email == "") { - http_response_code(400); - echo "email must not be empty"; - exit(); - } - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - http_response_code(400); - echo "<$email> is not a valid email address."; - exit(); - } - return $email; -} - -function getMessageFromPayloadAndValidate(array $payload): string -{ - $message = (string) $payload["message"]; - if (is_null($message) || $message == "") { - http_response_code(400); - echo "message must not be empty"; - exit(); - } - return $message; -} - -function getTokenFromPayloadAndValidate(array $payload): string -{ - $token = (string) $payload["token"]; - if (is_null($token) || $token == "") { - http_response_code(400); - echo "token must not be empty"; - exit(); - } - return $token; -} - -function getUserIdFromPayloadAndValidate(array $payload): string -{ - $userId = (string) $payload["userId"]; - if (is_null($userId) || $userId == "") { - http_response_code(400); - echo "userId must not be empty"; - exit(); - } - return $userId; -} diff --git a/http/public/do/send_otp.php b/http/public/do/send_otp.php deleted file mode 100644 index 5ca4d85..0000000 --- a/http/public/do/send_otp.php +++ /dev/null @@ -1,104 +0,0 @@ -<?php - -use JoeacNet\Http\Config; -use JoeacNet\Http\Db; -use JoeacNet\Http\Mail; - -require_once __DIR__ . "/../../php/config.php"; -require_once __DIR__ . "/../../php/db.php"; -require_once __DIR__ . "/../../php/mail.php"; - -const SEND_OTP_TYPES = ["email"]; - -$db = Db\connectDb(); - -$payload = json_decode(file_get_contents("php://input"), true); -$email = getEmailFromPayloadAndValidate($payload); -$name = getNameFromPayloadAndValidate($payload); -$type = getTypeFromPayloadAndValidate($payload); - -limitMaxDailyEmails(db: $db, email: $email, name: $name); - -$otp = strtoupper(bin2hex(random_bytes(3))); -$prettyOtp = substr($otp, 0, 3) . "-" . substr($otp, 3, 6); -Db\insertOtp(db: $db, userId: $email, otp: $otp); - -try { - $messageId = Mail\sendEmail( - toEmail: $email, - toName: $name, - subject: "joeac.net: your OTP is $prettyOtp", - body: <<<BODY - Someone tried to use this email address on joeac.net. If this was you, - your one-time passcode is $prettyOtp. If this wasn't you, you don't need - to do anything. - BODY, - ); -} catch (Exception $e) { - error_log("Email could not be sent: $e"); - http_response_code(500); - echo "Email could not be sent: $e"; - exit(); -} - -Db\recordSentEmail($db, $messageId); -syslog(LOG_INFO, "Sent OTP ($prettyOtp) to $email. Message ID: $messageId"); - -http_response_code(200); -echo $messageId; -exit(); - -/// functions /// - -function limitMaxDailyEmails(PDO $db, string $email, string $name) -{ - $countEmailsSentLast24Hours = Db\countEmailsSentLast24Hours($db); - if ($countEmailsSentLast24Hours > Config\maxDailyEmails()) { - $msg = - "$name <$email> requested an OTP email, but $countEmailsSentLast24Hours emails have already been sent, whereas the max daily load is " . - Config\maxDailyEmails() . - "."; - syslog(LOG_WARNING, $msg); - http_response_code(500); - echo $msg; - exit(); - } -} - -function getNameFromPayloadAndValidate(array $payload): string -{ - $name = (string) $payload["name"]; - if (is_null($name) || $name == "") { - http_response_code(400); - echo "name must not be empty"; - exit(); - } - return $name; -} - -function getEmailFromPayloadAndValidate(array $payload): string -{ - $email = (string) $payload["email"]; - if (is_null($email) || $email == "") { - http_response_code(400); - echo "email must not be empty"; - exit(); - } - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - http_response_code(400); - echo "<$email> is not a valid email address."; - exit(); - } - return $email; -} - -function getTypeFromPayloadAndValidate(array $payload): string -{ - $type = (string) $payload["type"]; - if (!in_array($type, SEND_OTP_TYPES)) { - http_response_code(400); - echo "type must be one of: " . SEND_OTP_TYPES; - exit(); - } - return $type; -} diff --git a/http/public/do/verify_otp.php b/http/public/do/verify_otp.php deleted file mode 100644 index 7f86858..0000000 --- a/http/public/do/verify_otp.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -use JoeacNet\Http\Db; - -require_once __DIR__ . "/../../php/db.php"; - -$db = Db\connectDb(); - -$payload = json_decode(file_get_contents("php://input"), true); -$guess = getGuessFromPayloadAndValidate($payload); -$leniencySecs = getLeniencySecsFromPayloadAndValidate($payload); -$userId = getUserIdFromPayloadAndValidate($payload); - -if ( - !Db\isOtpCorrect( - db: $db, - userId: $userId, - guess: $guess, - leniencySecs: $leniencySecs, - ) -) { - http_response_code(400); - echo "OTP is not valid"; - exit(); -} -Db\deleteAllOtpsForUser($db, $userId); -$token = bin2hex(random_bytes(256)); -Db\insertSendEmailToken(db: $db, userId: $userId, token: $token); -http_response_code(200); -echo $token; -exit(); - -/// functions /// - -function getGuessFromPayloadAndValidate(array $payload): string -{ - $guess = (string) $payload["guess"]; - if (strlen($guess) != 6) { - http_response_code(400); - echo "guess must be six characters long"; - exit(); - } - return $guess; -} - -function getLeniencySecsFromPayloadAndValidate(array $payload): int -{ - if ((bool) $payload["lenient"] ?? false) { - return 60; - } - return 0; -} - -function getUserIdFromPayloadAndValidate(array $payload): string -{ - $userId = (string) $payload["userId"]; - if (is_null($userId) || $userId == "") { - http_response_code(400); - echo "userId must not be empty"; - exit(); - } - return $userId; -} diff --git a/http/public/images b/http/public/images deleted file mode 120000 index ccc7a5b..0000000 --- a/http/public/images +++ /dev/null @@ -1 +0,0 @@ -../../common/images/
\ No newline at end of file diff --git a/http/public/scripts/contact/actions.js b/http/public/scripts/contact/actions.js deleted file mode 100644 index 5a7e32a..0000000 --- a/http/public/scripts/contact/actions.js +++ /dev/null @@ -1,60 +0,0 @@ -export const actions = { - /** - * @param {{type: 'email', name: string, email: string}} - * @returns {Promise<void>} - */ - sendOtp: async ({ type, name, email }) => { - const url = "/do/send_otp.php"; - const req = new Request(url, { - method: "POST", - body: JSON.stringify({ type, name, email }), - }); - const res = await fetch(req); - if (!res.ok) { - throw new Error( - `Request to ${url} failed: ${res.status} ${res.statusText} ${await res.text()}`, - ); - } - }, - - /** - * @param {{guess: string, lenient?: bool, userId: string}} - * @returns {Promise<string | false>} - */ - verifyOtp: async ({ guess, lenient, userId }) => { - const url = "/do/verify_otp.php"; - const req = new Request(url, { - method: "POST", - body: JSON.stringify({ guess, lenient, userId }), - }); - const res = await fetch(req); - - if (res.status === 400) { - return false; - } else if (!res.ok) { - throw new Error( - `Request to ${url} failed: ${res.status} ${res.statusText} ${await res.text()}`, - ); - } - - return res.text(); - }, - - /** - * @param {{email: string, message: string, name: string, userId: string, token: string}} - * @returns {Promise<void>} - */ - sendEmail: async ({ email, message, name, userId, token }) => { - const url = "/do/send_email.php"; - const req = new Request(url, { - method: "POST", - body: JSON.stringify({ email, message, name, userId, token }), - }); - const res = await fetch(req); - if (!res.ok) { - throw new Error( - `Request to ${url} failed: ${res.status} ${res.statusText} ${await res.text()}`, - ); - } - }, -}; diff --git a/http/public/scripts/contact/post-error-message.js b/http/public/scripts/contact/post-error-message.js deleted file mode 100644 index 121cbb6..0000000 --- a/http/public/scripts/contact/post-error-message.js +++ /dev/null @@ -1,34 +0,0 @@ -/** @typedef {import("./selectors.js").Selectors} Selectors */ - -/** - * @param {{contactForm: Selectors}} contactForm - * @param {string} errorMsg - */ -export function postErrorMessageOnContactForm({ contactForm }, errorMsg) { - const errorElem = contactForm.errorElem(); - if (errorElem) { - errorElem.textContent = errorMsg; - errorElem.removeAttribute("hidden"); - } else { - alert(errorMsg); - } -} - -/** - * @param {{otpDialog: Selectors}} otpDialog - * @param {string} errorMsg - */ -export function postErrorMessageOnOtpForm({ otpDialog }, errorMsg) { - const errorElem = otpDialog.errorElem(); - if (errorElem) { - errorElem.textContent = errorMsg; - errorElem.removeAttribute("hidden"); - } else { - alert(errorMsg); - } - /** @type NodeListOf<HTMLInputElement> */ - const inputs = otpDialog.allOtpInputs(); - for (const input of inputs) { - input.value = ""; - } -} diff --git a/http/public/scripts/contact/resend-otp.js b/http/public/scripts/contact/resend-otp.js deleted file mode 100644 index e083d95..0000000 --- a/http/public/scripts/contact/resend-otp.js +++ /dev/null @@ -1,31 +0,0 @@ -/** @typedef {import("./selectors.js").Selectors} Selectors */ - -import { actions } from "./actions.js"; -import { resetResendButton } from "./reset-resend-button.js"; -import { postErrorMessageOnOtpForm } from "./post-error-message.js"; - -/** @typedef {{ resendButtonInterval: NodeJS.Timeout | undefined }} Result */ - -const fallbackErrorMsg = - "No can do. I'm afraid joeac.net is a bit broken right now - sorry about that."; - -/** - * @param {Selectors} selectors - * @param {NodeJS.Timeout | undefined} resetButtonInterval - * @returns {Promise<Result>} - */ -export async function resendOtp(selectors, resetButtonInterval) { - const result = resetResendButton(selectors, resetButtonInterval); - const name = selectors.contactForm.nameElem()?.value; - const email = selectors.contactForm.emailElem().value; - - try { - await actions.sendOtp({ type: "email", name, email }); - } catch (sendOtpError) { - const errorMsg = sendOtpError?.toString() ?? fallbackErrorMsg; - postErrorMessageOnOtpForm(selectors, errorMsg); - throw sendOtpError; - } - - return result; -} diff --git a/http/public/scripts/contact/reset-resend-button.js b/http/public/scripts/contact/reset-resend-button.js deleted file mode 100644 index 47bc9ac..0000000 --- a/http/public/scripts/contact/reset-resend-button.js +++ /dev/null @@ -1,32 +0,0 @@ -/** @typedef {import("./selectors.js").Selectors} Selectors */ - -/** @typedef {{ resendButtonInterval: NodeJS.Timeout | undefined }} Result */ - -/** - * @param {Selectors} selectors - * @param {NodeJS.Timeout | undefined} resetButtonInterval - * @returns {Result} - */ -export function resetResendButton({ otpDialog }, resendButtonInterval) { - clearInterval(resendButtonInterval); - - const resendButton = otpDialog.resendButton(); - if (resendButton) { - resendButton.setAttribute("data-countdown", "60"); - resendButton.setAttribute("disabled", ""); - - resendButtonInterval = setInterval(() => { - const countdown = +(resendButton.getAttribute("data-countdown") ?? 1) - 1; - resendButton.setAttribute("data-countdown", countdown.toString()); - resendButton.textContent = `Resend (${countdown}s)`; - }, 1000); - - setTimeout(() => { - clearInterval(resendButtonInterval); - resendButton.textContent = "Resend"; - resendButton.removeAttribute("disabled"); - }, 1000 * 60); - } - - return { resendButtonInterval }; -} diff --git a/http/public/scripts/contact/selectors.js b/http/public/scripts/contact/selectors.js deleted file mode 100644 index 7676414..0000000 --- a/http/public/scripts/contact/selectors.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @typedef {Object} Selectors - * @property {Object} contactForm - * @property {() => HTMLInputElement} contactForm.emailElem - * @property {() => Element | null} contactForm.errorElem - * @property {() => HTMLInputElement} contactForm.nameElem - * @property {() => HTMLTextAreaElement} contactForm.messageElem - * @property {() => HTMLFormElement | null} contactForm.self - * @property {() => HTMLInputElement | null} contactForm.submitButton - * @property {Object} otpDialog - * @property {() => NodeListOf<HTMLInputElement>} otpDialog.allOtpInputs - * @property {() => Element | null} otpDialog.errorElem - * @property {() => HTMLInputElement | null} otpDialog.firstOtpInput - * @property {() => HTMLFormElement | null} otpDialog.otpForm - * @property {() => Element | null} otpDialog.otpRecipient - * @property {() => Element | null} otpDialog.otpValidUntil - * @property {() => HTMLButtonElement | null} otpDialog.resendButton - * @property {() => HTMLDialogElement} otpDialog.self - * @property {() => HTMLInputElement | null} otpDialog.submitButton - * @property {Object} successSection - * @property {() => Element | null} successSection.email - * @property {() => Element | null} successSection.message - * @property {() => Element | null} successSection.name - * @property {() => Element | null} successSection.self - */ diff --git a/http/public/scripts/contact/submit-contact-form.js b/http/public/scripts/contact/submit-contact-form.js deleted file mode 100644 index a90f420..0000000 --- a/http/public/scripts/contact/submit-contact-form.js +++ /dev/null @@ -1,53 +0,0 @@ -import { actions } from "./actions.js"; -import { postErrorMessageOnContactForm } from "./post-error-message.js"; -import { resetResendButton } from "./reset-resend-button.js"; - -/** @typedef {import("./selectors.js").Selectors} Selectors */ - -const fallbackErrorMsg = - "No can do. I'm afraid joeac.net is a bit broken right now - sorry about that."; - -/** - * @param {Selectors} selectors - * @param {NodeJS.Timeout | undefined} resetButtonInterval - * @returns {Promise<Result>} - */ -export async function submitContactForm(selectors, resendButtonInterval) { - const { contactForm, otpDialog } = selectors; - - const name = contactForm.nameElem()?.value; - const email = contactForm.emailElem().value; - - contactForm.submitButton()?.setAttribute("disabled", ""); - try { - await actions.sendOtp({ type: "email", name, email }); - } catch (sendOtpError) { - const errorMsg = sendOtpError?.toString() ?? fallbackErrorMsg; - postErrorMessageOnContactForm(selectors, errorMsg); - throw sendOtpError; - } - - const otpRecipient = otpDialog.otpRecipient(); - email && otpRecipient && (otpRecipient.textContent = `<${email}>`); - const otpValidUntil = otpDialog.otpValidUntil(); - const validUntil = new Date(Date.now() + 1000 * 60 * 5); - otpValidUntil && - (otpValidUntil.textContent = `until ${validUntil.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`); - - const dialog = otpDialog.self(); - dialog.showModal(); - contactForm.submitButton()?.removeAttribute("disabled"); - dialog.addEventListener("click", function (event) { - const rect = dialog.getBoundingClientRect(); - const isInDialog = - rect.top <= event.clientY && - event.clientY <= rect.top + rect.height && - rect.left <= event.clientX && - event.clientX <= rect.left + rect.width; - if (!isInDialog) { - dialog.close(); - } - }); - - return resetResendButton(selectors, resendButtonInterval); -} diff --git a/http/public/scripts/contact/submit-otp-form.js b/http/public/scripts/contact/submit-otp-form.js deleted file mode 100644 index 8c09314..0000000 --- a/http/public/scripts/contact/submit-otp-form.js +++ /dev/null @@ -1,77 +0,0 @@ -import { actions } from "./actions.js"; -import { postErrorMessageOnOtpForm } from "./post-error-message.js"; - -/** @typedef {import("./selectors.js").Selectors} Selectors */ - -const fallbackErrorMsg = - "No can do. I'm afraid joeac.net is a bit broken right now - sorry about that."; - -/** - * @param {Selectors} selectors - */ -export async function submitOtpForm(selectors) { - const { contactForm, otpDialog, successSection } = selectors; - const otpForm = otpDialog.otpForm(); - otpDialog.submitButton()?.setAttribute("disabled", ""); - - const otpFormData = new FormData(otpForm ?? undefined); - const guess = [ - otpFormData.get("1"), - otpFormData.get("2"), - otpFormData.get("3"), - otpFormData.get("4"), - otpFormData.get("5"), - otpFormData.get("6"), - ].join(""); - - const name = contactForm.nameElem()?.value; - const email = contactForm.emailElem().value; - const message = contactForm.messageElem()?.value; - - let verifyResult; - try { - verifyResult = await actions.verifyOtp({ guess, userId: email }); - } catch (verifyError) { - otpDialog.submitButton()?.removeAttribute("disabled"); - postErrorMessageOnOtpForm( - selectors, - verifyError?.toString() ?? fallbackErrorMsg, - ); - return; - } - - if (verifyResult === false) { - otpDialog.submitButton()?.removeAttribute("disabled"); - postErrorMessageOnOtpForm(selectors, "Incorrect OTP. Check your email?"); - otpDialog.firstOtpInput()?.focus(); - return; - } - const sendmailToken = verifyResult; - - try { - await actions.sendEmail({ - email, - message: message, - name: name, - userId: email, - token: sendmailToken, - }); - } catch (sendEmailError) { - const errorMsg = sendEmailError?.toString() ?? fallbackErrorMsg; - postErrorMessageOnOtpForm(selectors, errorMsg); - otpDialog.submitButton()?.removeAttribute("disabled"); - return; - } - - const sentName = successSection.name(); - const sentEmail = successSection.email(); - const sentMessage = successSection.message(); - sentName && (sentName.textContent = name ?? "???"); - sentEmail && (sentEmail.textContent = email ?? "???"); - sentMessage && (sentMessage.textContent = message ?? "???"); - - contactForm.self()?.remove(); - successSection.self()?.removeAttribute("hidden"); - otpDialog.submitButton()?.removeAttribute("disabled"); - otpDialog.self().close(); -} diff --git a/http/public/scripts/otp-form-wc.js b/http/public/scripts/otp-form-wc.js deleted file mode 100644 index 49e1b02..0000000 --- a/http/public/scripts/otp-form-wc.js +++ /dev/null @@ -1,68 +0,0 @@ -class OtpForm extends HTMLElement { - /** @type MutationObserver? */ - observer; - - constructor() { - super(); - } - - connectedCallback() { - this.observer = new MutationObserver(() => { - this.clearInputs(); - this.configureInputs(); - }); - this.observer.observe(this, { childList: true, subtree: true }); - } - - disconnectedCallback() { - this.observer?.disconnect(); - } - - clearInputs() { - console.log("clearing all inputs"); - /** @type NodeListOf<HTMLInputElement> */ - const inputs = this.querySelectorAll('input:not([type="submit"])'); - for (const input of inputs) { - input.value = ""; - } - } - - configureInputs() { - this.observer?.disconnect(); - /** @type NodeListOf<HTMLInputElement> */ - const inputs = this.querySelectorAll('input:not([type="submit"])'); - /** @type NodeListOf<HTMLFormElement> */ - const form = this.querySelector("form"); - - for (const input of inputs) { - input.addEventListener("focus", () => { - input.select(); - }); - - input.addEventListener("input", () => { - if (input.value.length > 0) { - input.value = input.value.slice(0, 1).toLocaleUpperCase(); - /** @type HTMLInputElement */ - const nextInput = input.nextElementSibling; - if (nextInput) { - nextInput.focus(); - return; - } - - /** @type HTMLInputElement */ - const submitButton = form.querySelector('input[type="submit"]'); - submitButton.focus(); - let areAllInputsEntered = true; - inputs.forEach((input) => { - areAllInputsEntered = areAllInputsEntered && input.value.length > 0; - }); - if (areAllInputsEntered) { - form.requestSubmit(submitButton); - } - } - }); - } - } -} - -customElements.define("otp-form", OtpForm); diff --git a/http/share/components/formatted_date.upphtml b/http/share/components/formatted_date.upphtml deleted file mode 100644 index ba86181..0000000 --- a/http/share/components/formatted_date.upphtml +++ /dev/null @@ -1,4 +0,0 @@ -#! -FORMATTED_DATE="$(date --date "${DATE}" +'%A %-d %B %Y')" -echo "<time datetime='${DATE}' class='${CLASS}' ${ATTRIBUTES}>${FORMATTED_DATE}</time>" -#! diff --git a/http/share/components/head.upphtml b/http/share/components/head.upphtml deleted file mode 100644 index 87728f1..0000000 --- a/http/share/components/head.upphtml +++ /dev/null @@ -1,37 +0,0 @@ -<!-- IndieWeb --> -<link rel="authorization_endpoint" href="https://indieauth.com/auth"> -<link rel="token_endpoint" href="https://tokens.indieauth.com/token"> -<link rel="micropub" href="https://tasty-windows-lick.loca.lt"> - -<!-- Stylesheets --> -<link rel="stylesheet" href="/css/reset.css" /> -<link rel="stylesheet" href="/css/base.css" /> -<link rel="stylesheet" href="/css/hcard.css" /> -<link rel="stylesheet" href="/css/feed.css" /> - -<!-- Global Metadata --> -<meta charset="utf-8" /> -<meta name="viewport" content="width=device-width,initial-scale=1" /> -<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> -<meta name="generator" content="mkws" /> - -<!-- Canonical URL --> -<link rel="canonical" href={canonicalURL} /> - -<!-- Open Graph / Facebook --> -<meta property="og:type" content="website" /> -<meta property="og:url" content={Astro.url} /> -<meta property="og:title" content={title} /> -<meta property="og:description" content={description} /> -<meta property="og:image" content={new URL(image, Astro.url)} /> - -<!-- Twitter --> -<meta property="twitter:card" content="summary_large_image" /> -<meta property="twitter:url" content={Astro.url} /> -<meta property="twitter:title" content={title} /> -<meta property="twitter:description" content={description} /> -<meta property="twitter:image" content={new URL(image, Astro.url)} /> - -<!-- Feeds --> -<link rel="alternate" type="text/xml" title="Blog RSS" href="/blog/rss.xml"> -<link rel="alternate" type="text/xml" title="Links RSS" href="/links/rss.xml"> diff --git a/http/share/components/meta.upphtml b/http/share/components/meta.upphtml deleted file mode 100644 index fbb0155..0000000 --- a/http/share/components/meta.upphtml +++ /dev/null @@ -1,11 +0,0 @@ -#! -if [ -z "${NO_META}" ]; then -#! - -<title>${TITLE}</title> -<meta name='title' content='${TITLE}' /> -<meta name='description' content='${DESCRIPTION}' /> - -#! -fi -#! diff --git a/http/share/components/navbar.upphtml b/http/share/components/navbar.upphtml deleted file mode 100644 index 777cb40..0000000 --- a/http/share/components/navbar.upphtml +++ /dev/null @@ -1,16 +0,0 @@ -<nav> - <ul> - <li> - <a href="/">Home</a> - </li> - <li> - <a href="/blog">Blog</a> - </li> - <li> - <a href="/microlog">Microlog</a> - </li> - <li> - <a href="/contact">Contact</a> - </li> - </ul> -</nav> diff --git a/http/share/components/otp-dialog.upphtml b/http/share/components/otp-dialog.upphtml deleted file mode 100644 index a51333f..0000000 --- a/http/share/components/otp-dialog.upphtml +++ /dev/null @@ -1,28 +0,0 @@ -<script src="/scripts/otp-form-wc.js"></script> -<link rel="stylesheet" href="/css/otp.css" /> - -<dialog class="otp-dialog"> - <otp-form> - <form class="otp-form" class="otp-form"> - <p> - I've sent a six-digit code to <span class="otp-recipient">your email address</span>. - Let me know what it is, so I can confirm this really is your email address. The - code will be valid <span class="otp-valid-until">for five minutes</span>. - </p> - - <p hidden class="error"/> - - <div class="otp-inputs"> - <input maxlength="1" name="1" required autocapitalize="characters" autocomplete="one-time-code" aria-label="First digit of one-time passcode" autofocus> - <input maxlength="1" name="2" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Second digit of one-time passcode"> - <input maxlength="1" name="3" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Third digit of one-time passcode"> - <input maxlength="1" name="4" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Fourth digit of one-time passcode"> - <input maxlength="1" name="5" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Fifth digit of one-time passcode"> - <input maxlength="1" name="6" required autocapitalize="characters" autocomplete="one-time-code" aria-label="Sixth digit of one-time passcode"> - </div> - - <input type="submit" value="Verify"> - <button disabled class="resend-button">Resend (60s)</button> - </form> - </otp-form> -</dialog> diff --git a/http/share/layouts/blog.layout.upphtml b/http/share/layouts/blog.layout.upphtml deleted file mode 100644 index ec6130c..0000000 --- a/http/share/layouts/blog.layout.upphtml +++ /dev/null @@ -1,66 +0,0 @@ -#! -PAGE="$1" -SITE_URL="$2" -DATE_PUBLISHED="${DATE_PUBLISHED:-$(basename "${PAGE}" | cut -c1-10)}" -CANONICAL_URL="${SITE_URL}/blog/$(basename "${PAGE}")" -#! - -<!doctype html> -<html lang="en-GB"> - -<head> -#! -pp "${SHARE}"/components/head.upphtml -#! - -<link rel="stylesheet" href="/css/post.css" /> -</head> - -<body> - -#! -pp "${SHARE}"/components/navbar.upphtml -#! - -<article class='h-entry'> - -<aside> -<span>This is a <a href='/blog'>blog</a> post by <a class='p-author h-card' href='/'>Joe Carstairs</a>.</span> -#! -if [ -z "${DATE_UPDATED}" ]; then -#! -<span>Published: -#! -DATE="${DATE_PUBLISHED}" CLASS="dt-published" pp "${SHARE}/components/formatted_date.upphtml" -#! -.</span> -#! -else -#! -<span>Updated: -#! -DATE="${DATE_UPDATED}" CLASS="dt-updated" pp "${SHARE}/components/formatted_date.upphtml" -#! -.</span> -<span>Originally published: -#! -DATE="${DATE_PUBLISHED}" CLASS="dt-published" pp "${SHARE}/components/formatted_date.upphtml" -#! -.</span> -#! -fi -#! -<span hidden><a class='u-url uid' href='${CANONICAL_URL}'>Permalink</a></span> -</aside> - -<header> -<h1 class='h-name'>${POST_TITLE}</h1> -</header> - -<section class='e-content'> -#! -pp "${PAGE}" -#! -</section> - -</article> diff --git a/http/share/layouts/default.layout.upphtml b/http/share/layouts/default.layout.upphtml deleted file mode 100644 index ba94c58..0000000 --- a/http/share/layouts/default.layout.upphtml +++ /dev/null @@ -1,20 +0,0 @@ -<!doctype html> -<html lang="en-GB"> - -<head> -#! -pp "${SHARE}"/components/head.upphtml -#! -</head> - -<body> -#! -pp "${SHARE}"/components/navbar.upphtml -#! -<main> -#! -pp "$1" "$2" -#! -</main> -</body> -</html> diff --git a/http/share/layouts/microlog.layout.upphtml b/http/share/layouts/microlog.layout.upphtml deleted file mode 100644 index 772db15..0000000 --- a/http/share/layouts/microlog.layout.upphtml +++ /dev/null @@ -1,51 +0,0 @@ -#! -SRC="$1" -SITE_URL="$2" -DATE_PUBLISHED="$(basename "${SRC}" | cut -c1-10)" -CANONICAL_URL="${SITE_URL}/microlog/$(basename "${SRC}")" -#! - -<!doctype html> -<html lang="en-GB"> - -<head> -#! -pp "${SHARE}"/components/head.upphtml -#! - -<link rel="stylesheet" href="/css/post.css" /> -</head> - -<body> - -#! -pp "${SHARE}"/components/navbar.upphtml -#! - -<article class="h-entry"> - -<aside> -<span>This is a <a href='/microlog'>microlog</a> post by <a class='p-author h-card' href='/'>Joe Carstairs</a>.</span> -<span>Published: -#! -DATE="${DATE_PUBLISHED}" CLASS="dt-published" pp "${SHARE}/components/formatted_date.upphtml" -#! -.</span> -<span hidden><a class='u-url uid' href='${CANONICAL_URL}'>Permalink</a></span> -</aside> - -<header> -<h1 class='h-name'> -#! -echo "${POST_TITLE}" -#! -</h1> -</header> - -<section class="e-content"> -#! -pp "${SRC}" -#! -</section> - -</article> diff --git a/http/share/layouts/trivial.layout.upphtml b/http/share/layouts/trivial.layout.upphtml deleted file mode 100644 index 897bfe0..0000000 --- a/http/share/layouts/trivial.layout.upphtml +++ /dev/null @@ -1,3 +0,0 @@ -#! -pp $@ -#! diff --git a/http/share/man/man1/lmt.1 b/http/share/man/man1/lmt.1 deleted file mode 100644 index 755d69b..0000000 --- a/http/share/man/man1/lmt.1 +++ /dev/null @@ -1,55 +0,0 @@ -.Dd Sep 4, 2020 -.Dt lmt -.Os -.Sh NAME -.Nm lmt -.Nd last modification time -.Sh SYNOPSIS -.Nm lmt -.Op Fl f Ar format -.Ar file ... -.Sh DESCRIPTION -The -.Nm -utility prints the last modification time for the given -.Ar file -to standard output. -The information displayed is obtained by calling -.Xr lstat 2 -with the given -argument and evaluating the returned structure. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl f Ar format -The format in which to display the time. -The format string may contain any of the conversion specifications -described in the -.Xr strftime 3 -manual page, as well as any arbitrary text. -A newline -.Pq Ql \en -character is always output after the characters specified by -the format string. -The format string for the default display is: -.Bd -literal -offset indent -%a %b %e %H:%M:%S %Z %Y -.Ed -.El -.Sh EXIT STATUS -.Ex -std -.Sh EXAMPLES -Print last modification time for -.Pa file1 -to standard output: -.Pp -.Dl $ lmt file1 -.Pp -Print last modification time for -.Pa file1 -to standard output in ISO 8601 format: -.Pp -.Dl $ lmt -f "%Y-%m-%dT%H:%M:%S%z" file1 -.Sh SEE ALSO -.Xr stat 1 -.Xr strftime 3 diff --git a/http/share/man/man1/mkws.1 b/http/share/man/man1/mkws.1 deleted file mode 100644 index 5ab337d..0000000 --- a/http/share/man/man1/mkws.1 +++ /dev/null @@ -1,91 +0,0 @@ -.Dd Dec 13, 2019 -.Dt mkws 1 -.Os -.Sh NAME -.Nm mkws -.Nd make web site -.Sh SYNOPSIS -.Nm -.Ar url -.Op path -.Sh DESCRIPTION -The -.Nm -utility uses -.Xr pp 1 -and the theme files in the -.Ev MKWSTHEMEPATH -directory -to preprocess all files in -.Op path -in order to produce a static web site in the current directory. If no -.Op path -is specified, the current directory is used instead. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Ar url -The url of the web site. It is used for generating -.Pa sitemap.xml -. -.It Op path -The path where the unpreprocessed files are located. If no -.Op path -is specified, the current directory is used instead. -.El -.Sh ENVIRONMENT -.Bl -tag -width MKWSTHEMEDIR -.It Ev MKWSTHEMEDIR -The directory containing the theme files. See the -.Em FILES -section for the minimum required files for a theme. If no -.Ev MKWSTHEMEDIR -variable is set -.Pa [path]/share -wil be used. -.It Ev LANG -The language for the website. It will be parsed to fill in the -.Em lang -attribute of the -.Em html -element and then -.Em charset -.Em meta -element. -.El -.Sh FILES -.Bl -tag -width "/tmp/vi.recover -.It Pa $MKWSTHEMEDIR/l.upphtml -The layout file of the website. It contains the -.Em DOCTYPE , -.Em html , -.Em head , -.Em title , -.Em meta -, -.Em link -, -.Em body -elements. -.It Pa $MKWSTHEMEDIR/sitemap.xml -The -.Pa sitemap.txt -template. -.El -.Sh EXIT STATUS -.Ex -std -.Sh EXAMPLES -Make https://example.com web site using the unpreprocessed files in the -current directory -.Pp -.Dl $ mkws https://example.com -.Pp -Make https://example.com web site using the unpreprocessed files in the -.Ar ~/src/ws -directory -.Pp -.Dl $ mkws https://example.com ~/src/ws -.Sh SEE ALSO -.Xr sh 1 -.Xr pp 1 -.Xr locale 1 diff --git a/http/share/man/man1/pp.1 b/http/share/man/man1/pp.1 deleted file mode 120000 index 5f87379..0000000 --- a/http/share/man/man1/pp.1 +++ /dev/null @@ -1 +0,0 @@ -../../../pp/pp.1
\ No newline at end of file diff --git a/http/src/blog.upphtml b/http/src/blog.upphtml deleted file mode 100644 index 8c0e429..0000000 --- a/http/src/blog.upphtml +++ /dev/null @@ -1,76 +0,0 @@ -#! -PAGE="$1" -SITE_URL="$2" -export TITLE="Joe Carstairs" -export DESCRIPTION="Joe Carstairs' personal website" - -POSTS="$(ls --reverse "${SRC}/blog/" | grep "^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\.blog.upphtml$")" -set -- -for post in $POSTS -do - this_year="$( echo "$post" | cut -c1-4 )" - has_this_year=0 - for year in $@ - do - if [ "$this_year" = "$year" ] - then - has_this_year=1 - break - fi - done - if [ $has_this_year -eq 0 ] - then - set -- $@ "$this_year" - fi -done -YEARS="$@" - -pp "${SHARE}"/components/meta.upphtml -#! - -<section class='h-feed'> - -<h1>My blog</h1> - -<aside> -<p>This blog is written by <a class='p-author h-card' href='/'>Joe Carstairs</a>.</p> -<p hidden><a class='u-url' href='${SITE_URL}/blog'>Permalink</a></p> -</aside> - -<section> -<p><a href='/subscribe'>How to subscribe to my logs</a></p> -</section> - -#! -for year in $YEARS; do -#! -<h2>${year}</h2> - -<ul> - -#! -for post in $POSTS; do if [ "$(echo "${post}" | cut -c1-4)" = "$year" ]; then -title="$(grep "^export TITLE=" "${SRC}/blog/${post}" | cut -c15-)" -title="$(echo ${title} | cut -c1-$(( ${#title} - 16 )) )" -date_published="$(echo "${post}" | cut -c1-10)" -href="${SITE_URL}/blog/$(echo "$post" | cut -c1-10).html" -#! - -<li class='h-entry'> -<h3><a class='u-url p-name' href='${href}'>${title}</a></h3> -#! -DATE="${date_published}" CLASS="dt-published" pp "${SHARE}/components/formatted_date.upphtml" -#! -</li> - -#! -fi; done -#! - -</ul> - -#! -done -#! - -</section> diff --git a/http/src/blog/rss.uppxml b/http/src/blog/rss.uppxml deleted file mode 100644 index 78800aa..0000000 --- a/http/src/blog/rss.uppxml +++ /dev/null @@ -1,50 +0,0 @@ -#! -SITE_URL="$1" -POSTS="$(ls --reverse "${SRC}/blog/" | grep "^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\(\.[0-9]\+\)\?\.blog.upphtml$")" -NOW_RFC_822="$(TZ=UT date +"%a, %d %b %Y %H:%M:%S UT")" -#! - -<rss version='2.0'> - -<channel> -<title>Joe Carstairs’ blog</title> -<description>Short posts on random topics I find interesting</description> -<link>${SITE_URL}/blog</link> -<image>${SITE_URL}/images/headshot.webp</image> -<language>en-GB</language> -<pubDate>${NOW_RFC_822}</pubDate> - -#! -for post in $POSTS; do -POST_ID="$(echo ${post} | cut -c1-$(( ${#post} - 13 )) )" -. "${SRC}/blog/${post}.args" -DATE_PUBLISHED_RFC_822="$(TZ=UT date --date="$( echo "${POST_ID}" | cut -c1-10)" +"%a, %d %b %Y %H:%M:%S UT")" -#! -<item> - -<title>${POST_TITLE}</title> -<link>${SITE_URL}/blog/${POST_ID}</link> -<guid isPermaLink='true'>${SITE_URL}/blog/${POST_ID}</guid> -<pubDate>${DATE_PUBLISHED_RFC_822}</pubDate> -<description> -#! -NO_META=1 pp "${SRC}/blog/$post" "${SITE_URL}" \ - | sed "s/&/\\&/g" \ - | sed "s/</\\</g" \ - | sed "s/>/\\>/g" \ - | sed "s/\"/\\"/g" \ - | sed "s/'/\\'/g" \ - | sed "s/\`/\\`/g" \ - | sed "s/\\\$/\\\\\$/g" -#! -</description> -<author>Joe Carstairs</author> - -</item> -#! -done -#! - -</channel> - -</rss> diff --git a/http/src/blog/subscribe.trivial.upphtml b/http/src/blog/subscribe.trivial.upphtml deleted file mode 100644 index e842234..0000000 --- a/http/src/blog/subscribe.trivial.upphtml +++ /dev/null @@ -1,4 +0,0 @@ -#! -SITE_URL="${1}" -#! -<meta http-equiv=refresh content=0;url=${SITE_URL}/subscribe /> diff --git a/http/src/contact.upphtml b/http/src/contact.upphtml deleted file mode 100644 index 1f425a6..0000000 --- a/http/src/contact.upphtml +++ /dev/null @@ -1,96 +0,0 @@ -#! -export TITLE="Contact" -export DESCRIPTION="Contact Joe Carstairs" -pp "${SHARE}"/components/meta.upphtml -#! - -#! -pp "${SHARE}"/components/otp-dialog.upphtml -#! - -<form class=contact-form> - <h1>Contact me</h1> - <p hidden class=error></p> - <label for=name>Name</label> - <input id=name name=name type=text required> - <label for=email>Email</label> - <input id=email name=email type=email required> - <label for=message>Message</label> - <textarea id=message name=message required></textarea> - <input type=submit value=Send> -</form> - -<section class=success hidden=true> - <h1>You sent me a message!</h1> - <p>Thanks for that. I may be in touch.</p> - <p>In case you forgot, your message was this:</p> - <hr /> - <dl> - <dt>Name</dt> <dd class=sentname>???</dd> - <dt>Email</dt> <dd class=sentemail>???</dd> - <dt>Message</dt> <dd class=sentmessage>???</dd> - </dl> -</section> - -<script type=module> - import { resendOtp } from '/scripts/contact/resend-otp.js'; - import { submitContactForm } from '/scripts/contact/submit-contact-form.js'; - import { submitOtpForm } from '/scripts/contact/submit-otp-form.js'; - - function locateOrPanic(selector, desc, root) { - const elem = (root ?? document).querySelector(selector); - if (!elem) { - alert('Technical error: could not locate ' + desc + '. Please let Joe know if you have another means of contacting him.'); - throw new Error('Could not locate ' + desc); - } - return elem; - } - - const contactForm = locateOrPanic('form.contact-form', 'contact form'); - const otpDialog = locateOrPanic('dialog.otp-dialog', 'OTP dialog'); - const otpForm = locateOrPanic('form.otp-form', 'OTP form'); - const successSection = document.querySelector('section.success'); - let resendButtonInterval = undefined; - - const selectors = { - contactForm: { - emailElem: () => locateOrPanic('input[name=email]', 'email input', contactForm), - errorElem: () => contactForm.querySelector('.error'), - nameElem: () => locateOrPanic('input[name=name]', 'name input', contactForm), - messageElem: () => locateOrPanic('textarea[name=message]', 'message textarea', contactForm), - self: () => contactForm, - submitButton: () => contactForm.querySelector('input[type=submit]'), - }, - otpDialog: { - allOtpInputs: () => otpForm.querySelectorAll('input:not([type=submit])'), - errorElem: () => otpDialog.querySelector('.error'), - firstOtpInput: () => otpForm.querySelector('input:not([type=submit]):first-child'), - otpForm: () => otpForm, - otpRecipient: () => otpDialog.querySelector('.otp-recipient'), - otpValidUntil: () => otpDialog.querySelector('.otp-valid-until'), - resendButton: () => otpDialog.querySelector('button.resend-button'), - self: () => otpDialog, - submitButton: () => otpForm.querySelector('input[type=submit]'), - }, - successSection: { - email: () => successSection?.querySelector('.sentemail') ?? null, - name: () => successSection?.querySelector('.sentname') ?? null, - message: () => successSection?.querySelector('.sentmessage') ?? null, - self: () => successSection, - } - }; - - contactForm.addEventListener('submit', async (event) => { - event.preventDefault(); - ({ resendButtonInterval } = await submitContactForm(selectors, resendButtonInterval)); - }); - - selectors.otpDialog.resendButton()?.addEventListener('click', async () => { - ({ resendButtonInterval } = await resendOtp(selectors, resendButtonInterval)); - }); - - otpForm.addEventListener('submit', async (event) => { - event.preventDefault(); - await submitOtpForm(selectors); - }); -</script> diff --git a/http/src/css/base.uppcss b/http/src/css/base.uppcss deleted file mode 100644 index b0e8565..0000000 --- a/http/src/css/base.uppcss +++ /dev/null @@ -1,515 +0,0 @@ -/** Variables */ - -:root { - --colour-primary-10: #060300; - --colour-primary-15: #150800; - --colour-primary-20: #1f1400; - --colour-primary-30: #3c2b00; - --colour-primary-40: #5c4300; - --colour-primary-50: #7f5d00; - --colour-primary-60: #a37800; - --colour-primary-70: #c89500; - --colour-primary-80: #efb300; - --colour-primary-90: #ffd98c; - --colour-primary-95: #ffecc8; - - --colour-hyperlink-10: #000409; - --colour-hyperlink-20: #001829; - --colour-hyperlink-30: #00314d; - --colour-hyperlink-40: #004d75; - --colour-hyperlink-50: #006a9f; - --colour-hyperlink-60: #1388c9; - --colour-hyperlink-70: #41a8ea; - --colour-hyperlink-80: #78c7ff; - --colour-hyperlink-90: #bfe3ff; - --colour-hyperlink-95: #e0f1ff; - - --colour-grey-10: oklch(0.1 0.01 84); - --colour-grey-20: oklch(0.2 0.01 84); - --colour-grey-30: oklch(0.3 0.01 84); - --colour-grey-40: oklch(0.4 0.01 84); - --colour-grey-50: oklch(0.5 0.01 84); - --colour-grey-60: oklch(0.6 0.01 84); - --colour-grey-70: oklch(0.7 0.01 84); - --colour-grey-80: oklch(0.8 0.01 84); - --colour-grey-90: oklch(0.9 0.01 84); - --colour-grey-95: oklch(0.95 0.01 84); - - --colour-error-10: oklch(0.1 0.2 26); - --colour-error-20: oklch(0.2 0.2 26); - --colour-error-30: oklch(0.3 0.2 26); - --colour-error-40: oklch(0.4 0.2 26); - --colour-error-50: oklch(0.5 0.2 26); - --colour-error-60: oklch(0.6 0.2 26); - --colour-error-70: oklch(0.7 0.2 26); - --colour-error-80: oklch(0.8 0.2 26); - --colour-error-90: oklch(0.9 0.2 26); - --colour-error-95: oklch(0.95 0.2 26); - - --colour-warn-10: oklch(0.1 0.2 46); - --colour-warn-20: oklch(0.2 0.2 46); - --colour-warn-30: oklch(0.3 0.2 46); - --colour-warn-40: oklch(0.4 0.2 46); - --colour-warn-50: oklch(0.5 0.2 46); - --colour-warn-60: oklch(0.6 0.2 46); - --colour-warn-70: oklch(0.7 0.2 46); - --colour-warn-80: oklch(0.8 0.2 46); - --colour-warn-90: oklch(0.9 0.2 46); - --colour-warn-95: oklch(0.95 0.2 46); - - --colour-primary-fg: var(--colour-primary-90); - --colour-primary-fg-accent: var(--colour-primary-80); - --colour-primary-bg: var(--colour-primary-10); - --colour-primary-bg-accent: var(--colour-primary-20); - --colour-code-fg: var(--colour-primary-90); - --colour-code-bg: var(--colour-primary-15); - --colour-hyperlink: var(--colour-hyperlink-80); - --colour-grey-fg: var(--colour-grey-70); - --colour-grey-bg: var(--colour-grey-30); - --colour-error-fg: var(--colour-error-90); - --colour-error-bg: var(--colour-error-40); - --colour-warn-fg: var(--colour-warn-20); - --colour-warn-bg: var(--colour-warn-80); - - --font-size-sm: 1rem; - --font-size-base: 1.125rem; - --font-size-md: 1.5rem; - --font-size-lg: 2rem; - --font-size-xl: 3rem; - - --spacing-block-xs: 0.5rem; - --spacing-block-sm: 1.75rem; - --spacing-block-md: 2.5rem; - --spacing-block-lg: 3.5rem; - --spacing-block-xl: 5rem; - --spacing-inline-xs: 0.25rem; - --spacing-inline-sm: 0.5rem; - --spacing-inline-md: 1.5rem; - --spacing-inline-lg: 3rem; - --spacing-inline-xl: 6rem; -} - -/** Light theme */ -@media (prefers-color-scheme: light) { - :root { - --colour-primary-fg: var(--colour-primary-20); - --colour-primary-fg-accent: var(--colour-primary-40); - --colour-primary-bg: var(--colour-primary-95); - --colour-hyperlink: var(--colour-hyperlink-40); - --colour-grey-fg: var(--colour-grey-40); - --colour-grey-bg: var(--colour-grey-80); - --colour-error-fg: var(--colour-error-20); - --colour-error-bg: var(--colour-error-80); - } -} - -/** Base typography */ - -body { - font-size: var(--font-size-base); - color: var(--colour-primary-fg); - font-weight: light; - background-color: var(--colour-primary-bg); - line-height: 1.5; - - /* Geometric Humanist stack from https://modernfontstacks.com */ - font-family: - Avenir, Montserrat, Corbel, 'URW Gothic', source-sans-pro, sans-serif; -} - -small { - font-size: var(--font-size-sm); -} - -:is(p, h1, h2, h3, h4, h5, h6, hr, img, figure, ul, ol, blockquote) { - margin-block-start: var(--spacing-block-sm); -} - -hr { - height: 2px; -} - -/** Base layout */ - -body { - --body-margin-inline-start: var(--spacing-inline-lg); - --body-margin-inline-end: var(--body-margin-inline-start); - --body-margin-block-end: var(--spacing-block-xl); - margin-inline: var(--body-margin-inline-start) var(--body-margin-inline-end); - margin-block-end: var(--body-margin-block-end); -} - -:is(h1, h2, h3, h4, h5, h6) { - text-align: center; -} - -img { - margin-inline: auto; - height: auto; -} - -@media (min-width: 60rem) { - body { - display: grid; - grid-template-columns: - [grid-start media-start] - var(--grid-margin-inline) - [media-end content-start] - minmax(var(--grid-max-content-width), auto) - [content-end grid-end]; - grid-auto-rows: max-content; - column-gap: var(--spacing-block-sm); - max-width: var(--grid-total-width); - - --body-margin-inline-end: 6rem; - --grid-margin-inline: 6rem; - --grid-total-width: 48rem; - --grid-max-content-width: calc( - var(--grid-total-width) - var(--body-margin-inline-start) - - var(--grid-margin-inline) - var(--spacing-block-sm) - - var(--grid-margin-inline) - ); - } - - :is(main, article, nav) { - display: grid; - grid-column: grid; - grid-template-columns: subgrid; - - > :is(section, header, aside, form) { - display: grid; - grid-template-columns: subgrid; - grid-column: grid; - - > :not(.not-grid-content) { - grid-column: content; - } - } - } - - :is(h1, h2, h3, h4, h5, h6) { - text-align: start; - } -} - -@media (min-width: 80rem) { - body { - grid-template-columns: - [grid-start media-start] - var(--grid-margin-inline) - [media-end content-start] - minmax(auto, var(--grid-max-content-width)) - [content-end margin-start] - auto - [margin-end grid-end]; - - --grid-total-width: 80rem; - --grid-max-content-width: 40rem; - } -} - -/** Headings */ - -h1 { - font-size: var(--font-size-xl); - font-weight: 900; - margin-block-start: 0; -} - -h2 { - font-size: var(--font-size-lg); - font-weight: 900; - margin-block-start: var(--spacing-block-xl); -} - -h3, -h4, -h5, -h6 { - font-size: var(--font-size-md); - font-weight: 600; - margin-block-start: var(--spacing-block-lg); -} - -/** Hyperlinks */ - -a:is(:link, :visited) { - color: var(--colour-hyperlink); - text-decoration: underline; - display: inline; -} - -a:hover { - text-decoration: wavy; -} - -/** Definition lists */ -dl { - margin-block-start: var(--spacing-block-md); -} - -dl dt { - font-weight: bold; -} - -dl dd + dt { - margin-block-start: var(--spacing-block-md); -} - -/** figcaptions */ - -figcaption { - font-size: var(--font-size-sm); - font-style: italic; - text-align: center; -} - -/** Lists */ - -:is(ol, ul) { - margin-inline-start: var(--spacing-inline-lg); -} - -/** Navigation bar */ - -nav { - margin-block: var(--spacing-block-sm); -} - -nav ul { - grid-column: media-start / content-end; - display: flex; - justify-content: center; - gap: var(--spacing-inline-md); - list-style: none; - margin: 0; -} - -@media (min-width: 36rem) { - nav { - display: grid; - grid-template-columns: subgrid; - } - - nav ul { - justify-content: end; - } -} - -/** Emphasis */ - -strong { - font-weight: bold; - font-style: italic; -} - -/** Blog feed */ - -/** Block quotes */ -blockquote { - padding-inline-start: var(--spacing-inline-lg); - border-inline-start: 2px solid var(--colour-primary-fg); - font-style: italic; -} - -blockquote :first-child { - margin-block-start: 0; -} - -blockquote footer { - font-style: initial; -} - -blockquote :is(b, strong) { - font-style: italic; - font-weight: bold; -} - -blockquote :is(i, em) { - font-style: normal; -} - -/** Small caps */ -.small-caps { - font-variant: small-caps; -} - -/** Pre-formatted blocks */ -pre { - border: 2px solid var(--colour-primary-fg); - border-radius: 2px; - background-color: var(--colour-code-bg) !important; - margin-block-start: var(--spacing-block-sm); - padding-inline: var(--spacing-inline-sm); - padding-block: var(--spacing-block-xs); -} - -/** Code blocks */ -code { - border: 2px solid var(--colour-primary-fg); - border-radius: 2px; - padding-inline: var(--spacing-inline-xs); - color: var(--colour-code-fg); - background-color: var(--colour-code-bg); - font-size: var(--font-size-sm); -} -pre code { - border: none; - border-radius: none; - padding: none; -} - -/* verse */ -.verse { - span { - display: block; - } -} -.verse--hanging-indents { - span + span:not(.not-hanging) { - margin-inline-start: var(--spacing-inline-md); - } -} - -/* block-comment */ -block-comment { - display: grid; - grid-template-columns: subgrid; - grid-column: grid; - - > * { - grid-column: content; - } - - > blockquote:last-of-type { - padding-block: var(--spacing-block-sm); - padding-inline: var(--spacing-inline-md); - overflow-y: scroll; - border: 2px solid var(--colour-primary-fg); - --colour-scroll-shadow: color-mix( - in srgb, - var(--colour-primary-fg), - transparent 20% - ); - - background: - linear-gradient(var(--colour-primary-bg) 30%, transparent) center top, - linear-gradient(transparent, var(--colour-primary-bg) 70%) center bottom, - radial-gradient( - farthest-side at 50% 0, - var(--colour-scroll-shadow), - transparent - ) - center top, - radial-gradient( - farthest-side at 50% 100%, - var(--colour-scroll-shadow), - transparent - ) - center bottom; - background-repeat: no-repeat; - background-size: - 100% 2rem, - 100% 2rem, - 100% 1rem, - 100% 1rem; - background-attachment: local, local, scroll, scroll; - - > :first-child { - margin-block-start: 0; - } - } -} - -@media (min-width: 80rem) { - block-comment > blockquote:last-of-type { - grid-column: margin; - max-height: 67vh; - } -} - -/* forms */ -form { - margin-inline: auto; - max-width: max-content; - - :is(button, fieldset, input, label, object, output, select, textarea, img) { - display: block; - } - - label { - margin-block-end: var(--spacing-block-xs); - } - - * + :is(label, input[type='submit'], button) { - margin-block-start: var(--spacing-block-md); - } - - :is(input[type='submit'], input[type='button'], button) { - padding-inline: var(--spacing-inline-sm); - max-width: max-content; - &:not(dialog *) { - margin-inline-start: auto; - } - } - - :is(input[type='email'], input[type='text'], select) { - max-width: 100%; - width: 16rem; - } - - textarea { - max-width: 100%; - width: 40rem; - } -} - -/* dialogs */ - -dialog { - background: var(--colour-primary-bg-accent); - border: 2px solid var(--colour-primary-fg-accent); - bottom: auto; - color: var(--colour-primary-fg); - left: calc(0.5 * (100vw - min(90vw, 36rem))); - margin: 0; - padding-block: var(--spacing-block-sm); - padding-inline: var(--spacing-inline-sm); - text-align: center; - top: auto; - width: min(90vw, 36rem); -} - -@media (min-width: 80rem) { - dialog { - left: calc( - var(--body-margin-inline-start) + var(--grid-margin-inline) + 2 * - var(--spacing-inline-md) - ); - } -} - -/* utilities */ -:is( - .para-spacing-tight:is(p, h1, h2, h3, h4, h5, h6, hr, img, figure, ul, ol), - .para-spacing-tight :is(p, h1, h2, h3, h4, h5, h6, hr, img, figure, ul, ol) -) { - margin-block-start: var(--spacing-block-xs); -} - -.error { - background: var(--colour-error-bg); - border: 2px solid var(--colour-error-fg); - color: var(--colour-error-fg); - padding-block: var(--spacing--block-sm); - padding-inline: var(--spacing-inline-sm); -} - -.warn { - background: var(--colour-warn-bg); - border: 2px solid var(--colour-warn-fg); - color: var(--colour-warn-fg); - padding-block: var(--spacing--block-sm); - padding-inline: var(--spacing-inline-sm); - --colour-hyperlink: var(--colour-hyperlink-40); -} diff --git a/http/src/css/feed.uppcss b/http/src/css/feed.uppcss deleted file mode 100644 index 60fcd6d..0000000 --- a/http/src/css/feed.uppcss +++ /dev/null @@ -1,45 +0,0 @@ -/* Assumes there is at most one level of subheading for sub-dividing entries */ -.h-feed :is(h2, h3, h4, h5, h6) { - margin-block-start: var(--spacing-block-md); -} - -.h-feed ul:has(> .h-entry) { - list-style-type: none; - margin-inline: 0; - - ul { - list-style-type: disc; - } -} - -.h-feed .h-entry { - display: flex; - flex-direction: column; -} - -.h-feed .h-entry + .h-entry { - margin-block-start: var(--spacing-block-md); -} - -.h-feed .h-entry > * { - order: 1; - margin: 0; -} - -.h-feed .h-entry .dt-published { - order: 0; - font-size: var(--font-size-sm); -} - -.h-feed .h-entry .p-name { - font-size: var(--font-size-md); - font-weight: 400; -} - -.h-feed .full-feed-link { - text-align: end; -} - -.h-feed :is(a.full-feed-link, .full-feed-link a)::after { - content: ' >'; -} diff --git a/http/src/css/hcard.uppcss b/http/src/css/hcard.uppcss deleted file mode 100644 index 7908bc8..0000000 --- a/http/src/css/hcard.uppcss +++ /dev/null @@ -1,57 +0,0 @@ -.h-card div:has(img) { - width: 6rem; - height: 6rem; - margin-inline: auto; -} - -.h-card img { - width: 6rem; - height: 6rem; - border-radius: 1rem; - filter: contrast(1.25); -} - -.h-card div:has(img)::after { - /* Colour overlay */ - background-color: var(--colour-primary-80); - opacity: 0.3; - - /* Same size and shape as the img */ - border-radius: 1rem; - width: 6rem; - height: 6rem; - - /* Positioned on top of the img */ - display: block; - position: relative; - top: -6rem; - - /* A content value is needed to get the ::after to render */ - content: ''; -} - -@media (min-width: 36rem) { - .h-card { - grid-column: media-start / content-end; - display: grid; - grid-template-columns: subgrid; /** Subgrid of main column layout */ - grid-template-rows: min-content 1fr; - grid-template-areas: - 'empty heading' - 'photo text'; - column-gap: var(--spacing-block-sm); - } - - .h-card div:has(img) { - grid-area: photo; - margin-block-start: var(--spacing-block-sm); - } - - .h-card header { - grid-area: heading; - } - - .h-card__text { - grid-area: text; - } -} diff --git a/http/src/css/otp.uppcss b/http/src/css/otp.uppcss deleted file mode 100644 index 47a74d3..0000000 --- a/http/src/css/otp.uppcss +++ /dev/null @@ -1,29 +0,0 @@ -.otp-inputs { - display: flex; - flex-direction: row; - gap: var(--spacing-inline-sm); - justify-content: center; - - input { - border: none; - border-block-end: 2px solid var(--colour-grey-fg); - font-size: var(--font-size-lg); - text-align: center; - width: var(--font-size-lg); - } -} - -.otp-form { - align-items: center; - display: flex; - flex-direction: column; - gap: var(--spacing-block-sm); - - > * { - margin: 0; - } - - input[type='submit'] { - max-width: max-content; - } -} diff --git a/http/src/css/post.uppcss b/http/src/css/post.uppcss deleted file mode 100644 index 436c13e..0000000 --- a/http/src/css/post.uppcss +++ /dev/null @@ -1,8 +0,0 @@ -.p-summary { - font-style: italic; - font-size: var(--font-size-sm); -} - -h1 { - margin-block-start: var(--spacing-block-md); -} diff --git a/http/src/css/reset.uppcss b/http/src/css/reset.uppcss deleted file mode 100644 index 03df124..0000000 --- a/http/src/css/reset.uppcss +++ /dev/null @@ -1,93 +0,0 @@ -/* Based on Andy Bell’s More Modern CSS Reset: https://piccalil.li/blog/a-more-modern-css-reset/ */ - -/* Box sizing rules */ -*, -*::before, -*::after { - box-sizing: border-box; -} - -/* Prevent font size inflation */ -html { - -moz-text-size-adjust: none; - -webkit-text-size-adjust: none; - text-size-adjust: none; -} - -/* Remove default margin in favour of better control in authored CSS */ -body, -h1, -h2, -h3, -h4, -p, -figure, -blockquote, -dl, -dd { - margin-block: 0; -} - -/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */ -ul[role='list'], -ol[role='list'] { - list-style: none; -} - -/* Set core body defaults */ -body { - min-height: 100vh; -} - -/* Set shorter line heights on headings and interactive elements */ -h1, -h2, -h3, -h4, -button, -input, -label { - line-height: 1.1; -} - -/* Balance text wrapping on headings */ -h1, -h2, -h3, -h4 { - text-wrap: balance; -} - -/* Make images easier to work with */ -img, -picture { - max-width: 100%; - display: block; -} - -/* Inherit fonts for inputs and buttons */ -input, -button, -textarea, -select { - font: inherit; -} - -/* Make sure textareas without a rows attribute are not tiny */ -textarea:not([rows]) { - min-height: 10em; -} - -/* Anything that has been anchored to should have extra scroll margin */ -:target { - scroll-margin-block: 5ex; -} - -* { - margin: 0; - padding: 0; -} - -[hidden] { - display: none; -} diff --git a/http/src/error.upphtml b/http/src/error.upphtml deleted file mode 100644 index 057006f..0000000 --- a/http/src/error.upphtml +++ /dev/null @@ -1,14 +0,0 @@ -#! -export TITLE="Got lost?" -export DESCRIPTION="Error page for Joe Carstairs' personal website" -pp "${SHARE}"/components/meta.upphtml -#! - -<section> - <h1>Got lost?</h1> - - <p> - If you’re on this page, something’s probably gone wrong. Try going to - my <a href='/'>homepage</a> instead. - </p> -</section> diff --git a/http/src/index.upphtml b/http/src/index.upphtml deleted file mode 100644 index fec3774..0000000 --- a/http/src/index.upphtml +++ /dev/null @@ -1,47 +0,0 @@ -#! -export TITLE="Joe Carstairs" -export DESCRIPTION="Joe Carstairs' personal website" -pp "${SHARE}"/components/meta.upphtml -#! - -<section class="h-card"> - <div> - <img class="u-photo" src="/images/headshot.webp" height="96" width="96" /> - </div> - - <header> - <h1>Joe Carstairs</h1> - <div hidden> - <a class="p-name u-url u-uid" href="https://joeac.net" rel="me"> - Permalink - </a> - </div> - </header> - - <div class="h-card__text"> - <p> - Hi! 👋 My name is <span class="p-given-name">Joe</span> - <span class="p-family-name">Carstairs</span>. I’m a - <span class="p-job-title">software developer</span> at - <a class="p-org" href="https://www.scottlogic.com">Scott Logic</a>, - a Divinity student at the University of Edinburgh, - a lapsed fiddle player and a very occasional poet. - </p> - - <p> - HMU with your thoughts on Elizabethan poetry, connecting to the - Internet, and the Apocalypse of Saint John. - </p> - - <p> - <small> - Or get me on - <a href="https://www.facebook.com/joe.carstairs.5" rel="me">Facebook</a>, - <a href="https://mastodon.social/@joe_carstairs" rel="me">Mastodon</a>, - <a href="https://www.linkedin.com/in/joe-carstairs-0aa936277" rel="me">LinkedIn</a>, - <a href="https://bsky.app/profile/joeacarstairs.bsky.social" rel="me">BlueSky</a>, - or <a href="https://github.com/joeacarstairs" rel="me">GitHub</a>. - </small> - </p> - </div> -</section> diff --git a/http/src/links/rss.trivial.upphtml b/http/src/links/rss.trivial.upphtml deleted file mode 100644 index 52becef..0000000 --- a/http/src/links/rss.trivial.upphtml +++ /dev/null @@ -1,4 +0,0 @@ -#! -SITE_URL="${1}" -#! -<meta http-equiv=refresh content=0;url=${SITE_URL}/microlog/rss.xml /> diff --git a/http/src/microlog.upphtml b/http/src/microlog.upphtml deleted file mode 100644 index aa3cfea..0000000 --- a/http/src/microlog.upphtml +++ /dev/null @@ -1,84 +0,0 @@ -#! -PAGE="$1" -SITE_URL="$2" -export TITLE="Joe Carstairs" -export DESCRIPTION="Joe Carstairs' personal website" - -POSTS="$(ls --reverse "${SRC}/microlog/" | grep "^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\(\.[0-9]\+\)\?\.microlog.upphtml$")" -set -- -for post in $POSTS -do - this_year="$( echo "$post" | cut -c1-4 )" - has_this_year=0 - for year in $@ - do - if [ "$this_year" = "$year" ] - then - has_this_year=1 - break - fi - done - if [ $has_this_year -eq 0 ] - then - set -- $@ "$this_year" - fi -done -YEARS="$@" - -pp "${SHARE}"/components/meta.upphtml -#! - -<main> - -<section class='h-feed'> - -<h1>My microlog</h1> - -<aside> -<p>This microlog is written by <a class='p-author h-card' href='/'>Joe Carstairs</a>.</p> -<p hidden><a class='u-url' href='${SITE_URL}/microlog'>Permalink</a></p> -</aside> - -<section> -<p><a href='/subscribe'>How to subscribe to my logs</a></p> -</section> - -#! -for year in $YEARS; do -#! -<h2>${year}</h2> - -<ul> - -#! -for post in $POSTS; do if [ "$(echo "${post}" | cut -c1-4)" = "$year" ]; then -title="$(grep "^export TITLE=" "${SRC}/microlog/${post}" | cut -c15-)" -title="$(echo "${title}" | cut -c1-$(( ${#title} - 16)) )" -date_published="$(echo "${post}" | cut -c1-10)" -href="${SITE_URL}/microlog/$(echo ${post} | cut -c1-$(( ${#post} - 17 )) ).html" -#! - -<li class='h-entry'> -<h3><a class='u-url p-name' href='${href}'>${title}</a></h3> -<section class='e-content'> -#! -pp "${SRC}/microlog/${post}" "${SITE_URL}" -#! -</section> -#! -DATE="${date_published}" CLASS="dt-published" ATTRIBUTES="hidden" \ - pp "${SHARE}/components/formatted_date.upphtml" -#! -</li> - -#! -fi; done -#! - -</ul> - -#! -done -#! - -</section> diff --git a/http/src/microlog/rss.uppxml b/http/src/microlog/rss.uppxml deleted file mode 100644 index 20a6ced..0000000 --- a/http/src/microlog/rss.uppxml +++ /dev/null @@ -1,50 +0,0 @@ -#! -SITE_URL="$1" -POSTS="$(ls --reverse "${SRC}/microlog/" | grep "^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\(\.[0-9]\+\)\?\.microlog.upphtml$")" -NOW_RFC_822="$(TZ=UT date +"%a, %d %b %Y %H:%M:%S UT")" -#! - -<rss version='2.0'> - -<channel> -<title>Joe Carstairs’ microlog</title> -<description>My private X feed I guess?</description> -<link>${SITE_URL}/microlog</link> -<image>${SITE_URL}/images/headshot.webp</image> -<language>en-GB</language> -<pubDate>${NOW_RFC_822}</pubDate> - -#! -for post in $POSTS; do -POST_ID="$(echo ${post} | cut -c1-$(( ${#post} - 17 )) )" -. "${SRC}/microlog/${post}.args" -DATE_PUBLISHED_RFC_822="$(TZ=UT date --date="$( echo "${POST_ID}" | cut -c1-10)" +"%a, %d %b %Y %H:%M:%S UT")" -#! -<item> - -<title>${POST_TITLE}</title> -<link>${SITE_URL}/microlog/${POST_ID}</link> -<guid isPermaLink='true'>${SITE_URL}/microlog/${POST_ID}</guid> -<pubDate>${DATE_PUBLISHED_RFC_822}</pubDate> -<description> -#! -NO_META=1 pp "${SRC}/microlog/$post" "${SITE_URL}" \ - | sed "s/&/\\&/g" \ - | sed "s/</\\</g" \ - | sed "s/>/\\>/g" \ - | sed "s/\"/\\"/g" \ - | sed "s/'/\\'/g" \ - | sed "s/\`/\\`/g" \ - | sed "s/\\\$/\\\\\$/g" -#! -</description> -<author>Joe Carstairs</author> - -</item> -#! -done -#! - -</channel> - -</rss> diff --git a/http/src/sitemap.uppxml b/http/src/sitemap.uppxml deleted file mode 100644 index bfcf8d8..0000000 --- a/http/src/sitemap.uppxml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'> -#! -for f in $(find "${OUT}" -name "*.html") -do -#! -<url> -#! -loc=$1/$(basename "${f}") -#! -<loc>${loc}</loc> -#! -lastmod=$(lmt -f '%Y-%m-%dT%H:%M:%SZ' "${f}" | cut -d' ' -f1) -#! -<lastmod>${lastmod}</lastmod> -<priority>1.0</priority> -</url> -#! -done -#! -</urlset> diff --git a/http/src/subscribe.upphtml b/http/src/subscribe.upphtml deleted file mode 100644 index 606e5c5..0000000 --- a/http/src/subscribe.upphtml +++ /dev/null @@ -1,91 +0,0 @@ -#! -PAGE="$1" -SITE_URL="$2" -export TITLE="How to subscribe to joeac's blog" -export DESCRIPTION="How to subscribe to joeac's blog" -#! - -<section> -<h1>How to subscribe to my logs</h1> - -<p> -The short version: use my <a href='/blog/rss.xml'>blog RSS feed</a> and -<a href='/microlog/rss.xml'>microlog RSS feed</a>, or my -<a href='gemini://joeac.net/logs/loglog.gmi'>gemlog</a>. -</p> - -<p> -If neither of those things means anything to you, no worries. You could just -look at my website every once in a while, but that gets old. A better way is to -use my RSS file. Here's how. -</p> - -<p> -An RSS file, if you don't know, is a file containing a list of posts in a format -that's so stupid even computers can understand it. Mine is at -<a href='https://joeac.net/rss.xml'>https://joeac.net/rss.xml</a>, -and I've also got one for just my blog posts at -<a href='https://joeac.net/blog/rss.xml'>https://joeac.net/blog/rss.xml</a>, -and one just for microlog posts at -<a href='https://joeac.net/microlog/rss.xml'>https://joeac.net/microlog/rss.xml</a>. -Since computers can understand it, you can get software to read it automatically -and tell you when I've made a new blog post. That's much less hassle than having -to check manually yourself! -</p> - -<p> -I'm not the only person serving RSS files, of course. You might not have heard -of them, but they're actually all over the Web. In the early days of the Web, -everyone was using them, so they've ended up built in to a whole load of -foundational Web technology, and hence ubiquitous, even if most Web users have -forgotten they exist. Once you've subscribed to my logs, you can subscribe to -basically any blog, podcast, or YouTube channel, and a lot of other Websites for -news, organisations, whatever. Point a well-designed RSS aggregator or RSS -reader at most Websites, and it'll automatically find an associated RSS feed. -</p> - -<p> -If you're not persuaded, try reading -<a href='https://doctorow.medium.com/you-should-be-using-an-rss-reader-76aed31151f9'> -Cory Doctorow explaining why RSS is ridiculously good -</a>. He's terrifyingly persuasive. Also, read his novels! -</p> - -<p> -If you just want to give it a go quickly, probably the quickest way is to -use someone else's RSS aggregator that they'll let you use as a service. -<a href='https://feedly.com/news-reader'>Feedly</a> and -<a href='https://www.inoreader.com'>Inoreader</a> have free tiers. -</p> - -<p> -If you want to have a little more control, you can self-host an RSS -reader, like <a href='https://tt-rss.org'>Tiny Tiny RSS</a>, -<a href='https://miniflux.app'>Miniflux</a>, or -<a href='https://freshrss.org'>FreshRSS</a>. But why would you do that? -Self-hosting is complicated! Why not just use a normal, local app? Here are -some options for that you might want to investigate. All these are active -free and open-source software projects at the time of writing. -</p> - -<ul> -<li><a href='https://hyliu.me/fluent-reader'>Fluent Reader</a> (Linux, Windows, Mac, also mobile I think?)</li> -<li><a href='https://www.rssowl.org'>RSSOwl</a> (Linux, Windows, Mac)</li> -<li><a href='https://capyreader.com'>Capy Reader</a> (Android)</li> -<li><a href='https://apps.kde.org/en-gb/akregator'>Akregator</a> (Linux, Windows (unstable))</li> -<li><a href='https://gfeeds.gabmus.org'>GFeeds</a> (Linux)</li> -<li><a href='https://lzone.de/liferea'>Liferea</a> (Linux)</li> -</ul> - -<p> -For the terminally geeky, there is also -<a href='https://newsboat.org'>Newsboat</a>. I used it for ages, it works -fabulously, but it's all in the terminal, so you have to be OK with that. -</p> - -<p> -You can also just enter 'RSS feed reader' into your favourite search -engine, software distribution, or app store and see what comes up. Try -adding 'FOSS' into the query to prioritise free, open-source software. -</p> -</section> |
