From 0c0f2492268d7a98c27050759e7a11af847e0a1c Mon Sep 17 00:00:00 2001 From: "N0\\A" Date: Wed, 1 Apr 2026 12:35:26 +0200 Subject: [PATCH] feat: modular building and search --- kewt.sh | 1144 +------------------------------------ lib/builder.sh | 707 +++++++++++++++++++++++ lib/commands.sh | 140 +++++ lib/config.sh | 159 ++++++ lib/generator.sh | 289 ++++++++++ lib/search.js | 44 ++ site/site.conf | 6 +- styles/kewt.css | 215 +++++++ tools/build-standalone.sh | 4 +- 9 files changed, 1591 insertions(+), 1117 deletions(-) create mode 100644 lib/builder.sh create mode 100644 lib/commands.sh create mode 100644 lib/config.sh create mode 100644 lib/generator.sh create mode 100644 lib/search.js diff --git a/kewt.sh b/kewt.sh index 903df56..255be87 100755 --- a/kewt.sh +++ b/kewt.sh @@ -5,32 +5,6 @@ die() { exit 1 } -usage() { - invoked_as=$(basename "${KEWT_INVOKED_AS:-$0}") - cat <] [--to ] - $invoked_as [src] [out] - $invoked_as --new [title] - $invoked_as --update [dir] - $invoked_as --post - $invoked_as --generate-template - $invoked_as --version - $invoked_as --help - -Options: - --help Show this help message. - --new [title] Create a new site directory (default: site) - --update [dir] Update site.conf and template.html with latest defaults (defaults to current directory) - --post Create a new empty post file in the configured posts_dir with current date and time as name - --generate-template [path] Generate a new template file at (default: template.html) - --version Show version information. - --from Source directory (default: site) - --to Output directory (default: out) - --watch, -w Watch for file changes and rebuild automatically. - --serve, -s [port] Start a local HTTP server after building (default port: 8000). -EOF -} - script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd) awk_dir="$script_dir/awk" @@ -38,177 +12,10 @@ KEWT_TMPDIR=$(mktemp -d "/tmp/kewt_run.XXXXXX") trap 'rm -rf "$KEWT_TMPDIR"' EXIT trap 'exit 0' HUP INT TERM -DEFAULT_CONF='title = "kewt" -style = "kewt" -lang = "en" -draft_by_default = false -dir_indexes = true -single_file_index = true -flatten = false -order = "" -home_name = "Home" -show_home_in_nav = true -nav_links = "" -nav_extra = "" -footer = "made with kewt" -logo = "" -display_logo = false -display_title = true -logo_as_favicon = true -favicon = "" -generate_page_title = true -error_page = "not_found.html" -versioning = false -enable_header_links = true -base_url = "" -generate_feed = false -feed_file = "rss.xml" -posts_dir = "" -posts_per_page = 12 -custom_admonitions = "" -cw_hide_url = true' - -DEFAULT_TMPL=' - - - - - {{TITLE}} - - - {{HEAD_EXTRA}} - - - - -
-

{{HEADER_BRAND}}

- -
- - - -
{{CONTENT}}
-
{{FOOTER}}
- -' - - -generate_template() { - _gt_path="$1" - [ -e "$_gt_path" ] && die "File '$_gt_path' already exists." - _gt_dir=$(dirname "$_gt_path") - [ -d "$_gt_dir" ] || mkdir -p "$_gt_dir" - printf '%s\n' "$DEFAULT_TMPL" > "$_gt_path" - echo "Generated template at '$_gt_path'." - exit 0 -} - -create_new_site() { - new_title="$1" - new_dir="site" - [ -n "$new_title" ] && new_dir="$new_title" - - [ -e "$new_dir" ] && die "Target '$new_dir' already exists." - - mkdir -p "$new_dir" - printf '%s\n' "$DEFAULT_CONF" > "$new_dir/site.conf" - printf '%s\n' "$DEFAULT_TMPL" > "$new_dir/template.html" - printf "# _kewt_ website\n" > "$new_dir/index.md" - - if [ -n "$new_title" ]; then - AWK_NEW_TITLE="$new_title" awk -f "$awk_dir/update_site_conf.awk" "$new_dir/site.conf" > "$new_dir/site.conf.tmp" && mv "$new_dir/site.conf.tmp" "$new_dir/site.conf" - fi - - echo "Created new site at '$new_dir'." - exit 0 -} - -create_new_post() { - post_src_dir="$1" - post_user_title="$2" - - target_dir="$post_src_dir" - if [ -n "$posts_dir" ]; then - target_dir="$post_src_dir/$posts_dir" - fi - - mkdir -p "$target_dir" - - base_filename="$(date +%Y-%m-%d-%H-%M)" - filename="${base_filename}.md" - file_path="$target_dir/$filename" - - counter=1 - while [ -e "$file_path" ]; do - filename="${base_filename}_${counter}.md" - file_path="$target_dir/$filename" - counter=$((counter + 1)) - done - - post_date_val="$(date "+%Y-%m-%d %H:%M")" - if [ -n "$post_user_title" ]; then - printf -- '---\ntitle = "%s"\ndate = "%s"\ndraft = %s\n---\n# %s\n' "$post_user_title" "$post_date_val" "$draft_by_default" "$post_user_title" > "$file_path" - else - printf -- '---\ndate = "%s"\ndraft = %s\n---\n' "$post_date_val" "$draft_by_default" > "$file_path" - fi - - echo "Created new post at '$file_path'." - exit 0 -} - -update_site() { - update_dir="${1:-.}" - [ -d "$update_dir" ] || die "Directory '$update_dir' does not exist." - - target_conf="$update_dir/site.conf" - target_tmpl="$update_dir/template.html" - - # Generate default site.conf - default_conf="$KEWT_TMPDIR/default_site.conf" - printf '%s\n' "$DEFAULT_CONF" > "$default_conf" - - # Update site.conf - if [ ! -f "$target_conf" ]; then - echo "No site.conf found in '$update_dir'; nothing to update." - else - added=0 - while IFS= read -r line; do - case "$line" in - ''|'#'*) continue ;; - *=*) ;; - *) continue ;; - esac - key=$(printf '%s' "${line%%=*}" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') - if ! grep -q "^[[:space:]]*${key}[[:space:]]*=" "$target_conf"; then - printf '%s\n' "$line" >> "$target_conf" - echo " Added: $key" - added=$((added + 1)) - fi - done < "$default_conf" - if [ "$added" -eq 0 ]; then - echo "site.conf is already up to date." - else - echo "Added $added new key(s) to '$target_conf'." - fi - fi - - # Update template.html - if [ -f "$target_tmpl" ]; then - default_tmpl="$KEWT_TMPDIR/default_template.html" - printf '%s\n' "$DEFAULT_TMPL" > "$default_tmpl" - if cmp -s "$default_tmpl" "$target_tmpl" 2>/dev/null; then - echo "template.html is already up to date." - else - cp "$default_tmpl" "${target_tmpl}.default" - echo "template.html has local changes; saved latest default as '${target_tmpl}.default'." - echo "" - diff "$target_tmpl" "${target_tmpl}.default" || true - fi - fi - - exit 0 -} +. "$script_dir/lib/config.sh" +. "$script_dir/lib/commands.sh" +. "$script_dir/lib/generator.sh" +. "$script_dir/lib/builder.sh" src="" out="" @@ -237,6 +44,32 @@ while [ $# -gt 0 ]; do echo "kewt version git" exit 0 ;; + --dump-zsh-completions) + cat <<'EOFCOMPS' +#compdef kewt +_kewt() { + local -a args + args=( + '--help[Show help message]' + '(-h)--help[Show help message]' + '(-)--new[Create a new site directory]' + '(-)--update[Update site.conf and template.html with latest defaults]' + '(-)--post[Create a new empty post file in the configured posts_dir]' + '(-)--generate-template[Generate a new template file]' + '(-v --version)'{-v,--version}'[Show version information]' + '--from[Source directory]:directory:_directories' + '--to[Output directory]:directory:_directories' + '(-w --watch)'{-w,--watch}'[Watch for file changes and rebuild automatically]' + '(-s --serve)'{-s,--serve}'[Start a local HTTP server after building]::port:' + ) + + _arguments -S -C $args '*: :_directories' +} + +_kewt "$@" +EOFCOMPS + exit 0 + ;; --post) post_mode="true" if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then @@ -299,8 +132,6 @@ done [ "$new_mode" = "true" ] && create_new_site "$new_title" - - if [ -z "$src" ]; then if [ "$post_mode" = "true" ] && [ -f "./site.conf" ]; then src="." @@ -412,105 +243,6 @@ while read -r kp; do done < "$KEWT_TMPDIR/kewt_preserve" rm -f "$KEWT_TMPDIR/kewt_preserve" -generate_nav() { - dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | AWK_SRC="$1" awk -f "$awk_dir/collect_dir_info.awk") - find_cmd="find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" - if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ]; then - find_cmd="$find_cmd && echo \"$1/$posts_dir/index.md\"" - fi - eval "$find_cmd" | sort -u | AWK_SRC="$1" AWK_SINGLE_FILE_INDEX="$single_file_index" AWK_FLATTEN="$flatten" AWK_ORDER="$order" AWK_HOME_NAME="$home_name" AWK_SHOW_HOME_IN_NAV="$show_home_in_nav" AWK_DINFO="$dinfo" awk -f "$awk_dir/generate_sidebar.awk" -} - -title="kewt" -style="kewt" -lang="en" -draft_by_default="false" -footer="made with kewt" -dir_indexes="true" -single_file_index="true" -flatten="false" -order="" -home_name="Home" -show_home_in_nav="true" -nav_links="" -nav_extra="" -footer="made with kewt" -logo="" -display_logo="false" -display_title="true" -logo_as_favicon="true" -favicon="" -generate_page_title="true" -error_page="not_found.html" -versioning="false" -enable_header_links="true" -base_url="" -generate_feed="false" -feed_file="rss.xml" -posts_dir="" -posts_per_page="12" -custom_admonitions="" -cw_hide_url="true" - -load_config() { - [ -f "$1" ] || return - while IFS= read -r line; do - case "$line" in - ''|'#'*) continue ;; - *=*) ;; - *) continue ;; - esac - - key=${line%%=*} - val=${line#*=} - - key=$(printf '%s' "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') - val=$(printf '%s' "$val" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') - case "$val" in - \"*\") - val=${val#\"}; val=${val%\"} - val=$(printf '%s' "$val" | sed 's/\\"/\"/g; s/\\\\/\\/g') - ;; - \'*\') - val=${val#\'}; val=${val%\'} - val=$(printf '%s' "$val" | sed "s/\\\\'/'/g; s/\\\\/\\/g") - ;; - esac - - case "$key" in - title) title="$val" ;; - style) style="${val#/}" ;; - dir_indexes) dir_indexes="$val" ;; - single_file_index) single_file_index="$val" ;; - flatten) flatten="$val" ;; - order) order="$val" ;; - home_name) home_name="$val" ;; - show_home_in_nav) show_home_in_nav="$val" ;; - nav_links) nav_links="$val" ;; - nav_extra) nav_extra="$val" ;; - footer) footer="$val" ;; - logo) logo="${val#/}" ;; - display_logo) display_logo="$val" ;; - display_title) display_title="$val" ;; - logo_as_favicon) logo_as_favicon="$val" ;; - favicon) favicon="${val#/}" ;; - generate_page_title) generate_page_title="$val" ;; - error_page) error_page="${val#/}" ;; - versioning) versioning="$val" ;; - enable_header_links) enable_header_links="$val" ;; - base_url) base_url="$val" ;; - generate_feed) generate_feed="$val" ;; - feed_file) feed_file="${val#/}" ;; - posts_dir) posts_dir="${val#/}" ;; - posts_per_page) posts_per_page="$val" ;; - custom_admonitions) custom_admonitions="$val" ;; - cw_hide_url) cw_hide_url="$val" ;; - lang) lang="$val" ;; - draft_by_default) draft_by_default="$val" ;; - esac - done < "$1" -} - load_config "./site.conf" load_config "$src/site.conf" @@ -525,87 +257,6 @@ if [ "$versioning" = "true" ]; then asset_version="?v=$(date +%s)" fi -escape_html_text() { - printf '%s' "$1" | sed \ - -e 's/&/\&/g' \ - -e 's//\>/g' -} - -escape_html_attr() { - printf '%s' "$1" | sed \ - -e 's/&/\&/g' \ - -e 's/"/\"/g' \ - -e 's//\>/g' -} - -parse_frontmatter() { - _fm_file="$1" - _fm_out="$KEWT_TMPDIR/fm_vals.txt" - : > "$_fm_out" - awk -v fm_out="$_fm_out" -f "$awk_dir/frontmatter.awk" "$_fm_file" > /dev/null - fm_title="" - fm_date="" - fm_draft="" - fm_description="" - fm_content_warning="" - while IFS='=' read -r _fk _fv; do - case "$_fk" in - title) fm_title="$_fv" ;; - date) fm_date="$_fv" ;; - draft) fm_draft="$_fv" ;; - description) fm_description="$_fv" ;; - content_warning) fm_content_warning="$_fv" ;; - esac - done < "$_fm_out" - rm -f "$_fm_out" -} - -nav_links_html() { - [ -n "$nav_links" ] || return - - old_ifs=$IFS - set -f - IFS=',' - # shellcheck disable=SC2086 - set -- $nav_links - IFS=$old_ifs - set +f - - [ $# -gt 0 ] || return - - printf '' -} - template="$src/template.html" [ -f "$template" ] || template="./template.html" if [ ! -f "$template" ]; then @@ -627,741 +278,6 @@ if [ -n "$nav_extra" ]; then $nav_extra" fi -find_closest() { - target="$1" - start_dir="$2" - curr="$start_dir" - while [ "$curr" != "$src" ] && [ "$curr" != "." ] && [ "$curr" != "/" ]; do - if [ -f "$curr/$target" ]; then - echo "$curr/$target" - return - fi - curr=$(dirname "$curr") - done - if [ -f "$src/$target" ]; then - echo "$src/$target" - fi -} - -copy_style_with_resolved_vars() { - src_style="$1" - out_style="$2" - awk -f "$awk_dir/replace_variables.awk" "$src_style" > "$out_style" -} - -render_markdown() { - file="$1" - is_home="$2" - url_override="$3" - - if [ -n "$url_override" ]; then - current_url="$url_override" - else - rel_path="${file#"$src"}" - rel_path="${rel_path#/}" - current_url="/${rel_path%.md}.html" - fi - - content_file="$file" - if [ -n "$posts_dir" ] && [ "$file" != "$src/$posts_dir/index.md" ]; then - rel_dir_of_url=$(dirname "$current_url") - rel_dir_of_url="${rel_dir_of_url#/}" - if { [ "$rel_dir_of_url" = "$posts_dir" ] || [ "./$rel_dir_of_url" = "$posts_dir" ]; } && [ "$(basename "$current_url")" != "index.html" ]; then - temp_post_with_backlink="$KEWT_TMPDIR/post_with_backlink_$$.md" - printf "[< Back](index.html)\n\n" > "$temp_post_with_backlink" - awk -f "$awk_dir/frontmatter.awk" "$file" >> "$temp_post_with_backlink" - - post_md_name="$(basename "$current_url" .html).md" - prevnext_file="$KEWT_TMPDIR/prevnext/$post_md_name" - if [ -f "$prevnext_file" ]; then - IFS='|' read -r prev_str next_str < "$prevnext_file" - - printf "\n\n---\n
\n" >> "$temp_post_with_backlink" - if [ -n "$prev_str" ]; then - printf "%s\n" "$prev_str" >> "$temp_post_with_backlink" - fi - if [ -n "$next_str" ]; then - printf "%s\n" "$next_str" >> "$temp_post_with_backlink" - fi - printf "
\n" >> "$temp_post_with_backlink" - fi - content_file="$temp_post_with_backlink" - fi - fi - - local_template=$(find_closest "template.html" "$(dirname "$file")") - [ -z "$local_template" ] && local_template="$template" - - closest_style_src=$(find_closest "styles.css" "$(dirname "$file")") - [ -z "$closest_style_src" ] && closest_style_src=$(find_closest "style.css" "$(dirname "$file")") - if [ -n "$closest_style_src" ]; then - style_rel_to_src="${closest_style_src#"$src"/}" - case "$closest_style_src" in - "$src/styles.css") style_rel_to_src="styles.css" ;; - "$src/style.css") style_rel_to_src="style.css" ;; - esac - style_path="/${style_rel_to_src%styles.css}" - style_path="${style_path%style.css}styles.css" - else - style_path="/styles.css" - fi - - logo_html="" - if [ "$display_logo" = "true" ] && [ -n "$logo" ]; then - logo_html="\"$title\"" - fi - - brand_text="" - if [ "$display_title" = "true" ]; then - brand_text="$title" - fi - - if [ -n "$logo_html" ] && [ -n "$brand_text" ]; then - header_brand="$logo_html $brand_text" - elif [ -n "$logo_html" ]; then - header_brand="$logo_html" - elif [ -n "$brand_text" ]; then - header_brand="$brand_text" - else - header_brand="$title" - fi - - favicon_src="" - if [ "$logo_as_favicon" = "true" ] && [ -n "$logo" ]; then - favicon_src="$logo" - elif [ -n "$favicon" ]; then - favicon_src="$favicon" - fi - head_extra="" - if [ -n "$favicon_src" ]; then - if echo "$favicon_src" | grep -q "^http"; then - head_extra="" - elif echo "$favicon_src" | grep -q "^/"; then - head_extra="" - else - head_extra="" - fi - fi - - parse_frontmatter "$file" - - page_title="$title" - if [ -n "$fm_title" ]; then - page_title="$fm_title - $title" - elif [ "$generate_page_title" = "true" ] && [ -n "$file" ] && [ -f "$file" ]; then - if [ "$is_home" = "true" ] && [ -n "$home_name" ]; then - page_title="$home_name - $title" - else - first_heading=$(grep -m 1 '^# ' "$file" | sed 's/^# *//; s/ *$//') - if [ -n "$first_heading" ]; then - first_heading=$(echo "$first_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') - page_title="$first_heading - $title" - else - basename_no_ext=$(basename "$file" .md) - if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then - cap_basename=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}') - page_title="$cap_basename - $title" - fi - fi - fi - fi - - head_extra_og="" - if [ -n "$fm_description" ]; then - head_extra_og="$head_extra_og - " - fi - og_url="${base_url%/}${current_url}" - head_extra_og="$head_extra_og - " - - if [ -n "$head_extra" ]; then - head_extra="$head_extra - $head_extra_og" - else - head_extra="$head_extra_og" - fi - - if [ "$is_cw_content_page" = "true" ] && [ "$cw_hide_url" = "true" ]; then - head_extra="$head_extra - " - fi - - ENABLE_HEADER_LINKS="$enable_header_links" CUSTOM_ADMONITIONS="$custom_admonitions" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_LANG="$lang" AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$nav" AWK_FOOTER="$footer" AWK_STYLE_PATH="${style_path}" AWK_HEADER_BRAND="$header_brand" AWK_HEAD_EXTRA="$head_extra" AWK_VERSION="$asset_version" AWK_CONTENT_WARNING="$fm_content_warning" awk -f "$awk_dir/render_template.awk" "$local_template" -} - -generate_content_warning_page() { - _fm_title="$1" - _fm_content_warning="$2" - _content_rel_url="$3" - _target_url="$4" - _out_file="$5" - _is_home="$6" - - _temp_cw="$KEWT_TMPDIR/cw_$$.md" - _cw_text="${_fm_content_warning}" - [ "$_cw_text" = "true" ] && _cw_text="This content may be sensitive." - - cat < "$_temp_cw" ---- -title = "$_fm_title" ---- - -> [!CAUTION] -> **Content Warning:** $_cw_text - -Reveal Content -EOF - render_markdown "$_temp_cw" "$_is_home" "$_target_url" > "$_out_file" - rm -f "$_temp_cw" -} - -needs_rebuild() { - src_file="$1" - out_file="$2" - [ ! -f "$out_file" ] && return 0 - [ "$src_file" -nt "$out_file" ] && return 0 - [ -f "./site.conf" ] && [ "./site.conf" -nt "$out_file" ] && return 0 - [ -f "$src/site.conf" ] && [ "$src/site.conf" -nt "$out_file" ] && return 0 - [ -f "$template" ] && [ "$template" -nt "$out_file" ] && return 0 - [ -f "$script_dir/styles/$style.css" ] && [ "$script_dir/styles/$style.css" -nt "$out_file" ] && return 0 - return 1 -} - -build_site() { -echo "Building site from '$src' to '$out'..." - -eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while read -r dir; do - rel_dir="${dir#"$src"}" - rel_dir="${rel_dir#/}" - [ -z "$rel_dir" ] && rel_dir="." - out_dir="$out/$rel_dir" - mkdir -p "$out_dir" - - if [ -f "$dir/styles.css" ]; then - if needs_rebuild "$dir/styles.css" "$out_dir/styles.css"; then - copy_style_with_resolved_vars "$dir/styles.css" "$out_dir/styles.css" - fi - elif [ -f "$dir/style.css" ]; then - if needs_rebuild "$dir/style.css" "$out_dir/styles.css"; then - copy_style_with_resolved_vars "$dir/style.css" "$out_dir/styles.css" - fi - fi - - [ "$dir_indexes" != "true" ] && continue - - has_custom_index="false" - has_list="false" - if [ -f "$dir/index.md" ]; then - has_custom_index="true" - if grep -q '^[[:space:]]*{{LIST}}[[:space:]]*$' "$dir/index.md" 2>/dev/null; then - has_list="true" - fi - fi - - if [ "$has_custom_index" = "false" ] || [ "$has_list" = "true" ]; then - is_posts_dir="false" - if [ -n "$posts_dir" ] && { [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; }; then - is_posts_dir="true" - fi - if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ] && [ "$has_list" = "false" ]; then - md_count=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md" | wc -l) - if [ "$md_count" -eq 1 ]; then - md_file=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md") - is_home="false"; [ "$dir" = "$src" ] && is_home="true" - target_url="/$rel_dir/index.html" - [ "$rel_dir" = "." ] && target_url="/index.html" - if needs_rebuild "$md_file" "$out_dir/index.html"; then - parse_frontmatter "$md_file" - if [ -n "$fm_content_warning" ]; then - content_out_file="$out_dir/content.html" - content_rel_url="/$rel_dir/content.html" - [ "$rel_dir" = "." ] && content_rel_url="/content.html" - - is_cw_content_page="true" - render_markdown "$md_file" "$is_home" "$target_url" > "$content_out_file" - is_cw_content_page="false" - - generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$target_url" "$out_dir/index.html" "false" - else - render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html" - fi - fi - continue - fi - fi - - temp_index="$KEWT_TMPDIR/index.md" - temp_list="$KEWT_TMPDIR/list.md" - : > "$temp_list" - - if [ "$has_custom_index" = "false" ]; then - display_dir="${rel_dir#.}" - [ -z "$display_dir" ] && display_dir="/" - echo "# Index of $display_dir" > "$temp_index" - echo "" >> "$temp_index" - fi - - - sort_args="" - # If this is the posts dir reverse - if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then - sort_args="-r" - fi - - temp_entries="$KEWT_TMPDIR/entries_$$.txt" - : > "$temp_entries" - - find "$dir" ! -name "$(basename "$dir")" -prune ! -name ".*" -print | while read -r entry; do - name="${entry##*/}" - case "$name" in - template.html|site.conf|style.css|index.md) continue ;; - esac - if [ -d "$entry" ]; then - echo "${name}|- [${name}/](${name}/index.html)" >> "$temp_entries" - elif [ "${entry%.md}" != "$entry" ]; then - label="${name%.md}" - - # Parse frontmatter for date/title/draft - parse_frontmatter "$entry" - [ "$fm_draft" = "true" ] && continue - - # Try to get first heading - post_h="$fm_title" - if [ -z "$post_h" ]; then - post_h=$(grep -m 1 '^# ' "$entry" | sed 's/^# *//') - if [ -n "$post_h" ]; then - post_h=$(echo "$post_h" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') - fi - fi - - is_post_entry="false" - if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then - is_post_entry="true" - fi - - if [ -n "$post_h" ]; then - if [ "$is_post_entry" = "true" ]; then - # Use frontmatter date if available, else parse from filename - if [ -n "$fm_date" ]; then - p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - p_time="" - if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then - p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - else - p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - p_time="00:00" - if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then - p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - fi - if [ -n "$p_time" ]; then - label="$post_h - $p_date $p_time" - else - label="$post_h - $p_date" - fi - else - label="$post_h" - fi - elif [ "$is_post_entry" = "true" ]; then - # No heading; use date - if [ -n "$fm_date" ]; then - p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - p_time="" - if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then - p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - if [ -n "$p_time" ]; then - label="$p_date $p_time" - else - label="$p_date" - fi - else - p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - p_time="00:00" - if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then - p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - label="$p_date $p_time" - fi - fi - if [ "$is_post_entry" = "true" ]; then - sort_key="${p_date} ${p_time}" - else - sort_key="$name" - fi - echo "${sort_key}|- [$label](${name%.md}.html)|$name|${name%.md}.html" >> "$temp_entries" - else - echo "${name}|- [$name]($name)|$name|$name" >> "$temp_entries" - fi - done - - if [ "$is_posts_dir" = "true" ]; then - LC_ALL=C sort $sort_args "$temp_entries" > "$KEWT_TMPDIR/sorted_entries_$$.txt" - cut -d'|' -f2 "$KEWT_TMPDIR/sorted_entries_$$.txt" >> "$temp_list" - mkdir -p "$KEWT_TMPDIR/prevnext" - awk -F'|' ' - { - name[NR] = $3 - url[NR] = $4 - } - END { - for(i=1; i<=NR; i++) { - prev_str = "" - next_str = "" - if(i > 1) { - next_str = "[Next >](" url[i-1] ")" - } - if(i < NR) { - prev_str = "[< Previous](" url[i+1] ")" - } - if (prev_str != "" || next_str != "") { - out = "'"$KEWT_TMPDIR"'/prevnext/" name[i] - printf "%s|%s\n", prev_str, next_str > out - } - } - } - ' "$KEWT_TMPDIR/sorted_entries_$$.txt" - rm -f "$KEWT_TMPDIR/sorted_entries_$$.txt" - else - LC_ALL=C sort $sort_args "$temp_entries" | cut -d'|' -f2 >> "$temp_list" - fi - rm -f "$temp_entries" - - is_home="false"; [ "$dir" = "$src" ] && is_home="true" - target_url="/$rel_dir/index.html" - [ "$rel_dir" = "." ] && target_url="/index.html" - - num_items=$(wc -l < "$temp_list") - if [ "$is_posts_dir" = "true" ] && [ -n "$posts_per_page" ] && [ "$posts_per_page" -gt 0 ] && [ "$num_items" -gt "$posts_per_page" ]; then - num_pages=$(( (num_items + posts_per_page - 1) / posts_per_page )) - for p in $(seq 1 $num_pages); do - chunk_list="$KEWT_TMPDIR/chunk.md" - start_line=$(( (p - 1) * posts_per_page + 1 )) - tail -n +$start_line "$temp_list" | head -n "$posts_per_page" > "$chunk_list" - - base_url_dir="$(dirname "$target_url")" - [ "$base_url_dir" = "/" ] && base_url_dir="" - - nav_html="
" - if [ "$p" -gt 1 ]; then - if [ "$p" -eq 2 ]; then - nav_html="$nav_html « Prev " - else - nav_html="$nav_html « Prev " - fi - fi - nav_html="$nav_html Page $p of $num_pages " - if [ "$p" -lt "$num_pages" ]; then - nav_html="$nav_html Next » " - fi - nav_html="$nav_html
" - - echo "" >> "$chunk_list" - echo "$nav_html" >> "$chunk_list" - - temp_index_p="$KEWT_TMPDIR/index_p$p.md" - if [ "$has_custom_index" = "false" ]; then - display_dir="${rel_dir#.}" - [ -z "$display_dir" ] && display_dir="/" - echo "# Index of $display_dir" > "$temp_index_p" - echo "" >> "$temp_index_p" - else - : > "$temp_index_p" - fi - - if [ "$has_custom_index" = "true" ]; then - awk ' - /^[[:space:]]*\{\{LIST\}\}[[:space:]]*$/ { - while((getline line < "'"$chunk_list"'") > 0) print line - close("'"$chunk_list"'") - next - } - { print } - ' "$dir/index.md" >> "$temp_index_p" - else - cat "$chunk_list" >> "$temp_index_p" - fi - - if [ "$p" -eq 1 ]; then - out_file="$out_dir/index.html" - target_url_p="$target_url" - else - out_file="$out_dir/page/$p/index.html" - target_url_p="$base_url_dir/page/$p/index.html" - mkdir -p "$(dirname "$out_file")" - fi - - render_markdown "$temp_index_p" "$is_home" "$target_url_p" > "$out_file" - rm -f "$temp_index_p" "$chunk_list" - done - else - if [ "$has_custom_index" = "true" ]; then - awk ' - /^[[:space:]]*\{\{LIST\}\}[[:space:]]*$/ { - while((getline line < "'"$temp_list"'") > 0) print line - close("'"$temp_list"'") - next - } - { print } - ' "$dir/index.md" > "$temp_index" - else - cat "$temp_list" >> "$temp_index" - fi - - do_rebuild="false" - needs_rebuild "$dir" "$out_dir/index.html" && do_rebuild="true" - [ "$has_custom_index" = "true" ] && needs_rebuild "$dir/index.md" "$out_dir/index.html" && do_rebuild="true" - - if [ "$do_rebuild" = "false" ] && [ -f "$out_dir/index.html" ]; then - for _child in "$dir"/*; do - [ -e "$_child" ] || continue - if [ "$_child" -nt "$out_dir/index.html" ]; then - do_rebuild="true" - break - fi - done - fi - - if [ "$do_rebuild" = "true" ]; then - if [ "$has_custom_index" = "true" ]; then - parse_frontmatter "$dir/index.md" - else - fm_content_warning="" - fi - - if [ -n "$fm_content_warning" ]; then - content_out_file="$out_dir/content.html" - content_rel_url="/$rel_dir/content.html" - [ "$rel_dir" = "." ] && content_rel_url="/content.html" - - is_cw_content_page="true" - render_markdown "$temp_index" "$is_home" "$target_url" > "$content_out_file" - is_cw_content_page="false" - - generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$target_url" "$out_dir/index.html" "false" - else - render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html" - fi - fi - fi - rm -f "$temp_index" "$temp_list" - fi -done - -if [ -f "$script_dir/styles/$style.css" ] && needs_rebuild "$script_dir/styles/$style.css" "$out/styles.css"; then - copy_style_with_resolved_vars "$script_dir/styles/$style.css" "$out/styles.css" -fi - -eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while IFS= read -r file; do - rel_path="${file#"$src"}" - rel_path="${rel_path#/}" - dir_rel=$(dirname "$rel_path") - out_dir="$out/$dir_rel" - - case "${file##*/}" in - template.html|site.conf|style.css|styles.css) continue ;; - esac - - if [ "${file##*/}" = "index.md" ] && grep -q '^[[:space:]]*{{LIST}}[[:space:]]*$' "$file" 2>/dev/null; then - continue - fi - - is_preserved=0 - if [ -n "$(eval "find \"$file\" \( $PRESERVE_ARGS \) -print")" ]; then - is_preserved=1 - fi - - is_posts_dir_2="false" - if [ -n "$posts_dir" ] && { [ "$dir_rel" = "$posts_dir" ] || [ "./$dir_rel" = "$posts_dir" ]; }; then - is_posts_dir_2="true" - fi - - if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ] && [ "$is_posts_dir_2" = "false" ]; then - md_count=$(find "$(dirname "$file")" ! -name "$(basename "$(dirname "$file")")" -prune -name "*.md" | wc -l) - [ "$md_count" -eq 1 ] && continue - fi - - if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then - # Skip draft files - parse_frontmatter "$file" - if [ "$fm_draft" = "true" ]; then - continue - fi - is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true" - out_file="$out/${rel_path%.md}.html" - if needs_rebuild "$file" "$out_file"; then - if [ -n "$fm_content_warning" ]; then - content_out_file="$out/${rel_path%.md}-content.html" - content_rel_url="/${rel_path%.md}-content.html" - orig_rel_url="/${rel_path%.md}.html" - - is_cw_content_page="true" - render_markdown "$file" "$is_home" "$orig_rel_url" > "$content_out_file" - is_cw_content_page="false" - - generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$orig_rel_url" "$out_file" "false" - else - render_markdown "$file" "$is_home" > "$out_file" - fi - fi - else - if needs_rebuild "$file" "$out/$rel_path"; then - cp "$file" "$out/$rel_path" - fi - fi -done - -if [ -n "$error_page" ] && [ ! -f "$out/$error_page" ]; then - temp_404="$KEWT_TMPDIR/404_gen.md" - echo "# 404 - Not Found" > "$temp_404" - echo "" >> "$temp_404" - echo "The requested page could not be found." >> "$temp_404" - render_markdown "$temp_404" "false" "/$error_page" > "$out/$error_page" - rm -f "$temp_404" -fi - -if [ -n "$base_url" ]; then - sitemap_file="$out/sitemap.xml" - base_url="${base_url%/}" - today=$(date +%Y-%m-%d) - - printf '\n' > "$sitemap_file" - printf '\n' >> "$sitemap_file" - - find "$out" -type f -name "*.html" -print | sort | while IFS= read -r html_file; do - rel_url="${html_file#"$out"}" - - # Don't include 404 in the sitemap (duh) - [ "${rel_url#/}" = "$error_page" ] && continue - - { - printf ' \n' - printf ' %s%s\n' "$base_url" "$rel_url" - printf ' %s\n' "$today" - printf ' \n' - } >> "$sitemap_file" - done - - printf '\n' >> "$sitemap_file" -fi - -if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then - feed_path="$out/$feed_file" - base_url_feed="${base_url%/}" - build_date=$(date -u '+%a, %d %b %Y %H:%M:%S +0000') - - printf '\n' > "$feed_path" - { - printf '\n' - printf ' \n' - printf ' %s\n' "$title" - printf ' %s\n' "$base_url_feed" - printf ' %s\n' "$title" - printf ' %s\n' "$build_date" - } >> "$feed_path" - - temp_feed_files="$KEWT_TMPDIR/feed_files_$$.txt" - : > "$temp_feed_files" - - find "$src" -type f -name '*.md' -path "*${posts_dir:-__no_posts__}*" -print | while IFS= read -r post_file; do - post_basename=$(basename "$post_file" .md) - # Parse frontmatter to get date - parse_frontmatter "$post_file" - [ "$fm_draft" = "true" ] && continue - if [ -n "$fm_date" ]; then - post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - post_time="00:00" - if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then - post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - else - post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - post_time="00:00" - if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then - post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - fi - echo "${post_date} ${post_time}|${post_file}" >> "$temp_feed_files" - done - - LC_ALL=C sort -r "$temp_feed_files" | cut -d'|' -f2- | while IFS= read -r post_file; do - post_basename=$(basename "$post_file" .md) - - # Parse frontmatter - parse_frontmatter "$post_file" - [ "$fm_draft" = "true" ] && continue - - # Use frontmatter date, fallback to filename - if [ -n "$fm_date" ]; then - post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - post_time="00:00" - if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then - post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - else - post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') - post_time="00:00" - if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then - post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') - fi - fi - - post_slug=$(echo "$post_basename" | sed -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' -e 's/^[_\-]//') - - post_heading="$fm_title" - if [ -z "$post_heading" ]; then - post_heading=$(grep -m 1 '^# ' "$post_file" | sed 's/^# *//') - fi - if [ -z "$post_heading" ]; then - if [ -n "$post_slug" ] && ! echo "$post_slug" | grep -q '^[0-9]\+$'; then - post_heading=$(echo "$post_slug" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1') - else - post_heading="Post" - fi - fi - post_heading=$(echo "$post_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') - feed_post_title="$post_heading - $post_date $post_time" - - rel_path="${post_file#"$src"}" - rel_path="${rel_path#/}" - post_url="$base_url_feed/${rel_path%.md}.html" - - if date -u -d "$post_date $post_time" '+%a, %d %b %Y %H:%M:%S +0000' >/dev/null 2>&1; then - pub_date=$(date -u -d "$post_date $post_time" '+%a, %d %b %Y %H:%M:%S +0000') - else - pub_year=$(echo "$post_date" | cut -d- -f1) - pub_month=$(echo "$post_date" | cut -d- -f2) - pub_day=$(echo "$post_date" | cut -d- -f3) - # zero-padded - pub_day=$(printf '%02d' "${pub_day#0}") - case "$pub_month" in - 01) pub_mon="Jan" ;; 02) pub_mon="Feb" ;; 03) pub_mon="Mar" ;; - 04) pub_mon="Apr" ;; 05) pub_mon="May" ;; 06) pub_mon="Jun" ;; - 07) pub_mon="Jul" ;; 08) pub_mon="Aug" ;; 09) pub_mon="Sep" ;; - 10) pub_mon="Oct" ;; 11) pub_mon="Nov" ;; 12) pub_mon="Dec" ;; - esac - pub_date="Mon, ${pub_day} ${pub_mon} ${pub_year} ${post_time}:00 +0000" - fi - - { - printf ' \n' - printf ' %s\n' "$feed_post_title" - printf ' %s\n' "$post_url" - printf ' %s\n' "$post_url" - printf ' %s\n' "$pub_date" - printf ' \n' - } >> "$feed_path" - done - - printf ' \n' >> "$feed_path" - printf '\n' >> "$feed_path" -fi - -echo "Build complete." -} - build_site if [ "$serve_mode" = "true" ]; then diff --git a/lib/builder.sh b/lib/builder.sh new file mode 100644 index 0000000..ac44162 --- /dev/null +++ b/lib/builder.sh @@ -0,0 +1,707 @@ +needs_rebuild() { + src_file="$1" + out_file="$2" + [ ! -f "$out_file" ] && return 0 + [ "$src_file" -nt "$out_file" ] && return 0 + [ -f "./site.conf" ] && [ "./site.conf" -nt "$out_file" ] && return 0 + [ -f "$src/site.conf" ] && [ "$src/site.conf" -nt "$out_file" ] && return 0 + [ -f "$template" ] && [ "$template" -nt "$out_file" ] && return 0 + [ -f "$script_dir/styles/$style.css" ] && [ "$script_dir/styles/$style.css" -nt "$out_file" ] && return 0 + return 1 +} + +build_site() { +echo "Building site from '$src' to '$out'..." + +eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while read -r dir; do + rel_dir="${dir#"$src"}" + rel_dir="${rel_dir#/}" + [ -z "$rel_dir" ] && rel_dir="." + out_dir="$out/$rel_dir" + mkdir -p "$out_dir" + + if [ -f "$dir/styles.css" ]; then + if needs_rebuild "$dir/styles.css" "$out_dir/styles.css"; then + copy_style_with_resolved_vars "$dir/styles.css" "$out_dir/styles.css" + fi + elif [ -f "$dir/style.css" ]; then + if needs_rebuild "$dir/style.css" "$out_dir/styles.css"; then + copy_style_with_resolved_vars "$dir/style.css" "$out_dir/styles.css" + fi + fi + + [ "$dir_indexes" != "true" ] && continue + + has_custom_index="false" + has_list="false" + if [ -f "$dir/index.md" ]; then + has_custom_index="true" + if grep -q '^[[:space:]]*{{LIST}}[[:space:]]*$' "$dir/index.md" 2>/dev/null; then + has_list="true" + fi + fi + + if [ "$has_custom_index" = "false" ] || [ "$has_list" = "true" ]; then + is_posts_dir="false" + if [ -n "$posts_dir" ] && { [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; }; then + is_posts_dir="true" + fi + if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ] && [ "$has_list" = "false" ]; then + md_count=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md" | wc -l) + if [ "$md_count" -eq 1 ]; then + md_file=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md") + is_home="false"; [ "$dir" = "$src" ] && is_home="true" + target_url="/$rel_dir/index.html" + [ "$rel_dir" = "." ] && target_url="/index.html" + if needs_rebuild "$md_file" "$out_dir/index.html"; then + parse_frontmatter "$md_file" + if [ -n "$fm_content_warning" ]; then + content_out_file="$out_dir/content.html" + content_rel_url="/$rel_dir/content.html" + [ "$rel_dir" = "." ] && content_rel_url="/content.html" + + is_cw_content_page="true" + render_markdown "$md_file" "$is_home" "$target_url" > "$content_out_file" + is_cw_content_page="false" + + generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$target_url" "$out_dir/index.html" "false" + else + render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html" + fi + fi + continue + fi + fi + + temp_index="$KEWT_TMPDIR/index.md" + temp_list="$KEWT_TMPDIR/list.md" + : > "$temp_list" + + if [ "$has_custom_index" = "false" ]; then + display_dir="${rel_dir#.}" + [ -z "$display_dir" ] && display_dir="/" + echo "# Index of $display_dir" > "$temp_index" + echo "" >> "$temp_index" + fi + + + sort_args="" + # If this is the posts dir reverse + if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then + sort_args="-r" + fi + + temp_entries="$KEWT_TMPDIR/entries_$$.txt" + : > "$temp_entries" + + find "$dir" ! -name "$(basename "$dir")" -prune ! -name ".*" -print | while read -r entry; do + name="${entry##*/}" + case "$name" in + template.html|site.conf|style.css|index.md) continue ;; + esac + if [ -d "$entry" ]; then + echo "${name}|- [${name}/](${name}/index.html)" >> "$temp_entries" + elif [ "${entry%.md}" != "$entry" ]; then + label="${name%.md}" + + # Parse frontmatter for date/title/draft + parse_frontmatter "$entry" + [ "$fm_draft" = "true" ] && continue + + # Try to get first heading + post_h="$fm_title" + if [ -z "$post_h" ]; then + post_h=$(grep -m 1 '^# ' "$entry" | sed 's/^# *//') + if [ -n "$post_h" ]; then + post_h=$(echo "$post_h" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') + fi + fi + + is_post_entry="false" + if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then + is_post_entry="true" + fi + + if [ -n "$post_h" ]; then + if [ "$is_post_entry" = "true" ]; then + # Use frontmatter date if available, else parse from filename + if [ -n "$fm_date" ]; then + p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + p_time="" + if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then + p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + else + p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + p_time="00:00" + if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then + p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + fi + if [ -n "$p_time" ]; then + label="$post_h - $p_date $p_time" + else + label="$post_h - $p_date" + fi + else + label="$post_h" + fi + elif [ "$is_post_entry" = "true" ]; then + # No heading; use date + if [ -n "$fm_date" ]; then + p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + p_time="" + if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then + p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + if [ -n "$p_time" ]; then + label="$p_date $p_time" + else + label="$p_date" + fi + else + p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + p_time="00:00" + if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then + p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + label="$p_date $p_time" + fi + fi + if [ "$is_post_entry" = "true" ]; then + sort_key="${p_date} ${p_time}" + else + sort_key="$name" + fi + echo "${sort_key}|- [$label](${name%.md}.html)|$name|${name%.md}.html" >> "$temp_entries" + else + echo "${name}|- [$name]($name)|$name|$name" >> "$temp_entries" + fi + done + + if [ "$is_posts_dir" = "true" ]; then + LC_ALL=C sort $sort_args "$temp_entries" > "$KEWT_TMPDIR/sorted_entries_$$.txt" + cut -d'|' -f2 "$KEWT_TMPDIR/sorted_entries_$$.txt" >> "$temp_list" + mkdir -p "$KEWT_TMPDIR/prevnext" + awk -F'|' ' + { + name[NR] = $3 + url[NR] = $4 + } + END { + for(i=1; i<=NR; i++) { + prev_str = "" + next_str = "" + if(i > 1) { + next_str = "[Next >](" url[i-1] ")" + } + if(i < NR) { + prev_str = "[< Previous](" url[i+1] ")" + } + if (prev_str != "" || next_str != "") { + out = "'"$KEWT_TMPDIR"'/prevnext/" name[i] + printf "%s|%s\n", prev_str, next_str > out + } + } + } + ' "$KEWT_TMPDIR/sorted_entries_$$.txt" + rm -f "$KEWT_TMPDIR/sorted_entries_$$.txt" + else + LC_ALL=C sort $sort_args "$temp_entries" | cut -d'|' -f2 >> "$temp_list" + fi + rm -f "$temp_entries" + + is_home="false"; [ "$dir" = "$src" ] && is_home="true" + target_url="/$rel_dir/index.html" + [ "$rel_dir" = "." ] && target_url="/index.html" + + num_items=$(wc -l < "$temp_list") + if [ "$is_posts_dir" = "true" ] && [ -n "$posts_per_page" ] && [ "$posts_per_page" -gt 0 ] && [ "$num_items" -gt "$posts_per_page" ]; then + num_pages=$(( (num_items + posts_per_page - 1) / posts_per_page )) + for p in $(seq 1 $num_pages); do + chunk_list="$KEWT_TMPDIR/chunk.md" + start_line=$(( (p - 1) * posts_per_page + 1 )) + tail -n +$start_line "$temp_list" | head -n "$posts_per_page" > "$chunk_list" + + base_url_dir="$(dirname "$target_url")" + [ "$base_url_dir" = "/" ] && base_url_dir="" + + nav_html="
" + if [ "$p" -gt 1 ]; then + if [ "$p" -eq 2 ]; then + nav_html="$nav_html « Prev " + else + nav_html="$nav_html « Prev " + fi + fi + nav_html="$nav_html Page $p of $num_pages " + if [ "$p" -lt "$num_pages" ]; then + nav_html="$nav_html Next » " + fi + nav_html="$nav_html
" + + echo "" >> "$chunk_list" + echo "$nav_html" >> "$chunk_list" + + temp_index_p="$KEWT_TMPDIR/index_p$p.md" + if [ "$has_custom_index" = "false" ]; then + display_dir="${rel_dir#.}" + [ -z "$display_dir" ] && display_dir="/" + echo "# Index of $display_dir" > "$temp_index_p" + echo "" >> "$temp_index_p" + else + : > "$temp_index_p" + fi + + if [ "$has_custom_index" = "true" ]; then + awk ' + /^[[:space:]]*\{\{LIST\}\}[[:space:]]*$/ { + while((getline line < "'"$chunk_list"'") > 0) print line + close("'"$chunk_list"'") + next + } + { print } + ' "$dir/index.md" >> "$temp_index_p" + else + cat "$chunk_list" >> "$temp_index_p" + fi + + if [ "$p" -eq 1 ]; then + out_file="$out_dir/index.html" + target_url_p="$target_url" + else + out_file="$out_dir/page/$p/index.html" + target_url_p="$base_url_dir/page/$p/index.html" + mkdir -p "$(dirname "$out_file")" + fi + + render_markdown "$temp_index_p" "$is_home" "$target_url_p" > "$out_file" + rm -f "$temp_index_p" "$chunk_list" + done + else + if [ "$has_custom_index" = "true" ]; then + awk ' + /^[[:space:]]*\{\{LIST\}\}[[:space:]]*$/ { + while((getline line < "'"$temp_list"'") > 0) print line + close("'"$temp_list"'") + next + } + { print } + ' "$dir/index.md" > "$temp_index" + else + cat "$temp_list" >> "$temp_index" + fi + + do_rebuild="false" + needs_rebuild "$dir" "$out_dir/index.html" && do_rebuild="true" + [ "$has_custom_index" = "true" ] && needs_rebuild "$dir/index.md" "$out_dir/index.html" && do_rebuild="true" + + if [ "$do_rebuild" = "false" ] && [ -f "$out_dir/index.html" ]; then + for _child in "$dir"/*; do + [ -e "$_child" ] || continue + if [ "$_child" -nt "$out_dir/index.html" ]; then + do_rebuild="true" + break + fi + done + fi + + if [ "$do_rebuild" = "true" ]; then + if [ "$has_custom_index" = "true" ]; then + parse_frontmatter "$dir/index.md" + else + fm_content_warning="" + fi + + if [ -n "$fm_content_warning" ]; then + content_out_file="$out_dir/content.html" + content_rel_url="/$rel_dir/content.html" + [ "$rel_dir" = "." ] && content_rel_url="/content.html" + + is_cw_content_page="true" + render_markdown "$temp_index" "$is_home" "$target_url" > "$content_out_file" + is_cw_content_page="false" + + generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$target_url" "$out_dir/index.html" "false" + else + render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html" + fi + fi + fi + rm -f "$temp_index" "$temp_list" + fi +done + +if [ -f "$script_dir/styles/$style.css" ] && needs_rebuild "$script_dir/styles/$style.css" "$out/styles.css"; then + copy_style_with_resolved_vars "$script_dir/styles/$style.css" "$out/styles.css" +fi + +eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while IFS= read -r file; do + rel_path="${file#"$src"}" + rel_path="${rel_path#/}" + dir_rel=$(dirname "$rel_path") + out_dir="$out/$dir_rel" + + case "${file##*/}" in + template.html|site.conf|style.css|styles.css) continue ;; + esac + + if [ "${file##*/}" = "index.md" ] && grep -q '^[[:space:]]*{{LIST}}[[:space:]]*$' "$file" 2>/dev/null; then + continue + fi + + is_preserved=0 + if [ -n "$(eval "find \"$file\" \( $PRESERVE_ARGS \) -print")" ]; then + is_preserved=1 + fi + + is_posts_dir_2="false" + if [ -n "$posts_dir" ] && { [ "$dir_rel" = "$posts_dir" ] || [ "./$dir_rel" = "$posts_dir" ]; }; then + is_posts_dir_2="true" + fi + + if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ] && [ "$is_posts_dir_2" = "false" ]; then + md_count=$(find "$(dirname "$file")" ! -name "$(basename "$(dirname "$file")")" -prune -name "*.md" | wc -l) + [ "$md_count" -eq 1 ] && continue + fi + + if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then + # Skip draft files + parse_frontmatter "$file" + if [ "$fm_draft" = "true" ]; then + continue + fi + is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true" + out_file="$out/${rel_path%.md}.html" + if needs_rebuild "$file" "$out_file"; then + if [ -n "$fm_content_warning" ]; then + content_out_file="$out/${rel_path%.md}-content.html" + content_rel_url="/${rel_path%.md}-content.html" + orig_rel_url="/${rel_path%.md}.html" + + is_cw_content_page="true" + render_markdown "$file" "$is_home" "$orig_rel_url" > "$content_out_file" + is_cw_content_page="false" + + generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$orig_rel_url" "$out_file" "false" + else + render_markdown "$file" "$is_home" > "$out_file" + fi + fi + else + if needs_rebuild "$file" "$out/$rel_path"; then + cp "$file" "$out/$rel_path" + fi + fi +done + +if [ -n "$error_page" ] && [ ! -f "$out/$error_page" ]; then + temp_404="$KEWT_TMPDIR/404_gen.md" + echo "# 404 - Not Found" > "$temp_404" + echo "" >> "$temp_404" + echo "The requested page could not be found." >> "$temp_404" + render_markdown "$temp_404" "false" "/$error_page" > "$out/$error_page" + rm -f "$temp_404" +fi + +if [ -n "$base_url" ]; then + sitemap_file="$out/sitemap.xml" + base_url="${base_url%/}" + today=$(date +%Y-%m-%d) + + printf '\n' > "$sitemap_file" + printf '\n' >> "$sitemap_file" + + find "$out" -type f -name "*.html" -print | sort | while IFS= read -r html_file; do + rel_url="${html_file#"$out"}" + + # Don't include 404 in the sitemap (duh) + [ "${rel_url#/}" = "$error_page" ] && continue + + { + printf ' \n' + printf ' %s%s\n' "$base_url" "$rel_url" + printf ' %s\n' "$today" + printf ' \n' + } >> "$sitemap_file" + done + + printf '\n' >> "$sitemap_file" +fi + +if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then + feed_path="$out/$feed_file" + base_url_feed="${base_url%/}" + build_date=$(date -u '+%a, %d %b %Y %H:%M:%S +0000') + + printf '\n' > "$feed_path" + { + printf '\n' + printf ' \n' + printf ' %s\n' "$title" + printf ' %s\n' "$base_url_feed" + printf ' %s\n' "$title" + printf ' %s\n' "$build_date" + } >> "$feed_path" + + temp_feed_files="$KEWT_TMPDIR/feed_files_$$.txt" + : > "$temp_feed_files" + + find "$src" -type f -name '*.md' -path "*${posts_dir:-__no_posts__}*" -print | while IFS= read -r post_file; do + post_basename=$(basename "$post_file" .md) + # Parse frontmatter to get date + parse_frontmatter "$post_file" + [ "$fm_draft" = "true" ] && continue + if [ -n "$fm_date" ]; then + post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + post_time="00:00" + if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then + post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + else + post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + post_time="00:00" + if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then + post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + fi + echo "${post_date} ${post_time}|${post_file}" >> "$temp_feed_files" + done + + LC_ALL=C sort -r "$temp_feed_files" | cut -d'|' -f2- | while IFS= read -r post_file; do + post_basename=$(basename "$post_file" .md) + + # Parse frontmatter + parse_frontmatter "$post_file" + [ "$fm_draft" = "true" ] && continue + + # Use frontmatter date, fallback to filename + if [ -n "$fm_date" ]; then + post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + post_time="00:00" + if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then + post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + else + post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') + post_time="00:00" + if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then + post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') + fi + fi + + post_slug=$(echo "$post_basename" | sed -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' -e 's/^[_\-]//') + + post_heading="$fm_title" + if [ -z "$post_heading" ]; then + post_heading=$(grep -m 1 '^# ' "$post_file" | sed 's/^# *//') + fi + if [ -z "$post_heading" ]; then + if [ -n "$post_slug" ] && ! echo "$post_slug" | grep -q '^[0-9]\+$'; then + post_heading=$(echo "$post_slug" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1') + else + post_heading="Post" + fi + fi + post_heading=$(echo "$post_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') + feed_post_title="$post_heading - $post_date $post_time" + + rel_path="${post_file#"$src"}" + rel_path="${rel_path#/}" + post_url="$base_url_feed/${rel_path%.md}.html" + + if date -u -d "$post_date $post_time" '+%a, %d %b %Y %H:%M:%S +0000' >/dev/null 2>&1; then + pub_date=$(date -u -d "$post_date $post_time" '+%a, %d %b %Y %H:%M:%S +0000') + else + pub_year=$(echo "$post_date" | cut -d- -f1) + pub_month=$(echo "$post_date" | cut -d- -f2) + pub_day=$(echo "$post_date" | cut -d- -f3) + # zero-padded + pub_day=$(printf '%02d' "${pub_day#0}") + case "$pub_month" in + 01) pub_mon="Jan" ;; 02) pub_mon="Feb" ;; 03) pub_mon="Mar" ;; + 04) pub_mon="Apr" ;; 05) pub_mon="May" ;; 06) pub_mon="Jun" ;; + 07) pub_mon="Jul" ;; 08) pub_mon="Aug" ;; 09) pub_mon="Sep" ;; + 10) pub_mon="Oct" ;; 11) pub_mon="Nov" ;; 12) pub_mon="Dec" ;; + esac + pub_date="Mon, ${pub_day} ${pub_mon} ${pub_year} ${post_time}:00 +0000" + fi + + { + printf ' \n' + printf ' %s\n' "$feed_post_title" + printf ' %s\n' "$post_url" + printf ' %s\n' "$post_url" + printf ' %s\n' "$pub_date" + printf ' \n' + } >> "$feed_path" + done + + printf ' \n' >> "$feed_path" + printf '\n' >> "$feed_path" +fi + +if [ "$generate_search" = "true" ] || [ "$generate_tags" = "true" ]; then + if [ "$generate_search" = "true" ]; then + printf '[\n' > "$out/search.json" + fi + first_search_item="true" + temp_tags="$KEWT_TMPDIR/tags_$$.txt" + : > "$temp_tags" + + eval "find \"$src\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" | sort | while IFS= read -r md_file; do + is_index="false" + [ "$(basename "$md_file")" = "index.md" ] && is_index="true" + + rel_path="${md_file#"$src"}" + rel_path="${rel_path#/}" + if [ "$is_index" = "true" ]; then + if [ "$rel_path" = "index.md" ]; then + md_url="/index.html" + else + md_url="/${rel_path%/index.md}/index.html" + fi + else + md_url="/${rel_path%.md}.html" + if [ "$single_file_index" = "true" ]; then + dir_of_file="$(dirname "$md_file")" + rel_dir_of_file="${dir_of_file#"$src"}" + rel_dir_of_file="${rel_dir_of_file#/}" + [ -z "$rel_dir_of_file" ] && rel_dir_of_file="." + + is_posts_dir_search="false" + if [ -n "$posts_dir" ] && { [ "$rel_dir_of_file" = "$posts_dir" ] || [ "./$rel_dir_of_file" = "$posts_dir" ]; }; then + is_posts_dir_search="true" + fi + + if [ "$is_posts_dir_search" = "false" ] && [ ! -f "$dir_of_file/index.md" ]; then + md_count_search=$(find "$dir_of_file" ! -name "$(basename "$dir_of_file")" -prune -name "*.md" | wc -l) + if [ "$md_count_search" -eq 1 ]; then + if [ "$rel_dir_of_file" = "." ]; then + md_url="/index.html" + else + md_url="/$rel_dir_of_file/index.html" + fi + fi + fi + fi + fi + + parse_frontmatter "$md_file" + [ "$fm_draft" = "true" ] && continue + [ -n "$fm_content_warning" ] && continue + + md_heading="$fm_title" + if [ -z "$md_heading" ]; then + md_heading=$(grep -m 1 '^# ' "$md_file" | sed 's/^# *//; s/ *$//') + if [ -n "$md_heading" ]; then + md_heading=$(echo "$md_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') + fi + fi + if [ -z "$md_heading" ]; then + basename_no_ext=$(basename "$md_file" .md) + if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then + md_heading=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}') + else + md_heading="$title - Page" + fi + fi + + if [ "$generate_search" = "true" ]; then + md_content=$(awk '{ + if (NR == 1 && $0 == "---") { in_fm = 1; next } + if (in_fm && $0 == "---") { in_fm = 0; next } + if (in_fm) next + if ($0 ~ /^```/) { in_code = !in_code; next } + if (in_code) next + print + }' "$md_file" | sed \ + -e 's/^#\{1,6\} //' \ + -e 's/\*\*\([^*]*\)\*\*/\1/g' \ + -e 's/\*\([^*]*\)\*/\1/g' \ + -e 's/__\([^_]*\)__/\1/g' \ + -e 's/_\([^_]*\)_/\1/g' \ + -e 's/`\([^`]*\)`/\1/g' \ + -e 's/\[\([^]]*\)](\([^)]*\))/\1/g' \ + -e 's/!\[\([^]]*\)](\([^)]*\))//g' \ + -e 's/^[[:space:]]*[-*+] //' \ + -e 's/^[[:space:]]*[0-9]\{1,\}\. //' \ + -e 's/^>[[:space:]]*//' \ + -e 's/<[^>]*>//g' \ + -e '/^[[:space:]]*$/d' \ + -e 's/|//g' \ + -e 's/^[[:space:]]*---[[:space:]]*$//' \ + | tr '\n' ' ' | sed -e 's/ */ /g' -e 's/\\/\\\\/g' -e 's/"/\\"/g' | head -c 500) + if [ "$first_search_item" = "false" ]; then + printf ',\n' >> "$out/search.json" + fi + printf ' {"url": "%s", "title": "%s", "content": "%s"}' "$md_url" "$md_heading" "$md_content" >> "$out/search.json" + first_search_item="false" + fi + + if [ "$generate_tags" = "true" ] && [ -n "$fm_tags" ]; then + old_ifs=$IFS + IFS=',' + for tag in $fm_tags; do + tag=$(echo "$tag" | sed 's/^[ \t]*//;s/[ \t]*$//') + [ -z "$tag" ] && continue + printf '%s|%s|%s\n' "$tag" "$md_url" "$md_heading" >> "$temp_tags" + done + IFS=$old_ifs + fi + done + + if [ "$generate_search" = "true" ]; then + printf '\n]\n' >> "$out/search.json" + + cp "$script_dir/lib/search.js" "$out/search.js" + + search_md="$KEWT_TMPDIR/search_$$.md" + printf '%s\n' '# Search' '' \ + '
' \ + ' ' \ + ' ' \ + '
' '' \ + '
' \ + '

Loading...

' \ + '
' '' \ + '' > "$search_md" + render_markdown "$search_md" "false" "/search.html" > "$out/search.html" + rm -f "$search_md" + fi + + if [ "$generate_tags" = "true" ]; then + tags_out_dir="$out/$tags_dir" + mkdir -p "$tags_out_dir" + + tags_index_md="$KEWT_TMPDIR/tags_index_$$.md" + echo "# Tags" > "$tags_index_md" + echo "" >> "$tags_index_md" + + cut -d'|' -f1 "$temp_tags" | sort -u | while IFS= read -r tag; do + tag_slug=$(echo "$tag" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g') + + echo "- [$tag](/$(echo $tags_dir | sed 's|^\/||; s|\/$||')/$tag_slug.html)" >> "$tags_index_md" + + tag_page_md="$KEWT_TMPDIR/tag_page_$$.md" + echo "# Tag: $tag" > "$tag_page_md" + echo "" >> "$tag_page_md" + echo "Posts tagged with **$tag**:" >> "$tag_page_md" + echo "" >> "$tag_page_md" + + grep "^${tag}|" "$temp_tags" | while IFS='|' read -r _t t_url t_title; do + echo "- [$t_title]($t_url)" >> "$tag_page_md" + done + + render_markdown "$tag_page_md" "false" "/$tags_dir/$tag_slug.html" > "$tags_out_dir/$tag_slug.html" + rm -f "$tag_page_md" + done + + render_markdown "$tags_index_md" "false" "/$tags_dir/index.html" > "$tags_out_dir/index.html" + rm -f "$tags_index_md" + fi + rm -f "$temp_tags" +fi + +echo "Build complete." +} diff --git a/lib/commands.sh b/lib/commands.sh new file mode 100644 index 0000000..10bd177 --- /dev/null +++ b/lib/commands.sh @@ -0,0 +1,140 @@ +usage() { + invoked_as=$(basename "${KEWT_INVOKED_AS:-$0}") + cat <] [--to ] + $invoked_as [src] [out] + $invoked_as --new [title] + $invoked_as --update [dir] + $invoked_as --post + $invoked_as --generate-template + $invoked_as --version + $invoked_as --help + +Options: + --help Show this help message. + --new [title] Create a new site directory (default: site) + --update [dir] Update site.conf and template.html with latest defaults (defaults to current directory) + --post Create a new empty post file in the configured posts_dir with current date and time as name + --generate-template [path] Generate a new template file at (default: template.html) + --version Show version information. + --from Source directory (default: site) + --to Output directory (default: out) + --watch, -w Watch for file changes and rebuild automatically. + --serve, -s [port] Start a local HTTP server after building (default port: 8000). +EOF +} +generate_template() { + _gt_path="$1" + [ -e "$_gt_path" ] && die "File '$_gt_path' already exists." + _gt_dir=$(dirname "$_gt_path") + [ -d "$_gt_dir" ] || mkdir -p "$_gt_dir" + printf '%s\n' "$DEFAULT_TMPL" > "$_gt_path" + echo "Generated template at '$_gt_path'." + exit 0 +} + +create_new_site() { + new_title="$1" + new_dir="site" + [ -n "$new_title" ] && new_dir="$new_title" + + [ -e "$new_dir" ] && die "Target '$new_dir' already exists." + + mkdir -p "$new_dir" + printf '%s\n' "$DEFAULT_CONF" > "$new_dir/site.conf" + printf '%s\n' "$DEFAULT_TMPL" > "$new_dir/template.html" + printf "# _kewt_ website\n" > "$new_dir/index.md" + + if [ -n "$new_title" ]; then + AWK_NEW_TITLE="$new_title" awk -f "$awk_dir/update_site_conf.awk" "$new_dir/site.conf" > "$new_dir/site.conf.tmp" && mv "$new_dir/site.conf.tmp" "$new_dir/site.conf" + fi + + echo "Created new site at '$new_dir'." + exit 0 +} + +create_new_post() { + post_src_dir="$1" + post_user_title="$2" + + target_dir="$post_src_dir" + if [ -n "$posts_dir" ]; then + target_dir="$post_src_dir/$posts_dir" + fi + + mkdir -p "$target_dir" + + base_filename="$(date +%Y-%m-%d-%H-%M)" + filename="${base_filename}.md" + file_path="$target_dir/$filename" + + counter=1 + while [ -e "$file_path" ]; do + filename="${base_filename}_${counter}.md" + file_path="$target_dir/$filename" + counter=$((counter + 1)) + done + + post_date_val="$(date "+%Y-%m-%d %H:%M")" + if [ -n "$post_user_title" ]; then + printf -- '---\ntitle = "%s"\ndate = "%s"\ndraft = %s\n---\n# %s\n' "$post_user_title" "$post_date_val" "$draft_by_default" "$post_user_title" > "$file_path" + else + printf -- '---\ndate = "%s"\ndraft = %s\n---\n' "$post_date_val" "$draft_by_default" > "$file_path" + fi + + echo "Created new post at '$file_path'." + exit 0 +} + +update_site() { + update_dir="${1:-.}" + [ -d "$update_dir" ] || die "Directory '$update_dir' does not exist." + + target_conf="$update_dir/site.conf" + target_tmpl="$update_dir/template.html" + + # Generate default site.conf + default_conf="$KEWT_TMPDIR/default_site.conf" + printf '%s\n' "$DEFAULT_CONF" > "$default_conf" + + # Update site.conf + if [ ! -f "$target_conf" ]; then + echo "No site.conf found in '$update_dir'; nothing to update." + else + added=0 + while IFS= read -r line; do + case "$line" in + ''|'#'*) continue ;; + *=*) ;; + *) continue ;; + esac + key=$(printf '%s' "${line%%=*}" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + if ! grep -q "^[[:space:]]*${key}[[:space:]]*=" "$target_conf"; then + printf '%s\n' "$line" >> "$target_conf" + echo " Added: $key" + added=$((added + 1)) + fi + done < "$default_conf" + if [ "$added" -eq 0 ]; then + echo "site.conf is already up to date." + else + echo "Added $added new key(s) to '$target_conf'." + fi + fi + + # Update template.html + if [ -f "$target_tmpl" ]; then + default_tmpl="$KEWT_TMPDIR/default_template.html" + printf '%s\n' "$DEFAULT_TMPL" > "$default_tmpl" + if cmp -s "$default_tmpl" "$target_tmpl" 2>/dev/null; then + echo "template.html is already up to date." + else + cp "$default_tmpl" "${target_tmpl}.default" + echo "template.html has local changes; saved latest default as '${target_tmpl}.default'." + echo "" + diff "$target_tmpl" "${target_tmpl}.default" || true + fi + fi + + exit 0 +} diff --git a/lib/config.sh b/lib/config.sh new file mode 100644 index 0000000..2b1a83a --- /dev/null +++ b/lib/config.sh @@ -0,0 +1,159 @@ +DEFAULT_CONF='title = "kewt" +style = "kewt" +lang = "en" +draft_by_default = false +dir_indexes = true +single_file_index = true +flatten = false +order = "" +home_name = "Home" +show_home_in_nav = true +nav_links = "" +nav_extra = "" +footer = "made with kewt" +logo = "" +display_logo = false +display_title = true +logo_as_favicon = true +favicon = "" +generate_page_title = true +error_page = "not_found.html" +versioning = false +enable_header_links = true +base_url = "" +generate_feed = false +feed_file = "rss.xml" +posts_dir = "" +posts_per_page = 12 +custom_admonitions = "" +cw_hide_url = true +generate_tags = false +tags_dir = "tags" +generate_search = false +search_in_footer = false +search_in_header = false' + +DEFAULT_TMPL=' + + + + + {{TITLE}} + + + {{HEAD_EXTRA}} + + + + +
+

{{HEADER_BRAND}}

+ +
+ + + +
{{CONTENT}}
+
{{FOOTER}}
+ +' + +title="kewt" +style="kewt" +lang="en" +draft_by_default="false" +footer="made with kewt" +dir_indexes="true" +single_file_index="true" +flatten="false" +order="" +home_name="Home" +show_home_in_nav="true" +nav_links="" +nav_extra="" +footer="made with kewt" +logo="" +display_logo="false" +display_title="true" +logo_as_favicon="true" +favicon="" +generate_page_title="true" +error_page="not_found.html" +versioning="false" +enable_header_links="true" +base_url="" +generate_feed="false" +feed_file="rss.xml" +posts_dir="" +posts_per_page="12" +custom_admonitions="" +cw_hide_url="true" +generate_tags="false" +tags_dir="tags" +generate_search="false" +search_in_footer="false" +search_in_header="false" + +load_config() { + [ -f "$1" ] || return + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + ''|'#'*) continue ;; + *=*) ;; + *) continue ;; + esac + + key=${line%%=*} + val=${line#*=} + + key=$(printf '%s' "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + val=$(printf '%s' "$val" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + case "$val" in + \"*\") + val=${val#\"}; val=${val%\"} + val=$(printf '%s' "$val" | sed 's/\\"/\"/g; s/\\\\/\\/g') + ;; + \'*\') + val=${val#\'}; val=${val%\'} + val=$(printf '%s' "$val" | sed "s/\\\\'/'/g; s/\\\\/\\/g") + ;; + esac + + case "$key" in + title) title="$val" ;; + style) style="${val#/}" ;; + dir_indexes) dir_indexes="$val" ;; + single_file_index) single_file_index="$val" ;; + flatten) flatten="$val" ;; + order) order="$val" ;; + home_name) home_name="$val" ;; + show_home_in_nav) show_home_in_nav="$val" ;; + nav_links) nav_links="$val" ;; + nav_extra) nav_extra="$val" ;; + footer) footer="$val" ;; + logo) logo="${val#/}" ;; + display_logo) display_logo="$val" ;; + display_title) display_title="$val" ;; + logo_as_favicon) logo_as_favicon="$val" ;; + favicon) favicon="${val#/}" ;; + generate_page_title) generate_page_title="$val" ;; + error_page) error_page="${val#/}" ;; + versioning) versioning="$val" ;; + enable_header_links) enable_header_links="$val" ;; + base_url) base_url="$val" ;; + generate_feed) generate_feed="$val" ;; + feed_file) feed_file="${val#/}" ;; + posts_dir) posts_dir="${val#/}" ;; + posts_per_page) posts_per_page="$val" ;; + custom_admonitions) custom_admonitions="$val" ;; + cw_hide_url) cw_hide_url="$val" ;; + lang) lang="$val" ;; + draft_by_default) draft_by_default="$val" ;; + generate_tags) generate_tags="$val" ;; + tags_dir) tags_dir="${val#/}" ;; + generate_search) generate_search="$val" ;; + search_in_footer) search_in_footer="$val" ;; + search_in_header) search_in_header="$val" ;; + esac + done < "$1" +} diff --git a/lib/generator.sh b/lib/generator.sh new file mode 100644 index 0000000..84bf64b --- /dev/null +++ b/lib/generator.sh @@ -0,0 +1,289 @@ +SEARCH_FORM_FOOTER='' +SEARCH_FORM_HEADER='
' +SEARCH_FORM_NAV='
' + +generate_nav() { + dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | AWK_SRC="$1" awk -f "$awk_dir/collect_dir_info.awk") + find_cmd="find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" + if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ]; then + find_cmd="$find_cmd && echo \"$1/$posts_dir/index.md\"" + fi + eval "$find_cmd" | sort -u | AWK_SRC="$1" AWK_SINGLE_FILE_INDEX="$single_file_index" AWK_FLATTEN="$flatten" AWK_ORDER="$order" AWK_HOME_NAME="$home_name" AWK_SHOW_HOME_IN_NAV="$show_home_in_nav" AWK_DINFO="$dinfo" awk -f "$awk_dir/generate_sidebar.awk" +} +escape_html_text() { + printf '%s' "$1" | sed \ + -e 's/&/\&/g' \ + -e 's//\>/g' +} +escape_html_attr() { + printf '%s' "$1" | sed \ + -e 's/&/\&/g' \ + -e 's/"/\"/g' \ + -e 's//\>/g' +} +parse_frontmatter() { + _fm_file="$1" + _fm_out="$KEWT_TMPDIR/fm_vals.txt" + : > "$_fm_out" + awk -v fm_out="$_fm_out" -f "$awk_dir/frontmatter.awk" "$_fm_file" > /dev/null + fm_title="" + fm_date="" + fm_draft="" + fm_description="" + fm_content_warning="" + fm_tags="" + while IFS='=' read -r _fk _fv; do + case "$_fk" in + title) fm_title="$_fv" ;; + date) fm_date="$_fv" ;; + draft) fm_draft="$_fv" ;; + description) fm_description="$_fv" ;; + content_warning) fm_content_warning="$_fv" ;; + tags) fm_tags="$_fv" ;; + esac + done < "$_fm_out" + rm -f "$_fm_out" +} +nav_links_html() { + [ -n "$nav_links" ] || return + + old_ifs=$IFS + set -f + IFS=',' + # shellcheck disable=SC2086 + set -- $nav_links + IFS=$old_ifs + set +f + + [ $# -gt 0 ] || return + + printf '' +} +find_closest() { + target="$1" + start_dir="$2" + curr="$start_dir" + while [ "$curr" != "$src" ] && [ "$curr" != "." ] && [ "$curr" != "/" ]; do + if [ -f "$curr/$target" ]; then + echo "$curr/$target" + return + fi + curr=$(dirname "$curr") + done + if [ -f "$src/$target" ]; then + echo "$src/$target" + fi +} +copy_style_with_resolved_vars() { + src_style="$1" + out_style="$2" + awk -f "$awk_dir/replace_variables.awk" "$src_style" > "$out_style" +} +render_markdown() { + file="$1" + is_home="$2" + url_override="$3" + + if [ -n "$url_override" ]; then + current_url="$url_override" + else + rel_path="${file#"$src"}" + rel_path="${rel_path#/}" + current_url="/${rel_path%.md}.html" + fi + + content_file="$file" + if [ -n "$posts_dir" ] && [ "$file" != "$src/$posts_dir/index.md" ]; then + rel_dir_of_url=$(dirname "$current_url") + rel_dir_of_url="${rel_dir_of_url#/}" + if { [ "$rel_dir_of_url" = "$posts_dir" ] || [ "./$rel_dir_of_url" = "$posts_dir" ]; } && [ "$(basename "$current_url")" != "index.html" ]; then + temp_post_with_backlink="$KEWT_TMPDIR/post_with_backlink_$$.md" + printf "[< Back](index.html)\n\n" > "$temp_post_with_backlink" + awk -f "$awk_dir/frontmatter.awk" "$file" >> "$temp_post_with_backlink" + + post_md_name="$(basename "$current_url" .html).md" + prevnext_file="$KEWT_TMPDIR/prevnext/$post_md_name" + if [ -f "$prevnext_file" ]; then + IFS='|' read -r prev_str next_str < "$prevnext_file" + + printf "\n\n---\n
\n" >> "$temp_post_with_backlink" + if [ -n "$prev_str" ]; then + printf "%s\n" "$prev_str" >> "$temp_post_with_backlink" + fi + if [ -n "$next_str" ]; then + printf "%s\n" "$next_str" >> "$temp_post_with_backlink" + fi + printf "
\n" >> "$temp_post_with_backlink" + fi + content_file="$temp_post_with_backlink" + fi + fi + + local_template=$(find_closest "template.html" "$(dirname "$file")") + [ -z "$local_template" ] && local_template="$template" + + closest_style_src=$(find_closest "styles.css" "$(dirname "$file")") + [ -z "$closest_style_src" ] && closest_style_src=$(find_closest "style.css" "$(dirname "$file")") + if [ -n "$closest_style_src" ]; then + style_rel_to_src="${closest_style_src#"$src"/}" + case "$closest_style_src" in + "$src/styles.css") style_rel_to_src="styles.css" ;; + "$src/style.css") style_rel_to_src="style.css" ;; + esac + style_path="/${style_rel_to_src%styles.css}" + style_path="${style_path%style.css}styles.css" + else + style_path="/styles.css" + fi + + logo_html="" + if [ "$display_logo" = "true" ] && [ -n "$logo" ]; then + logo_html="\"$title\"" + fi + + brand_text="" + if [ "$display_title" = "true" ]; then + brand_text="$title" + fi + + if [ -n "$logo_html" ] && [ -n "$brand_text" ]; then + header_brand="$logo_html $brand_text" + elif [ -n "$logo_html" ]; then + header_brand="$logo_html" + elif [ -n "$brand_text" ]; then + header_brand="$brand_text" + else + header_brand="$title" + fi + + favicon_src="" + if [ "$logo_as_favicon" = "true" ] && [ -n "$logo" ]; then + favicon_src="$logo" + elif [ -n "$favicon" ]; then + favicon_src="$favicon" + fi + head_extra="" + if [ -n "$favicon_src" ]; then + if echo "$favicon_src" | grep -q "^http"; then + head_extra="" + elif echo "$favicon_src" | grep -q "^/"; then + head_extra="" + else + head_extra="" + fi + fi + + parse_frontmatter "$file" + + page_title="$title" + if [ -n "$fm_title" ]; then + page_title="$fm_title - $title" + elif [ "$generate_page_title" = "true" ] && [ -n "$file" ] && [ -f "$file" ]; then + if [ "$is_home" = "true" ] && [ -n "$home_name" ]; then + page_title="$home_name - $title" + else + first_heading=$(grep -m 1 '^# ' "$file" | sed 's/^# *//; s/ *$//') + if [ -n "$first_heading" ]; then + first_heading=$(echo "$first_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') + page_title="$first_heading - $title" + else + basename_no_ext=$(basename "$file" .md) + if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then + cap_basename=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}') + page_title="$cap_basename - $title" + fi + fi + fi + fi + + head_extra_og="" + if [ -n "$fm_description" ]; then + head_extra_og="$head_extra_og + " + fi + og_url="${base_url%/}${current_url}" + head_extra_og="$head_extra_og + " + + if [ -n "$head_extra" ]; then + head_extra="$head_extra + $head_extra_og" + else + head_extra="$head_extra_og" + fi + + if [ "$is_cw_content_page" = "true" ] && [ "$cw_hide_url" = "true" ]; then + head_extra="$head_extra + " + fi + + final_footer="$footer" + if [ "$search_in_footer" = "true" ]; then + final_footer="$footer $SEARCH_FORM_FOOTER" + fi + + final_nav="$nav" + final_header_brand="$header_brand" + if [ "$search_in_header" = "true" ]; then + final_header_brand="$header_brand $SEARCH_FORM_HEADER" + final_nav="$SEARCH_FORM_NAV +$nav" + fi + + ENABLE_HEADER_LINKS="$enable_header_links" CUSTOM_ADMONITIONS="$custom_admonitions" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_LANG="$lang" AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$final_nav" AWK_FOOTER="$final_footer" AWK_STYLE_PATH="${style_path}" AWK_HEADER_BRAND="$final_header_brand" AWK_HEAD_EXTRA="$head_extra" AWK_VERSION="$asset_version" AWK_CONTENT_WARNING="$fm_content_warning" awk -f "$awk_dir/render_template.awk" "$local_template" +} +generate_content_warning_page() { + _fm_title="$1" + _fm_content_warning="$2" + _content_rel_url="$3" + _target_url="$4" + _out_file="$5" + _is_home="$6" + + _temp_cw="$KEWT_TMPDIR/cw_$$.md" + _cw_text="${_fm_content_warning}" + [ "$_cw_text" = "true" ] && _cw_text="This content may be sensitive." + + cat < "$_temp_cw" +--- +title = "$_fm_title" +--- + +> [!CAUTION] +> **Content Warning:** $_cw_text + +Reveal Content +EOF + render_markdown "$_temp_cw" "$_is_home" "$_target_url" > "$_out_file" + rm -f "$_temp_cw" +} diff --git a/lib/search.js b/lib/search.js new file mode 100644 index 0000000..d204ff1 --- /dev/null +++ b/lib/search.js @@ -0,0 +1,44 @@ +document.addEventListener('DOMContentLoaded', function() { + var params = new URLSearchParams(window.location.search); + var query = params.get('q'); + var box = document.getElementById('search-box'); + var resultsContainer = document.getElementById('search-results-list'); + + if (box && query) box.value = query; + + if (!query) { + resultsContainer.innerHTML = '

Enter a search term above.

'; + return; + } + + fetch('/search.json') + .then(function(response) { return response.json(); }) + .then(function(data) { + var q = query.toLowerCase(); + var results = data.filter(function(item) { + return item.title.toLowerCase().indexOf(q) !== -1 || + item.content.toLowerCase().indexOf(q) !== -1; + }); + + var esc = query.replace(//g, '>'); + + if (results.length === 0) { + resultsContainer.innerHTML = '

No results found for "' + esc + '".

'; + return; + } + + var html = '

Found ' + results.length + ' result(s) for "' + esc + '":

'; + results.forEach(function(result) { + var snippet = result.content.substring(0, 200); + if (result.content.length > 200) snippet += '...'; + html += '
'; + html += '' + result.title + ''; + if (snippet) html += '

' + snippet + '

'; + html += '
'; + }); + resultsContainer.innerHTML = html; + }) + .catch(function() { + resultsContainer.innerHTML = '

Error loading search index.

'; + }); +}); diff --git a/site/site.conf b/site/site.conf index dbf5ff1..047411b 100644 --- a/site/site.conf +++ b/site/site.conf @@ -22,4 +22,8 @@ base_url = "https://kewt.krzak.org" custom_admonitions = "" generate_feed = false feed_file = "rss.xml" -posts_dir = "" \ No newline at end of file +posts_dir = "" +generate_tags = false +generate_search = true +search_in_footer = true +search_in_header = true \ No newline at end of file diff --git a/styles/kewt.css b/styles/kewt.css index 1d748ea..0a0e000 100644 --- a/styles/kewt.css +++ b/styles/kewt.css @@ -48,6 +48,9 @@ header h1 { font-weight: bold; font-style: italic; color: var(--fg-heading); + display: flex; + align-items: center; + flex-wrap: wrap; } .site-logo { @@ -218,6 +221,7 @@ pre code { background: var(--adm-caution-bg); border-color: var(--adm-caution-border); } + .cw-button { display: inline-block; padding: 8px 16px; @@ -321,4 +325,215 @@ hr { .task-list-item-checkbox { margin: 0 0.2em 0.25em -1.6em; vertical-align: middle; +} + +.kewt-search-page { + display: flex; + gap: 8px; + margin: 20px 0; +} + +.kewt-search-page input[type="text"] { + flex: 1; + padding: 8px 12px; + font-size: 16px; + font-family: inherit; + background: var(--code-bg); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + outline: none; +} + +.kewt-search-page input[type="text"]:focus { + border-color: var(--fg-link); +} + +.kewt-search-page button { + padding: 8px 20px; + font-size: 16px; + font-family: inherit; + background: var(--bg-deep); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + cursor: pointer; +} + +.kewt-search-page button:hover { + background: var(--fg); + color: var(--bg); +} + +.search-result { + margin: 16px 0; + padding: 12px; + background: var(--code-bg); + border: 1px solid var(--code-border); + border-radius: 3px; +} + +.search-result a { + font-size: 18px; + font-weight: bold; + color: var(--fg-link); +} + +.search-result p { + margin: 6px 0 0 0; + color: var(--fg-muted); + font-size: 14px; +} + +/* Footer search */ +.kewt-search-footer { + display: inline-flex; + gap: 4px; + margin-left: 12px; + vertical-align: middle; +} + +.kewt-search-footer input[type="text"] { + padding: 3px 8px; + font-size: 14px; + font-family: inherit; + background: var(--code-bg); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + outline: none; + width: 120px; +} + +.kewt-search-footer input[type="text"]:focus { + border-color: var(--fg-link); +} + +.kewt-search-footer button { + padding: 3px 10px; + font-size: 14px; + font-family: inherit; + background: var(--bg-deep); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + cursor: pointer; +} + +.kewt-search-footer button:hover { + background: var(--fg); + color: var(--bg); +} + +.kewt-search-header { + display: inline-flex; + gap: 4px; + margin-left: auto; + vertical-align: middle; + font-style: normal; +} + +.kewt-search-header input[type="text"] { + padding: 4px 8px; + font-size: 14px; + font-family: inherit; + background: var(--code-bg); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + outline: none; + width: 160px; +} + +.kewt-search-header input[type="text"]:focus { + border-color: var(--fg-link); +} + +.kewt-search-header button { + padding: 4px 10px; + font-size: 14px; + font-family: inherit; + background: var(--bg-deep); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + cursor: pointer; +} + +.kewt-search-header button:hover { + background: var(--fg); + color: var(--bg); +} + +.kewt-search-nav { + display: none; + padding: 8px 0 12px 0; + margin-bottom: 8px; + border-bottom: 1px solid var(--code-border); +} + +.kewt-search-nav form { + display: flex; + gap: 4px; +} + +.kewt-search-nav input[type="text"] { + flex: 1; + padding: 6px 8px; + font-size: 14px; + font-family: inherit; + background: var(--code-bg); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + outline: none; +} + +.kewt-search-nav input[type="text"]:focus { + border-color: var(--fg-link); +} + +.kewt-search-nav button { + padding: 6px 10px; + font-size: 14px; + font-family: inherit; + background: var(--bg-deep); + color: var(--fg); + border: 1px solid var(--code-border); + border-radius: 3px; + cursor: pointer; +} + +.kewt-search-nav button:hover { + background: var(--fg); + color: var(--bg); +} + +@media screen and (max-width: 600px) { + .kewt-search-header { + display: none; + } + + .kewt-search-nav { + display: block; + } + + .kewt-search-page { + flex-direction: column; + } + + .kewt-search-page button { + align-self: flex-start; + } + + .kewt-search-footer { + display: flex; + margin-left: 0; + margin-top: 8px; + } + + .kewt-search-footer input[type="text"] { + flex: 1; + width: auto; + } } \ No newline at end of file diff --git a/tools/build-standalone.sh b/tools/build-standalone.sh index e63f6bd..c6811d7 100755 --- a/tools/build-standalone.sh +++ b/tools/build-standalone.sh @@ -28,10 +28,10 @@ EOF VERSION=$(git describe --tags 2>/dev/null || echo "standalone") tmpbuild=$(mktemp -d) -cp -r "$REPO_ROOT/kewt.sh" "$REPO_ROOT/markdown.sh" "$REPO_ROOT/awk" "$REPO_ROOT/styles" "$tmpbuild/" +cp -r "$REPO_ROOT/kewt.sh" "$REPO_ROOT/markdown.sh" "$REPO_ROOT/awk" "$REPO_ROOT/styles" "$REPO_ROOT/lib" "$tmpbuild/" sed -e "s/kewt version git/kewt version $VERSION/" "$tmpbuild/kewt.sh" > "$tmpbuild/kewt.sh.tmp" && mv "$tmpbuild/kewt.sh.tmp" "$tmpbuild/kewt.sh" chmod +x "$tmpbuild/kewt.sh" "$tmpbuild/markdown.sh" -tar -cz -C "$tmpbuild" kewt.sh markdown.sh awk styles >> "$OUT_FILE" +tar -cz -C "$tmpbuild" kewt.sh markdown.sh awk styles lib >> "$OUT_FILE" rm -rf "$tmpbuild" chmod +x "$OUT_FILE"