initial commit
This commit is contained in:
commit
ea7537565c
183
.githooks/pre-commit
Executable file
183
.githooks/pre-commit
Executable file
@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# This script is run by git before a commit is made. #
|
||||||
|
# To use it, copy it to .git/hooks/pre-commit and make it executable. #
|
||||||
|
# Alternatively, run the following command from the root of the repo: #
|
||||||
|
# git config core.hooksPath .githooks #
|
||||||
|
# #
|
||||||
|
# FEATURES #
|
||||||
|
# Updates the "updated" field in the front matter of .md files. #
|
||||||
|
# Compresses PNG files with either oxipng or optipng if available. #
|
||||||
|
# Creates/updates the social media card for .md files. #
|
||||||
|
# Aborts the commit if a draft .md file or a file with "TODO" is being committed. #
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
|
# Check if the script is being run from the root of the repo.
|
||||||
|
if [[ ! $(git rev-parse --show-toplevel) == $(pwd) ]]; then
|
||||||
|
error_exit "This script must be run from the root of the repo."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to exit the script with an error message.
|
||||||
|
function error_exit() {
|
||||||
|
echo "ERROR: $1" >&2
|
||||||
|
exit "${2:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to extract the date from the front matter.
|
||||||
|
function extract_date() {
|
||||||
|
local file="$1"
|
||||||
|
local field="$2"
|
||||||
|
grep -m 1 "^$field =" "$file" | sed -e "s/$field = //" -e 's/ *$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if the .md file is a draft.
|
||||||
|
function is_draft() {
|
||||||
|
local file="$1"
|
||||||
|
grep -q "draft = true" "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the file contains "TODO".
|
||||||
|
function contains_todo() {
|
||||||
|
local file="$1"
|
||||||
|
grep -q "TODO" "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for changes outside of the front matter.
|
||||||
|
function has_body_changes() {
|
||||||
|
local file="$1"
|
||||||
|
local in_front_matter=1
|
||||||
|
local triple_plus_count=0
|
||||||
|
|
||||||
|
diff_output=$(git diff --unified=999 --cached --output-indicator-new='%' --output-indicator-old='&' "$file")
|
||||||
|
|
||||||
|
while read -r line; do
|
||||||
|
if [[ "$line" =~ ^\+\+\+$ ]]; then
|
||||||
|
triple_plus_count=$((triple_plus_count + 1))
|
||||||
|
if [[ $triple_plus_count -eq 2 ]]; then
|
||||||
|
in_front_matter=0
|
||||||
|
fi
|
||||||
|
elif [[ $in_front_matter -eq 0 ]]; then
|
||||||
|
if [[ "$line" =~ ^[\%\&] ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$diff_output"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to update the social media card for a post or section.
|
||||||
|
function generate_and_commit_card {
|
||||||
|
local file=$1
|
||||||
|
social_media_card=$(social-cards-zola -o static/img/social_cards -b http://127.0.0.1:1111 -u -p -i "$file") || {
|
||||||
|
echo "Failed to update social media card for $file"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
git add "$social_media_card" || {
|
||||||
|
echo "Failed to add social media card $social_media_card"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
git add "$file" || {
|
||||||
|
echo "Failed to add $file"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export -f generate_and_commit_card
|
||||||
|
|
||||||
|
# Get the modified .md to update the "updated" field in the front matter.
|
||||||
|
mapfile -t modified_md_files < <(git diff --cached --name-only --diff-filter=M | grep -Ei '\.md$' | grep -v '_index.md$')
|
||||||
|
|
||||||
|
# Loop through each modified .md file.
|
||||||
|
for file in "${modified_md_files[@]}"; do
|
||||||
|
# If changes are only in the front matter, skip the file.
|
||||||
|
if ! has_body_changes "$file"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the last modified date from the filesystem.
|
||||||
|
last_modified_date=$(date -r "$file" +'%Y-%m-%d')
|
||||||
|
|
||||||
|
# Extract the "date" field from the front matter.
|
||||||
|
date_value=$(extract_date "$file" "date")
|
||||||
|
|
||||||
|
# Skip the file if the last modified date is the same as the "date" field.
|
||||||
|
if [[ "$last_modified_date" == "$date_value" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update the "updated" field with the last modified date.
|
||||||
|
# If the "updated" field doesn't exist, create it below the "date" field.
|
||||||
|
if ! awk -v date_line="$last_modified_date" 'BEGIN{FS=OFS=" = "; first = 1} {
|
||||||
|
if (/^date =/ && first) {
|
||||||
|
print; getline;
|
||||||
|
if (!/^updated =/) print "updated" OFS date_line;
|
||||||
|
first=0;
|
||||||
|
}
|
||||||
|
if (/^updated =/ && !first) gsub(/[^ ]*$/, date_line, $2);
|
||||||
|
print;
|
||||||
|
}' "$file" > "${file}.tmp"
|
||||||
|
then
|
||||||
|
error_exit "Failed to process $file with AWK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "${file}.tmp" "$file" || error_exit "Failed to overwrite $file with updated content"
|
||||||
|
|
||||||
|
# Stage the changes.
|
||||||
|
git add "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if oxipng is installed.
|
||||||
|
png_compressor=""
|
||||||
|
if command -v oxipng &> /dev/null; then
|
||||||
|
png_compressor="oxipng -o max"
|
||||||
|
elif command -v optipng &> /dev/null; then
|
||||||
|
png_compressor="optipng -o 7"
|
||||||
|
fi
|
||||||
|
|
||||||
|
##################################################################
|
||||||
|
# Compress PNG files with either oxipng or optipng if available. #
|
||||||
|
# Update the "updated" field in the front matter of .md files. #
|
||||||
|
# https://osc.garden/blog/zola-date-git-hook/ #
|
||||||
|
# Interrupt the commit if a draft .md file is being committed. #
|
||||||
|
# Interrupt the commit if a file with "TODO" is being committed. #
|
||||||
|
# Create/update the social media card for posts and sections. #
|
||||||
|
# For more info about the social media cards part, see: #
|
||||||
|
# https://osc.garden/blog/automating-social-media-cards-zola #
|
||||||
|
##################################################################
|
||||||
|
|
||||||
|
# Get the newly added and modified files, but not deleted files.
|
||||||
|
mapfile -t all_changed_files < <(git diff --cached --name-only --diff-filter=d)
|
||||||
|
|
||||||
|
# Loop through each newly added or modified .md or .png file for PNG optimization and draft checking.
|
||||||
|
for file in "${all_changed_files[@]}"; do
|
||||||
|
# Ignore this script.
|
||||||
|
if [[ "$file" == "$0" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If the file is a PNG and png_compressor is set, compress it and add it to the commit.
|
||||||
|
if [[ "$file" =~ \.png$ ]] && [[ -n "$png_compressor" ]]; then
|
||||||
|
$png_compressor "$file" || error_exit "Failed to compress PNG file $file"
|
||||||
|
git add "$file" || error_exit "Failed to add compressed PNG file $file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If the file is an .md file and it's a draft, abort the commit.
|
||||||
|
if [[ "$file" == *.md ]]; then
|
||||||
|
if is_draft "$file"; then
|
||||||
|
error_exit "Draft file $file is being committed!"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Use `social-cards-zola` to create/update the social media card for Markdown files.
|
||||||
|
# See https://osc.garden/blog/automating-social-media-cards-zola/ for context.
|
||||||
|
changed_md_files=$(echo "$all_changed_files" | grep '\.md$')
|
||||||
|
echo "Changed Markdown files: $changed_md_files"
|
||||||
|
# Use parallel to create the social media cards in parallel and commit them.
|
||||||
|
if [[ -n "$changed_md_files" ]]; then
|
||||||
|
echo "$changed_md_files" | parallel -j 8 generate_and_commit_card
|
||||||
|
fi
|
228
.githooks/social-cards-zola
Executable file
228
.githooks/social-cards-zola
Executable file
@ -0,0 +1,228 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# This script takes a markdown post, crafts the corresponding URL, checks if it's accessible,
|
||||||
|
# takes a screenshot, and saves it to a specified location.
|
||||||
|
# It can update the front matter of the post with the path to the generated image (-u | --update-front-matter option).
|
||||||
|
# It's meant to be used as a pre-commit hook to generate social media cards for Zola sites using the tabi theme.
|
||||||
|
# More details: https://osc.garden/blog/automating-social-media-cards-zola/
|
||||||
|
|
||||||
|
function help_function(){
|
||||||
|
echo "This script automates the creation of social media cards for Zola websites."
|
||||||
|
echo "It takes a Markdown post and saves its live screenshot to a specified location."
|
||||||
|
echo ""
|
||||||
|
echo "IMPORTANT! It needs to be run from the root of the Zola site."
|
||||||
|
echo ""
|
||||||
|
echo "Usage: social-cards-zola [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message and exit."
|
||||||
|
echo " -b, --base_url URL The base URL where the Zola site is hosted. Default is http://127.0.0.1:1111."
|
||||||
|
echo " -i, --input INPUT_PATH The relative path to the markdown file of the post/section you want to capture. Should be in the format 'content/blog/post_name.language.md'."
|
||||||
|
echo " -k, --key KEY The front matter key to update. Default is 'social_media_card'."
|
||||||
|
echo " -o, --output_path PATH The directory where the generated image will be saved."
|
||||||
|
echo " -p, --print_output Print the path to the resulting screenshot at the end."
|
||||||
|
echo " -u, --update-front-matter Update or add the 'social_media_card' key in the front matter of the Markdown file."
|
||||||
|
echo
|
||||||
|
echo "Examples:"
|
||||||
|
echo " social-cards-zola --base_url https://example.com --input content/blog/my_post.md --output_path static/img/social_cards"
|
||||||
|
echo " social-cards-zola -u -b http://127.0.0.1:1025 -i content/archive/_index.es.md -o static/img"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert_filename_to_url() {
|
||||||
|
# Remove .md extension.
|
||||||
|
local post_name="${1%.md}"
|
||||||
|
|
||||||
|
# Remove "content/" prefix.
|
||||||
|
local url="${post_name#content/}"
|
||||||
|
|
||||||
|
# Extract language code.
|
||||||
|
local lang_code="${url##*.}"
|
||||||
|
if [[ "$lang_code" == "$url" ]]; then
|
||||||
|
lang_code="" # No language code.
|
||||||
|
else
|
||||||
|
lang_code="${lang_code}/" # Add trailing slash.
|
||||||
|
url="${url%.*}" # Remove the language code from the URL.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle co-located index.md by stripping it and using the directory as the URL.
|
||||||
|
if [[ "$url" == */index ]]; then
|
||||||
|
url="${url%/*}" # Remove the /index suffix.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove "_index" suffix.
|
||||||
|
if [[ "$url" == *"_index"* ]]; then
|
||||||
|
url="${url%%_index*}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return the final URL with a single trailing slash.
|
||||||
|
full_url="${lang_code}${url}"
|
||||||
|
echo "${full_url%/}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_exit() {
|
||||||
|
echo "ERROR: $1" >&2
|
||||||
|
exit "${2:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_input_params() {
|
||||||
|
missing_params=()
|
||||||
|
if [[ -z "$base_url" ]]; then
|
||||||
|
missing_params+=("base_url")
|
||||||
|
fi
|
||||||
|
if [[ -z "$input" ]]; then
|
||||||
|
missing_params+=("input")
|
||||||
|
fi
|
||||||
|
if [[ -z "$output_path" ]]; then
|
||||||
|
missing_params+=("output_path")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing_params[@]} -ne 0 ]; then
|
||||||
|
error_exit "The following required settings are missing: ${missing_params[*]}. Use -h or --help for usage."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_dependencies() {
|
||||||
|
for cmd in "curl" "shot-scraper"; do
|
||||||
|
if ! command -v $cmd &> /dev/null; then
|
||||||
|
error_exit "$cmd could not be found. Please install it."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_status() {
|
||||||
|
local retry_count=0
|
||||||
|
local max_retries=5
|
||||||
|
local status
|
||||||
|
while [[ $retry_count -lt $max_retries ]]; do
|
||||||
|
status=$(curl -s -o /dev/null -I -w "%{http_code}" "${base_url}${post_url}")
|
||||||
|
if [[ "$status" -eq "200" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
retry_count=$((retry_count + 1))
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
error_exit "Post $input is not accessible. Max retries ($max_retries) reached."
|
||||||
|
}
|
||||||
|
|
||||||
|
function capture_screenshot() {
|
||||||
|
temp_file=$(mktemp /tmp/social-zola.XXXXXX)
|
||||||
|
trap 'rm -f "$temp_file"' EXIT
|
||||||
|
shot-scraper --silent "${base_url}/${post_url}" -w 700 -h 400 --retina --quality 60 -o "$temp_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
function move_file() {
|
||||||
|
local safe_filename=$(echo "${post_url%/}" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
|
|
||||||
|
# Create the output directory if it doesn't exist.
|
||||||
|
mkdir -p "$output_path"
|
||||||
|
|
||||||
|
image_filename="${output_path}/${safe_filename:-index}.jpg" # If the filename is empty, use "index".
|
||||||
|
mv "$temp_file" "$image_filename" || error_exit "Failed to move the file to $image_filename"
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_front_matter {
|
||||||
|
local md_file_path="$1"
|
||||||
|
local image_output="${2#static/}"
|
||||||
|
# Temporary file for awk processing
|
||||||
|
temp_awk=$(mktemp /tmp/frontmatter.XXXXXX)
|
||||||
|
|
||||||
|
awk -v card_path="$image_output" '
|
||||||
|
# Initialize flags for tracking state.
|
||||||
|
BEGIN { in_extra=done=front_matter=extra_exists=0; }
|
||||||
|
|
||||||
|
# Function to insert the social_media_card path.
|
||||||
|
function insert_card() { print "social_media_card = \"" card_path "\""; done=1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
# If card has been inserted, simply output remaining lines.
|
||||||
|
if (done) { print; next; }
|
||||||
|
|
||||||
|
# Toggle front_matter flag at its start, denoted by +++
|
||||||
|
if (/^\+\+\+/ && front_matter == 0) {
|
||||||
|
front_matter = 1;
|
||||||
|
print "+++";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect [extra] section and set extra_exists flag.
|
||||||
|
if (/^\[extra\]/) { in_extra=1; extra_exists=1; print; next; }
|
||||||
|
|
||||||
|
# Update existing social_media_card.
|
||||||
|
if (in_extra && /^social_media_card =/) { insert_card(); in_extra=0; next; }
|
||||||
|
|
||||||
|
# End of front matter or start of new section.
|
||||||
|
if (in_extra && (/^\[[a-zA-Z_-]+\]/ || (/^\+\+\+/ && front_matter == 1))) {
|
||||||
|
insert_card(); # Add the missing social_media_card.
|
||||||
|
in_extra=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insert missing [extra] section.
|
||||||
|
if (/^\+\+\+/ && front_matter == 1 && in_extra == 0 && extra_exists == 0) {
|
||||||
|
print "\n[extra]";
|
||||||
|
insert_card();
|
||||||
|
in_extra=0;
|
||||||
|
front_matter = 0;
|
||||||
|
print "+++";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print all other lines as-is.
|
||||||
|
print;
|
||||||
|
}' "$md_file_path" > "$temp_awk"
|
||||||
|
|
||||||
|
# Move the temporary file back to the original markdown file.
|
||||||
|
mv "$temp_awk" "$md_file_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
help_function;;
|
||||||
|
-b|--base_url)
|
||||||
|
base_url="$2"
|
||||||
|
shift 2;;
|
||||||
|
-i|--input)
|
||||||
|
input="$2"
|
||||||
|
shift 2;;
|
||||||
|
-o|--output_path)
|
||||||
|
output_path="$2"
|
||||||
|
shift 2;;
|
||||||
|
-k|--key)
|
||||||
|
front_matter_key="$2"
|
||||||
|
shift 2;;
|
||||||
|
-u|--update-front-matter)
|
||||||
|
update="true"
|
||||||
|
shift 1;;
|
||||||
|
-p|--print_output)
|
||||||
|
print_output="true"
|
||||||
|
shift 1;;
|
||||||
|
*)
|
||||||
|
error_exit "Unknown option: $1";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
validate_input_params
|
||||||
|
check_dependencies
|
||||||
|
|
||||||
|
: "${base_url:="http://127.0.0.1:1111"}"
|
||||||
|
: "${front_matter_key:="social_media_card"}"
|
||||||
|
base_url="${base_url%/}/" # Ensure one trailing slash.
|
||||||
|
post_url="$(convert_filename_to_url "$input")"
|
||||||
|
|
||||||
|
fetch_status
|
||||||
|
capture_screenshot
|
||||||
|
move_file
|
||||||
|
|
||||||
|
if [[ "$update" == "true" ]]; then
|
||||||
|
update_front_matter "$input" "$image_filename"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$print_output" == "true" ]]; then
|
||||||
|
echo "$image_filename"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "themes/tabi"]
|
||||||
|
path = themes/tabi
|
||||||
|
url = https://github.com/welpo/tabi.git
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
6
.idea/git_toolbox_blame.xml
generated
Normal file
6
.idea/git_toolbox_blame.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GitToolBoxBlameSettings">
|
||||||
|
<option name="version" value="2" />
|
||||||
|
</component>
|
||||||
|
</project>
|
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KubernetesApiProvider">{}</component>
|
||||||
|
</project>
|
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/themes/tabi" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
39
config.toml
Normal file
39
config.toml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# The URL the site will be built for
|
||||||
|
base_url = "https://www.aldofunes.com"
|
||||||
|
default_language = "en"
|
||||||
|
build_search_index = true
|
||||||
|
theme = "tabi"
|
||||||
|
title = "Aldo Funes"
|
||||||
|
taxonomies = [{name = "tags", feed = true}]
|
||||||
|
|
||||||
|
[languages.es]
|
||||||
|
title = "Aldo Funes"
|
||||||
|
taxonomies = [{name = "tags", feed = true}]
|
||||||
|
|
||||||
|
[languages.pt-PT]
|
||||||
|
title = "Aldo Funes"
|
||||||
|
taxonomies = [{name = "tags", feed = true}]
|
||||||
|
|
||||||
|
[markdown]
|
||||||
|
# Whether to do syntax highlighting
|
||||||
|
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
|
||||||
|
highlight_code = true
|
||||||
|
#highlight_theme = "dracula"
|
||||||
|
highlight_theme = "css"
|
||||||
|
highlight_themes_css = [
|
||||||
|
{ theme = "dracula", filename = "css/syntax.css" },
|
||||||
|
]
|
||||||
|
external_links_class = "external"
|
||||||
|
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
# Put all your custom variables here
|
||||||
|
stylesheets = ["css/syntax.css"]
|
||||||
|
|
||||||
|
remote_repository_url = "https://github.com/aldofunes/blog"
|
||||||
|
remote_repository_git_platform = "auto"
|
||||||
|
remote_repository_branch = "main"
|
||||||
|
show_remote_changes = true
|
||||||
|
show_remote_source = true
|
||||||
|
|
||||||
|
favicon_emoji = "👾"
|
12
content/_index.es.md
Normal file
12
content/_index.es.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
+++
|
||||||
|
title = "Home"
|
||||||
|
# Note we're not setting `paginate_by` here.
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
header = { title = "Hola! I'm aldo", img = "img/main.webp", img_alt = "Óscar Fernández, the theme's author" }
|
||||||
|
|
||||||
|
#section_path = "blog/_index.md" # Where to find your posts.
|
||||||
|
#max_posts = 5 # Show 5 posts on the home page.
|
||||||
|
+++
|
||||||
|
|
||||||
|
# Welcome to your new blog!
|
12
content/_index.md
Normal file
12
content/_index.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
+++
|
||||||
|
title = "Home"
|
||||||
|
# Note we're not setting `paginate_by` here.
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
header = { title = "Hello! I'm aldo", img = "img/main.webp", img_alt = "Óscar Fernández, the theme's author" }
|
||||||
|
|
||||||
|
#section_path = "blog/_index.md" # Where to find your posts.
|
||||||
|
#max_posts = 5 # Show 5 posts on the home page.
|
||||||
|
+++
|
||||||
|
|
||||||
|
# Welcome to your new blog!
|
4
content/archive/_index.md
Normal file
4
content/archive/_index.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
+++
|
||||||
|
title = "Archive"
|
||||||
|
#template = "archive.html"
|
||||||
|
+++
|
4
content/blog/_index.md
Normal file
4
content/blog/_index.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
+++
|
||||||
|
title = "Blog"
|
||||||
|
paginate_by = 5 # Show 5 posts per page.
|
||||||
|
+++
|
13
content/blog/infrastructure_as_code.md
Normal file
13
content/blog/infrastructure_as_code.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
+++
|
||||||
|
title = "Infrastructure as code"
|
||||||
|
+++
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
pub async fn main() -> Result<(), Error> {
|
||||||
|
let mut infrastructure = Infrastructure::new();
|
||||||
|
dbg!(infrastructure);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is [a link](https://example.com) to example.com.
|
4
content/pages/_index.md
Normal file
4
content/pages/_index.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
+++
|
||||||
|
render = false
|
||||||
|
insert_anchor_links = "left"
|
||||||
|
+++
|
7
content/pages/about.md
Normal file
7
content/pages/about.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
+++
|
||||||
|
title = "About"
|
||||||
|
template = "info-page.html"
|
||||||
|
path = "about"
|
||||||
|
+++
|
||||||
|
|
||||||
|
# About me
|
10
content/projects/_index.md
Normal file
10
content/projects/_index.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
+++
|
||||||
|
title = "Projects"
|
||||||
|
sort_by = "weight"
|
||||||
|
template = "cards.html"
|
||||||
|
insert_anchor_links = "left"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
show_reading_time = false
|
||||||
|
quick_navigation_buttons = true
|
||||||
|
+++
|
12
content/projects/airtm.md
Normal file
12
content/projects/airtm.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
+++
|
||||||
|
title = "Airtm"
|
||||||
|
description = "A dollar wallet"
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
tags = ["tag one", "tag 2", "third tag"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
local_image = "img/seedling.png"
|
||||||
|
link_to = "https://airtm.com"
|
||||||
|
+++
|
11
content/projects/tabi.md
Normal file
11
content/projects/tabi.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
+++
|
||||||
|
title = "tabi"
|
||||||
|
description = "A feature-rich modern Zola theme with first-class multi-language support."
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
tags = ["tag one", "tag 2", "third tag"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
local_image = "img/seedling.png"
|
||||||
|
+++
|
228
public/code/social-cards-zola
Executable file
228
public/code/social-cards-zola
Executable file
@ -0,0 +1,228 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# This script takes a markdown post, crafts the corresponding URL, checks if it's accessible,
|
||||||
|
# takes a screenshot, and saves it to a specified location.
|
||||||
|
# It can update the front matter of the post with the path to the generated image (-u | --update-front-matter option).
|
||||||
|
# It's meant to be used as a pre-commit hook to generate social media cards for Zola sites using the tabi theme.
|
||||||
|
# More details: https://osc.garden/blog/automating-social-media-cards-zola/
|
||||||
|
|
||||||
|
function help_function(){
|
||||||
|
echo "This script automates the creation of social media cards for Zola websites."
|
||||||
|
echo "It takes a Markdown post and saves its live screenshot to a specified location."
|
||||||
|
echo ""
|
||||||
|
echo "IMPORTANT! It needs to be run from the root of the Zola site."
|
||||||
|
echo ""
|
||||||
|
echo "Usage: social-cards-zola [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message and exit."
|
||||||
|
echo " -b, --base_url URL The base URL where the Zola site is hosted. Default is http://127.0.0.1:1111."
|
||||||
|
echo " -i, --input INPUT_PATH The relative path to the markdown file of the post/section you want to capture. Should be in the format 'content/blog/post_name.language.md'."
|
||||||
|
echo " -k, --key KEY The front matter key to update. Default is 'social_media_card'."
|
||||||
|
echo " -o, --output_path PATH The directory where the generated image will be saved."
|
||||||
|
echo " -p, --print_output Print the path to the resulting screenshot at the end."
|
||||||
|
echo " -u, --update-front-matter Update or add the 'social_media_card' key in the front matter of the Markdown file."
|
||||||
|
echo
|
||||||
|
echo "Examples:"
|
||||||
|
echo " social-cards-zola --base_url https://example.com --input content/blog/my_post.md --output_path static/img/social_cards"
|
||||||
|
echo " social-cards-zola -u -b http://127.0.0.1:1025 -i content/archive/_index.es.md -o static/img"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert_filename_to_url() {
|
||||||
|
# Remove .md extension.
|
||||||
|
local post_name="${1%.md}"
|
||||||
|
|
||||||
|
# Remove "content/" prefix.
|
||||||
|
local url="${post_name#content/}"
|
||||||
|
|
||||||
|
# Extract language code.
|
||||||
|
local lang_code="${url##*.}"
|
||||||
|
if [[ "$lang_code" == "$url" ]]; then
|
||||||
|
lang_code="" # No language code.
|
||||||
|
else
|
||||||
|
lang_code="${lang_code}/" # Add trailing slash.
|
||||||
|
url="${url%.*}" # Remove the language code from the URL.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle co-located index.md by stripping it and using the directory as the URL.
|
||||||
|
if [[ "$url" == */index ]]; then
|
||||||
|
url="${url%/*}" # Remove the /index suffix.
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove "_index" suffix.
|
||||||
|
if [[ "$url" == *"_index"* ]]; then
|
||||||
|
url="${url%%_index*}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Return the final URL with a single trailing slash.
|
||||||
|
full_url="${lang_code}${url}"
|
||||||
|
echo "${full_url%/}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_exit() {
|
||||||
|
echo "ERROR: $1" >&2
|
||||||
|
exit "${2:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_input_params() {
|
||||||
|
missing_params=()
|
||||||
|
if [[ -z "$base_url" ]]; then
|
||||||
|
missing_params+=("base_url")
|
||||||
|
fi
|
||||||
|
if [[ -z "$input" ]]; then
|
||||||
|
missing_params+=("input")
|
||||||
|
fi
|
||||||
|
if [[ -z "$output_path" ]]; then
|
||||||
|
missing_params+=("output_path")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing_params[@]} -ne 0 ]; then
|
||||||
|
error_exit "The following required settings are missing: ${missing_params[*]}. Use -h or --help for usage."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_dependencies() {
|
||||||
|
for cmd in "curl" "shot-scraper"; do
|
||||||
|
if ! command -v $cmd &> /dev/null; then
|
||||||
|
error_exit "$cmd could not be found. Please install it."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_status() {
|
||||||
|
local retry_count=0
|
||||||
|
local max_retries=5
|
||||||
|
local status
|
||||||
|
while [[ $retry_count -lt $max_retries ]]; do
|
||||||
|
status=$(curl -s -o /dev/null -I -w "%{http_code}" "${base_url}${post_url}")
|
||||||
|
if [[ "$status" -eq "200" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
retry_count=$((retry_count + 1))
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
error_exit "Post $input is not accessible. Max retries ($max_retries) reached."
|
||||||
|
}
|
||||||
|
|
||||||
|
function capture_screenshot() {
|
||||||
|
temp_file=$(mktemp /tmp/social-zola.XXXXXX)
|
||||||
|
trap 'rm -f "$temp_file"' EXIT
|
||||||
|
shot-scraper --silent "${base_url}/${post_url}" -w 700 -h 400 --retina --quality 60 -o "$temp_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
function move_file() {
|
||||||
|
local safe_filename=$(echo "${post_url%/}" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
|
|
||||||
|
# Create the output directory if it doesn't exist.
|
||||||
|
mkdir -p "$output_path"
|
||||||
|
|
||||||
|
image_filename="${output_path}/${safe_filename:-index}.jpg" # If the filename is empty, use "index".
|
||||||
|
mv "$temp_file" "$image_filename" || error_exit "Failed to move the file to $image_filename"
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_front_matter {
|
||||||
|
local md_file_path="$1"
|
||||||
|
local image_output="${2#static/}"
|
||||||
|
# Temporary file for awk processing
|
||||||
|
temp_awk=$(mktemp /tmp/frontmatter.XXXXXX)
|
||||||
|
|
||||||
|
awk -v card_path="$image_output" '
|
||||||
|
# Initialize flags for tracking state.
|
||||||
|
BEGIN { in_extra=done=front_matter=extra_exists=0; }
|
||||||
|
|
||||||
|
# Function to insert the social_media_card path.
|
||||||
|
function insert_card() { print "social_media_card = \"" card_path "\""; done=1; }
|
||||||
|
|
||||||
|
{
|
||||||
|
# If card has been inserted, simply output remaining lines.
|
||||||
|
if (done) { print; next; }
|
||||||
|
|
||||||
|
# Toggle front_matter flag at its start, denoted by +++
|
||||||
|
if (/^\+\+\+/ && front_matter == 0) {
|
||||||
|
front_matter = 1;
|
||||||
|
print "+++";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect [extra] section and set extra_exists flag.
|
||||||
|
if (/^\[extra\]/) { in_extra=1; extra_exists=1; print; next; }
|
||||||
|
|
||||||
|
# Update existing social_media_card.
|
||||||
|
if (in_extra && /^social_media_card =/) { insert_card(); in_extra=0; next; }
|
||||||
|
|
||||||
|
# End of front matter or start of new section.
|
||||||
|
if (in_extra && (/^\[[a-zA-Z_-]+\]/ || (/^\+\+\+/ && front_matter == 1))) {
|
||||||
|
insert_card(); # Add the missing social_media_card.
|
||||||
|
in_extra=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Insert missing [extra] section.
|
||||||
|
if (/^\+\+\+/ && front_matter == 1 && in_extra == 0 && extra_exists == 0) {
|
||||||
|
print "\n[extra]";
|
||||||
|
insert_card();
|
||||||
|
in_extra=0;
|
||||||
|
front_matter = 0;
|
||||||
|
print "+++";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print all other lines as-is.
|
||||||
|
print;
|
||||||
|
}' "$md_file_path" > "$temp_awk"
|
||||||
|
|
||||||
|
# Move the temporary file back to the original markdown file.
|
||||||
|
mv "$temp_awk" "$md_file_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
help_function;;
|
||||||
|
-b|--base_url)
|
||||||
|
base_url="$2"
|
||||||
|
shift 2;;
|
||||||
|
-i|--input)
|
||||||
|
input="$2"
|
||||||
|
shift 2;;
|
||||||
|
-o|--output_path)
|
||||||
|
output_path="$2"
|
||||||
|
shift 2;;
|
||||||
|
-k|--key)
|
||||||
|
front_matter_key="$2"
|
||||||
|
shift 2;;
|
||||||
|
-u|--update-front-matter)
|
||||||
|
update="true"
|
||||||
|
shift 1;;
|
||||||
|
-p|--print_output)
|
||||||
|
print_output="true"
|
||||||
|
shift 1;;
|
||||||
|
*)
|
||||||
|
error_exit "Unknown option: $1";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
validate_input_params
|
||||||
|
check_dependencies
|
||||||
|
|
||||||
|
: "${base_url:="http://127.0.0.1:1111"}"
|
||||||
|
: "${front_matter_key:="social_media_card"}"
|
||||||
|
base_url="${base_url%/}/" # Ensure one trailing slash.
|
||||||
|
post_url="$(convert_filename_to_url "$input")"
|
||||||
|
|
||||||
|
fetch_status
|
||||||
|
capture_screenshot
|
||||||
|
move_file
|
||||||
|
|
||||||
|
if [[ "$update" == "true" ]]; then
|
||||||
|
update_front_matter "$input" "$image_filename"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$print_output" == "true" ]]; then
|
||||||
|
echo "$image_filename"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
250
public/css/syntax.css
Normal file
250
public/css/syntax.css
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* theme "Dracula" generated by syntect
|
||||||
|
*/
|
||||||
|
|
||||||
|
.z-code {
|
||||||
|
color: #f8f8f2;
|
||||||
|
background-color: #282a36;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-comment {
|
||||||
|
color: #6272a4;
|
||||||
|
}
|
||||||
|
.z-string {
|
||||||
|
color: #f1fa8c;
|
||||||
|
}
|
||||||
|
.z-constant.z-numeric {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-constant.z-language {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-constant.z-character, .z-constant.z-other {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-variable {
|
||||||
|
}
|
||||||
|
.z-variable.z-other.z-readwrite.z-instance {
|
||||||
|
color: #ffb86c;
|
||||||
|
}
|
||||||
|
.z-constant.z-character.z-escaped, .z-constant.z-character.z-escape, .z-string .z-source, .z-string .z-source.z-ruby {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-source.z-ruby .z-string.z-regexp.z-classic.z-ruby, .z-source.z-ruby .z-string.z-regexp.z-mod-r.z-ruby {
|
||||||
|
color: #ff5555;
|
||||||
|
}
|
||||||
|
.z-keyword {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-storage {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-storage.z-type {
|
||||||
|
color: #8be9fd;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-storage.z-type.z-namespace {
|
||||||
|
color: #8be9fd;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-storage.z-type.z-class {
|
||||||
|
color: #ff79c6;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-class {
|
||||||
|
color: #8be9fd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.z-meta.z-path {
|
||||||
|
color: #66d9ef;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.z-entity.z-other.z-inherited-class {
|
||||||
|
color: #8be9fd;
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-function {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-variable.z-parameter {
|
||||||
|
color: #ffb86c;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-tag {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-entity.z-other.z-attribute-name {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-support.z-function {
|
||||||
|
color: #8be9fd;
|
||||||
|
}
|
||||||
|
.z-support.z-constant {
|
||||||
|
color: #6be5fd;
|
||||||
|
}
|
||||||
|
.z-support.z-type, .z-support.z-class {
|
||||||
|
color: #66d9ef;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-support.z-other.z-variable {
|
||||||
|
}
|
||||||
|
.z-support.z-other.z-namespace {
|
||||||
|
color: #66d9ef;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-invalid {
|
||||||
|
color: #f8f8f0;
|
||||||
|
background-color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-invalid.z-deprecated {
|
||||||
|
color: #f8f8f0;
|
||||||
|
background-color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
|
||||||
|
color: #cfcfc2;
|
||||||
|
}
|
||||||
|
.z-meta.z-diff, .z-meta.z-diff.z-header {
|
||||||
|
color: #6272a4;
|
||||||
|
}
|
||||||
|
.z-markup.z-deleted {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-markup.z-inserted {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-markup.z-changed {
|
||||||
|
color: #e6db74;
|
||||||
|
}
|
||||||
|
.z-constant.z-numeric.z-line-number.z-find-in-files {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-filename {
|
||||||
|
color: #e6db74;
|
||||||
|
}
|
||||||
|
.z-message.z-error {
|
||||||
|
color: #f83333;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-definition.z-string.z-begin.z-json, .z-punctuation.z-definition.z-string.z-end.z-json {
|
||||||
|
color: #eeeeee;
|
||||||
|
}
|
||||||
|
.z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json {
|
||||||
|
color: #8be9fd;
|
||||||
|
}
|
||||||
|
.z-meta.z-structure.z-dictionary.z-value.z-json .z-string.z-quoted.z-double.z-json {
|
||||||
|
color: #f1fa8c;
|
||||||
|
}
|
||||||
|
.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-value .z-string {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-value .z-string {
|
||||||
|
color: #ffb86c;
|
||||||
|
}
|
||||||
|
.z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-value .z-string {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-value .z-string {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-meta .z-meta .z-meta.z-structure.z-dictionary.z-value .z-string {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-meta .z-meta.z-structure.z-dictionary.z-value .z-string {
|
||||||
|
color: #ffb86c;
|
||||||
|
}
|
||||||
|
.z-markup.z-strike {
|
||||||
|
color: #ffb86c;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-markup.z-bold {
|
||||||
|
color: #ffb86c;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.z-markup.z-italic {
|
||||||
|
color: #ffb86c;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-markup.z-heading {
|
||||||
|
color: #8be9fd;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-definition.z-list_item.z-markdown {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-markup.z-quote {
|
||||||
|
color: #6272a4;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-definition.z-blockquote.z-markdown {
|
||||||
|
color: #6272a4;
|
||||||
|
background-color: #6272a4;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-meta.z-separator {
|
||||||
|
color: #6272a4;
|
||||||
|
}
|
||||||
|
.z-text.z-html.z-markdown .z-markup.z-raw.z-inline {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-markup.z-underline {
|
||||||
|
color: #bd93f9;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.z-markup.z-raw.z-block {
|
||||||
|
color: #cfcfc2;
|
||||||
|
}
|
||||||
|
.z-markup.z-raw.z-block.z-fenced.z-markdown .z-source {
|
||||||
|
color: #f8f8f2;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-definition.z-fenced.z-markdown, .z-variable.z-language.z-fenced.z-markdown {
|
||||||
|
color: #6272a4;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-variable.z-language.z-fenced.z-markdown {
|
||||||
|
color: #6272a4;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-accessor {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-meta.z-function.z-return-type {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-section.z-block.z-begin {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-section.z-block.z-end {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-section.z-embedded.z-begin {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-section.z-embedded.z-end {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-punctuation.z-separator.z-namespace {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-variable.z-function {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
.z-variable.z-other {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.z-variable.z-language {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-module.z-ruby {
|
||||||
|
color: #8be9fd;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-constant.z-ruby {
|
||||||
|
color: #bd93f9;
|
||||||
|
}
|
||||||
|
.z-support.z-function.z-builtin.z-ruby {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.z-storage.z-type.z-namespace.z-cs {
|
||||||
|
color: #ff79c6;
|
||||||
|
}
|
||||||
|
.z-entity.z-name.z-namespace.z-cs {
|
||||||
|
color: #8be9fd;
|
||||||
|
}
|
1
public/custom_subset.css
Normal file
1
public/custom_subset.css
Normal file
File diff suppressed because one or more lines are too long
10
public/elasticlunr.min.js
vendored
Normal file
10
public/elasticlunr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
119
public/feed_style.xsl
Normal file
119
public/feed_style.xsl
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom" xmlns:tabi="https://github.com/welpo/tabi">
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
<xsl:attribute name="data-theme">
|
||||||
|
<xsl:value-of select="/atom:feed/tabi:metadata/tabi:default_theme"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<xsl:value-of select="/atom:feed/atom:title"/> • Feed
|
||||||
|
</title>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<xsl:variable name="baseUrl" select="/atom:feed/tabi:metadata/tabi:base_url"/>
|
||||||
|
<link rel="stylesheet" href="{$baseUrl}/main.css"/>
|
||||||
|
<link rel="stylesheet" href="{/atom:feed/atom:link[@rel='extra-stylesheet']/@href}" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body dir="auto">
|
||||||
|
<div class="content">
|
||||||
|
<main>
|
||||||
|
<div class="info-box">
|
||||||
|
<!-- This block replaces the text "About Feeds" with a hyperlink in the translated string -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="contains(/atom:feed/tabi:metadata/tabi:about_feeds, 'About Feeds')">
|
||||||
|
<xsl:value-of select="substring-before(/atom:feed/tabi:metadata/tabi:about_feeds, 'About Feeds')"/>
|
||||||
|
<a href="https://aboutfeeds.com/" target="_blank">About Feeds</a>
|
||||||
|
<xsl:value-of select="substring-after(/atom:feed/tabi:metadata/tabi:about_feeds, 'About Feeds')"/>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select="/atom:feed/tabi:metadata/tabi:about_feeds"/>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</div>
|
||||||
|
<section id="banner-home-subtitle">
|
||||||
|
<div class="padding-top home-title">
|
||||||
|
<xsl:value-of select="/atom:feed/atom:title"/>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<xsl:value-of select="/atom:feed/atom:subtitle"/>
|
||||||
|
</p>
|
||||||
|
<a class="readmore">
|
||||||
|
<xsl:attribute name="href">
|
||||||
|
<xsl:value-of select="/atom:feed/atom:link[@rel='alternate']/@href"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select="/atom:feed/tabi:metadata/tabi:visit_the_site" />
|
||||||
|
<xsl:if test="/atom:feed/tabi:metadata/tabi:current_section != /atom:feed/atom:title">
|
||||||
|
<xsl:text>: </xsl:text>
|
||||||
|
<xsl:value-of select="/atom:feed/tabi:metadata/tabi:current_section" />
|
||||||
|
</xsl:if>
|
||||||
|
<span class="arrow"> →</span>
|
||||||
|
</a>
|
||||||
|
<p></p>
|
||||||
|
</section>
|
||||||
|
<div class="padding-top listing-title bottom-divider">
|
||||||
|
<h1><xsl:value-of select="/atom:feed/tabi:metadata/tabi:recent_posts" /></h1>
|
||||||
|
</div>
|
||||||
|
<xsl:variable name="post_listing_date" select="/atom:feed/tabi:metadata/tabi:post_listing_date"/>
|
||||||
|
<div class="bloglist-container">
|
||||||
|
<xsl:for-each select="/atom:feed/atom:entry">
|
||||||
|
<section class="bloglist-meta bottom-divider">
|
||||||
|
<ul>
|
||||||
|
<xsl:variable name="show_date" select="$post_listing_date = 'date' or $post_listing_date = 'both'"/>
|
||||||
|
<xsl:variable name="show_updated" select="$post_listing_date = 'updated' or $post_listing_date = 'both'"/>
|
||||||
|
|
||||||
|
<xsl:if test="$show_date">
|
||||||
|
<li class="date">
|
||||||
|
<xsl:value-of select="substring(atom:published, 0, 11)"/>
|
||||||
|
</li>
|
||||||
|
</xsl:if>
|
||||||
|
|
||||||
|
<xsl:if test="$show_date and $show_updated">
|
||||||
|
<li class="mobile-only">
|
||||||
|
<xsl:value-of select="/atom:feed/tabi:metadata/tabi:separator"/>
|
||||||
|
</li>
|
||||||
|
</xsl:if>
|
||||||
|
|
||||||
|
<xsl:if test="$show_updated">
|
||||||
|
<li class="date">
|
||||||
|
<xsl:variable name="update_string" select="/atom:feed/tabi:metadata/tabi:last_updated_on"/>
|
||||||
|
<xsl:variable name="update_date" select="substring(atom:updated, 0, 11)"/>
|
||||||
|
<xsl:value-of select="substring-before($update_string, '$DATE')"/>
|
||||||
|
<xsl:value-of select="$update_date"/>
|
||||||
|
<xsl:value-of select="substring-after($update_string, '$DATE')"/>
|
||||||
|
</li>
|
||||||
|
</xsl:if>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section class="bloglist-content bottom-divider">
|
||||||
|
<div>
|
||||||
|
<div class="bloglist-title">
|
||||||
|
<a>
|
||||||
|
<xsl:attribute name="href">
|
||||||
|
<xsl:value-of select="atom:link/@href"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select="atom:title"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<xsl:value-of select="atom:summary"/>
|
||||||
|
</div>
|
||||||
|
<a class="readmore" href="">
|
||||||
|
<xsl:attribute name="href">
|
||||||
|
<xsl:value-of select="atom:link/@href"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</xsl:for-each>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
BIN
public/fonts/CascadiaCode-SemiLight.woff2
Normal file
BIN
public/fonts/CascadiaCode-SemiLight.woff2
Normal file
Binary file not shown.
BIN
public/fonts/Inter4.woff2
Normal file
BIN
public/fonts/Inter4.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_AMS-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_AMS-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_AMS-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_AMS-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_AMS-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_AMS-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Bold.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Bold.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Bold.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Bold.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Bold.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Caligraphic-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Bold.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Bold.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Bold.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Bold.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Bold.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Fraktur-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Bold.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Bold.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Bold.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Bold.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Bold.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-BoldItalic.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-BoldItalic.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-BoldItalic.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-BoldItalic.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Italic.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Italic.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Italic.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Italic.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Main-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Main-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Math-BoldItalic.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Math-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Math-BoldItalic.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Math-BoldItalic.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Math-BoldItalic.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Math-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Math-Italic.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Math-Italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Math-Italic.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Math-Italic.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Math-Italic.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Math-Italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Bold.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Bold.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Bold.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Bold.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Bold.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Italic.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Italic.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Italic.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Italic.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_SansSerif-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Script-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Script-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Script-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Script-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Script-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Script-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size1-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size1-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size1-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size1-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size1-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size1-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size2-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size2-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size2-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size2-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size2-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size2-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size3-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size3-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size3-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size3-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size3-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size3-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size4-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size4-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size4-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size4-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Size4-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Size4-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Typewriter-Regular.ttf
Normal file
BIN
public/fonts/KaTeX/KaTeX_Typewriter-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Typewriter-Regular.woff
Normal file
BIN
public/fonts/KaTeX/KaTeX_Typewriter-Regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/KaTeX/KaTeX_Typewriter-Regular.woff2
Normal file
BIN
public/fonts/KaTeX/KaTeX_Typewriter-Regular.woff2
Normal file
Binary file not shown.
BIN
public/fonts/SourceSerif4Variable-Roman.ttf.woff2
Normal file
BIN
public/fonts/SourceSerif4Variable-Roman.ttf.woff2
Normal file
Binary file not shown.
BIN
public/img/main.webp
Normal file
BIN
public/img/main.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
public/img/seedling.png
Normal file
BIN
public/img/seedling.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
1
public/inter_subset_en.css
Normal file
1
public/inter_subset_en.css
Normal file
File diff suppressed because one or more lines are too long
1
public/inter_subset_es.css
Normal file
1
public/inter_subset_es.css
Normal file
File diff suppressed because one or more lines are too long
375
public/isso.css
Normal file
375
public/isso.css
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
/* ========================================================================== */
|
||||||
|
/* Generic styling */
|
||||||
|
/* ========================================================================== */
|
||||||
|
#isso-thread * {
|
||||||
|
/* Reset */
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================== */
|
||||||
|
/* Thread heading area */
|
||||||
|
/* ========================================================================== */
|
||||||
|
#isso-thread {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: var(--sans-serif-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
h4.isso-thread-heading {
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-feedlink {
|
||||||
|
float: inline-end;
|
||||||
|
padding-inline-start: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-feedlink a {
|
||||||
|
vertical-align: bottom;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================== */
|
||||||
|
/* Comments */
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
.isso-comment {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 68em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-preview .isso-comment {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment:not(:first-of-type),
|
||||||
|
.isso-follow-up .isso-comment {
|
||||||
|
margin-block-end: 0.5em;
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-avatar {
|
||||||
|
display: block;
|
||||||
|
float: inline-start;
|
||||||
|
margin: 0.95em 0.95em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-avatar svg {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 48px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-text-wrapper {
|
||||||
|
display: block;
|
||||||
|
padding: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-follow-up {
|
||||||
|
padding-inline-start: calc(7% + 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment-footer {
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment-header {
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment-header a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only for comment header, spacer between up-/downvote should have no padding */
|
||||||
|
.isso-comment-header .isso-spacer {
|
||||||
|
padding-inline: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-spacer,
|
||||||
|
.isso-permalink,
|
||||||
|
.isso-note,
|
||||||
|
.isso-parent {
|
||||||
|
color: var(--meta-color);
|
||||||
|
font-weight: normal;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-permalink:hover,
|
||||||
|
.isso-note:hover,
|
||||||
|
.isso-parent:hover {
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-note {
|
||||||
|
float: inline-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-author {
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-page-author-suffix {
|
||||||
|
color: var(--text-color-high-contrast);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style author comments and replies */
|
||||||
|
.isso-is-page-author>.isso-text-wrapper {
|
||||||
|
background-color: var(--bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-textarea,
|
||||||
|
.isso-preview {
|
||||||
|
background-color: var(--bg-2);
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-family: var(--sans-serif-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-text p {
|
||||||
|
margin-top: -0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-text p:last-child {
|
||||||
|
margin-block-end: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-text h1,
|
||||||
|
.isso-text h2,
|
||||||
|
.isso-text h3,
|
||||||
|
.isso-text h4,
|
||||||
|
.isso-text h5,
|
||||||
|
.isso-text h6 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 130%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment-footer {
|
||||||
|
clear: left;
|
||||||
|
color: var(--meta-color);
|
||||||
|
font-size: 0.80em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-feedlink,
|
||||||
|
.isso-comment-footer a {
|
||||||
|
margin: 0.4em;
|
||||||
|
padding: 0.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment-footer .isso-votes {
|
||||||
|
color: var(--meta-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-upvote svg,
|
||||||
|
.isso-downvote svg {
|
||||||
|
position: relative;
|
||||||
|
top: .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-upvote:hover svg,
|
||||||
|
.isso-downvote:hover svg {
|
||||||
|
fill: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reply postbox under existing comment */
|
||||||
|
.isso-comment .isso-postbox {
|
||||||
|
margin-top: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-comment.isso-no-votes>*>.isso-comment-footer .isso-votes {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================== */
|
||||||
|
/* Postbox */
|
||||||
|
/* ========================================================================== */
|
||||||
|
.isso-postbox {
|
||||||
|
clear: right;
|
||||||
|
margin: 0 auto 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-form-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-textarea,
|
||||||
|
.isso-preview {
|
||||||
|
margin-top: 0.2em;
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-textarea {
|
||||||
|
outline: 0;
|
||||||
|
width: 100%;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-form-wrapper input[type=checkbox] {
|
||||||
|
position: relative;
|
||||||
|
bottom: 1px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-notification-section {
|
||||||
|
display: none;
|
||||||
|
padding-top: .3em;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
font-size: 0.90em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-auth-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper,
|
||||||
|
.isso-post-action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 35%;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-family: var(--sans-serif-font);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper {
|
||||||
|
margin-inline-end: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper input,
|
||||||
|
.isso-post-action input {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: auto;
|
||||||
|
height: auto;
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper input {
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--bg-2);
|
||||||
|
padding: 0.3em;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
line-height: 1.2em;
|
||||||
|
font-family: var(--sans-serif-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-post-action input {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0.1em;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
padding-inline: 1em;
|
||||||
|
padding-block: 0.6em;
|
||||||
|
color: var(--background-color);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-post-action {
|
||||||
|
display: block;
|
||||||
|
align-self: flex-end;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-post-action>input:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================== */
|
||||||
|
/* Postbox (preview mode) */
|
||||||
|
/* ========================================================================== */
|
||||||
|
.isso-preview,
|
||||||
|
.isso-post-action input[name="edit"],
|
||||||
|
.isso-postbox.isso-preview-mode>.isso-form-wrapper input[name="preview"],
|
||||||
|
.isso-postbox.isso-preview-mode>.isso-form-wrapper .isso-textarea {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-postbox.isso-preview-mode>.isso-form-wrapper .isso-preview {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-postbox.isso-preview-mode>.isso-form-wrapper input[name="edit"] {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-preview {
|
||||||
|
background: repeating-linear-gradient(-45deg,
|
||||||
|
var(--bg-0),
|
||||||
|
var(--bg-0) 10px,
|
||||||
|
var(--bg-2) 10px,
|
||||||
|
var(--bg-2) 20px);
|
||||||
|
background-color: var(--bg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================== */
|
||||||
|
/* Animations */
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
/* "target" means the comment that's being linked to, for example:
|
||||||
|
* https://example.com/blog/example/#isso-15
|
||||||
|
*/
|
||||||
|
.isso-target {
|
||||||
|
animation: isso-target-fade 5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes isso-target-fade {
|
||||||
|
0% {
|
||||||
|
background-color: var(--divider-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================================================================== */
|
||||||
|
/* Media queries */
|
||||||
|
/* ========================================================================== */
|
||||||
|
@media screen and (max-width:600px) {
|
||||||
|
.isso-auth-section {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper {
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 .4em;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-input-wrapper input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isso-post-action {
|
||||||
|
margin: 0.4em auto;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
1
public/isso.min.css
vendored
Normal file
1
public/isso.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.isso-avatar svg,.isso-preview,.isso-textarea{border:1px solid var(--divider-color);width:100%}#isso-thread *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}#isso-thread{margin:0 auto;padding:0;width:100%;color:var(--text-color);font-size:.9em;font-family:var(--sans-serif-font)}h4.isso-thread-heading{padding-bottom:.2em;color:var(--text-color);font-size:1.2rem}.isso-feedlink,.isso-note{float:right}.isso-feedlink a{vertical-align:bottom;font-size:.8em}.isso-comment{margin:0 auto;max-width:68em}.isso-preview .isso-comment{margin:0;padding-top:0}.isso-comment:not(:first-of-type),.isso-follow-up .isso-comment{margin-bottom:.5em;border-top:1px solid var(--divider-color)}.isso-avatar{display:block;float:left;margin:.95em .95em 0}.isso-avatar svg{border-radius:3px;max-width:48px;height:100%;max-height:48px}.isso-text-wrapper{display:block;padding:.3em}.isso-follow-up{padding-inline-start:calc(7% + 20px)}.isso-comment-header{font-size:.85em}.isso-comment-header a{text-decoration:none}.isso-comment-header .isso-spacer{padding:0 6px}.isso-note,.isso-parent,.isso-permalink,.isso-spacer{color:var(--meta-color);font-weight:400;text-shadow:none}.isso-note:hover,.isso-parent:hover,.isso-permalink:hover{color:var(--hover-color)}.isso-author{color:var(--text-color);font-weight:500}.isso-page-author-suffix{color:var(--text-color-high-contrast);font-weight:700}.isso-input-wrapper input,.isso-preview,.isso-textarea{background-color:var(--bg-2);color:var(--text-color);font-family:var(--sans-serif-font)}.isso-is-page-author>.isso-text-wrapper{background-color:var(--bg-1)}.isso-preview,.isso-textarea{padding:10px;font-size:.8em}.isso-comment-footer,.isso-comment-footer .isso-votes{color:var(--meta-color)}.isso-text p{margin-top:-.4em}.isso-text p:last-child{margin-bottom:.2em}.isso-text h1,.isso-text h2,.isso-text h3,.isso-text h4,.isso-text h5,.isso-text h6{font-weight:700;font-size:130%}.isso-comment-footer{clear:left;font-size:.8em}.isso-comment-footer a,.isso-feedlink{margin:.4em;padding:.1em;font-weight:700;text-decoration:none}.isso-downvote svg,.isso-upvote svg{position:relative;top:.2em}.isso-downvote:hover svg,.isso-upvote:hover svg{fill:var(--hover-color)}.isso-comment .isso-postbox{margin-top:.8em}.isso-comment.isso-no-votes>*>.isso-comment-footer .isso-votes,.isso-post-action input[name=edit],.isso-postbox.isso-preview-mode>.isso-form-wrapper .isso-textarea,.isso-postbox.isso-preview-mode>.isso-form-wrapper input[name=preview],.isso-preview{display:none}.isso-postbox{clear:right;margin:0 auto 2em}.isso-form-wrapper{display:flex;flex-direction:column}.isso-preview,.isso-textarea{margin-top:.2em;border-radius:5px}.isso-textarea{outline:0;width:100%;resize:none}.isso-form-wrapper input[type=checkbox]{position:relative;bottom:1px;vertical-align:middle;margin-inline-end:0}.isso-notification-section{display:none;padding-top:.3em;padding-bottom:10px;font-size:.9em}.isso-auth-section{display:flex;flex-direction:row}.isso-input-wrapper,.isso-post-action{display:flex;flex-direction:column;justify-content:flex-end;align-items:center;margin:0 auto;max-width:35%;font-size:.8em;font-family:var(--sans-serif-font);text-align:center}.isso-input-wrapper{margin-inline-end:.5em}.isso-input-wrapper input,.isso-post-action input{margin-top:auto}.isso-input-wrapper label{display:inline-block;margin-top:auto;height:auto;line-height:1.4em}.isso-input-wrapper input{border:1px solid var(--divider-color);border-radius:5px;padding:.3em;width:100%;line-height:1.2em}.isso-post-action input{cursor:pointer;margin:.1em;border:none;border-radius:5px;background-color:var(--primary-color);padding:.6em 1em;color:var(--background-color);font-size:.8rem}.isso-post-action{display:block;align-self:flex-end;margin:0 auto}.isso-post-action>input:hover{opacity:.8}.isso-postbox.isso-preview-mode>.isso-form-wrapper .isso-preview{display:block}.isso-postbox.isso-preview-mode>.isso-form-wrapper input[name=edit]{display:inline}.isso-preview{background:repeating-linear-gradient(-45deg,var(--bg-0),var(--bg-0) 10px,var(--bg-2) 10px,var(--bg-2) 20px);background-color:var(--bg-0)}.isso-target{animation:5s ease-out isso-target-fade}@keyframes isso-target-fade{0%{background-color:var(--divider-color)}}@media screen and (max-width:600px){.isso-auth-section{flex-direction:column;text-align:center}.isso-input-wrapper{display:block;margin:0 0 .4em;max-width:100%}.isso-input-wrapper input{width:100%}.isso-post-action{margin:.4em auto;width:60%}}
|
36
public/js/codeBlockNameLinks.js
Normal file
36
public/js/codeBlockNameLinks.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
// Convert URLs in data-name to links.
|
||||||
|
document.querySelectorAll('code[data-name]').forEach(function(code) {
|
||||||
|
const name = code.getAttribute('data-name');
|
||||||
|
if (name.startsWith('http')) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = name;
|
||||||
|
link.className = 'source-path';
|
||||||
|
link.textContent = name;
|
||||||
|
code.insertBefore(link, code.firstChild);
|
||||||
|
// Remove data-name to avoid overlap with Zola's native display.
|
||||||
|
code.removeAttribute('data-name');
|
||||||
|
code.parentElement?.removeAttribute('data-name');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Legacy support for old shortcode. https://github.com/welpo/tabi/pull/489
|
||||||
|
document.querySelectorAll('.code-source').forEach(function(marker) {
|
||||||
|
const sourceUrl = marker.getAttribute('data-source');
|
||||||
|
const nextPre = marker.nextElementSibling;
|
||||||
|
if (nextPre?.tagName === 'PRE') {
|
||||||
|
const code = nextPre.querySelector('code');
|
||||||
|
if (code) {
|
||||||
|
if (sourceUrl.startsWith('http')) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = sourceUrl;
|
||||||
|
link.className = 'source-path';
|
||||||
|
link.textContent = sourceUrl;
|
||||||
|
code.insertBefore(link, code.firstChild);
|
||||||
|
} else {
|
||||||
|
code.setAttribute('data-name', sourceUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
1
public/js/codeBlockNameLinks.min.js
vendored
Normal file
1
public/js/codeBlockNameLinks.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("code[data-name]").forEach(function(e){var t,a=e.getAttribute("data-name");a.startsWith("http")&&((t=document.createElement("a")).href=a,t.className="source-path",t.textContent=a,e.insertBefore(t,e.firstChild),e.removeAttribute("data-name"),e.parentElement?.removeAttribute("data-name"))}),document.querySelectorAll(".code-source").forEach(function(e){var t,a=e.getAttribute("data-source");"PRE"===(e=e.nextElementSibling)?.tagName&&(e=e.querySelector("code"))&&(a.startsWith("http")?((t=document.createElement("a")).href=a,t.className="source-path",t.textContent=a,e.insertBefore(t,e.firstChild)):e.setAttribute("data-name",a))})});
|
47
public/js/copyCodeToClipboard.js
Normal file
47
public/js/copyCodeToClipboard.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const copiedText = document.getElementById('copy-success').textContent;
|
||||||
|
const initCopyText = document.getElementById('copy-init').textContent;
|
||||||
|
|
||||||
|
const changeIcon = (copyDiv, className) => {
|
||||||
|
copyDiv.classList.add(className);
|
||||||
|
copyDiv.setAttribute('aria-label', copiedText);
|
||||||
|
setTimeout(() => {
|
||||||
|
copyDiv.classList.remove(className);
|
||||||
|
copyDiv.setAttribute('aria-label', initCopyText);
|
||||||
|
}, 2500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCopyEventListenerToDiv = (copyDiv, block) => {
|
||||||
|
copyDiv.addEventListener('click', () => copyCodeAndChangeIcon(copyDiv, block));
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyCodeAndChangeIcon = async (copyDiv, block) => {
|
||||||
|
const code = block.querySelector('table')
|
||||||
|
? getTableCode(block)
|
||||||
|
: getNonTableCode(block);
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
changeIcon(copyDiv, 'checked');
|
||||||
|
} catch (error) {
|
||||||
|
changeIcon(copyDiv, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNonTableCode = (block) => {
|
||||||
|
return [...block.querySelectorAll('code')].map((code) => code.textContent).join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableCode = (block) => {
|
||||||
|
return [...block.querySelectorAll('tr')]
|
||||||
|
.map((row) => row.querySelector('td:last-child')?.innerText ?? '')
|
||||||
|
.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('pre:not(.mermaid)').forEach((block) => {
|
||||||
|
const copyDiv = document.createElement('div');
|
||||||
|
copyDiv.setAttribute('role', 'button');
|
||||||
|
copyDiv.setAttribute('aria-label', initCopyText);
|
||||||
|
copyDiv.setAttribute('title', initCopyText);
|
||||||
|
copyDiv.className = 'copy-code';
|
||||||
|
block.prepend(copyDiv);
|
||||||
|
addCopyEventListenerToDiv(copyDiv, block);
|
||||||
|
});
|
1
public/js/copyCodeToClipboard.min.js
vendored
Normal file
1
public/js/copyCodeToClipboard.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
const copiedText=document.getElementById("copy-success").textContent,initCopyText=document.getElementById("copy-init").textContent,changeIcon=(e,t)=>{e.classList.add(t),e.setAttribute("aria-label",copiedText),setTimeout(()=>{e.classList.remove(t),e.setAttribute("aria-label",initCopyText)},2500)},addCopyEventListenerToDiv=(e,t)=>{e.addEventListener("click",()=>copyCodeAndChangeIcon(e,t))},copyCodeAndChangeIcon=async(t,e)=>{e=(e.querySelector("table")?getTableCode:getNonTableCode)(e);try{await navigator.clipboard.writeText(e),changeIcon(t,"checked")}catch(e){changeIcon(t,"error")}},getNonTableCode=e=>[...e.querySelectorAll("code")].map(e=>e.textContent).join(""),getTableCode=e=>[...e.querySelectorAll("tr")].map(e=>e.querySelector("td:last-child")?.innerText??"").join("");document.querySelectorAll("pre:not(.mermaid)").forEach(e=>{var t=document.createElement("div");t.setAttribute("role","button"),t.setAttribute("aria-label",initCopyText),t.setAttribute("title",initCopyText),t.className="copy-code",e.prepend(t),addCopyEventListenerToDiv(t,e)});
|
44
public/js/decodeMail.js
Normal file
44
public/js/decodeMail.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Utility function: Base64 Decoding.
|
||||||
|
function decodeBase64(encodedString) {
|
||||||
|
try {
|
||||||
|
// Can't use atob() directly because it doesn't support non-ascii characters.
|
||||||
|
// And non-ascii characters are allowed in email addresses and domains.
|
||||||
|
// See https://en.wikipedia.org/wiki/Email_address#Internationalization
|
||||||
|
// Code below adapted from Jackie Han: https://stackoverflow.com/a/64752311
|
||||||
|
const byteString = atob(encodedString);
|
||||||
|
|
||||||
|
// Convert byteString to an array of char codes.
|
||||||
|
const charCodes = [...byteString].map((char) => char.charCodeAt(0));
|
||||||
|
|
||||||
|
// Use TypedArray.prototype.set() to copy the char codes into a Uint8Array.
|
||||||
|
const bytes = new Uint8Array(charCodes.length);
|
||||||
|
bytes.set(charCodes);
|
||||||
|
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
return decoder.decode(bytes);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to decode Base64 string: ', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function: Update href of an element with a decoded email.
|
||||||
|
function updateEmailHref(element) {
|
||||||
|
const encodedEmail = element.getAttribute('data-encoded-email');
|
||||||
|
const decodedEmail = decodeBase64(encodedEmail);
|
||||||
|
|
||||||
|
if (decodedEmail) {
|
||||||
|
element.setAttribute('href', `mailto:${decodedEmail}`);
|
||||||
|
} else {
|
||||||
|
// If the decoding fails, hide the email link.
|
||||||
|
element.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch and process email elements with the "data-encoded-email" attribute.
|
||||||
|
const encodedEmailElements = document.querySelectorAll('[data-encoded-email]');
|
||||||
|
encodedEmailElements.forEach(updateEmailHref);
|
||||||
|
})();
|
1
public/js/decodeMail.min.js
vendored
Normal file
1
public/js/decodeMail.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!function(){"use strict";document.querySelectorAll("[data-encoded-email]").forEach(function(e){var t=function(e){try{var t=[...atob(e)].map(e=>e.charCodeAt(0)),r=new Uint8Array(t.length);return r.set(t),new TextDecoder("utf-8").decode(r)}catch(e){return console.error("Failed to decode Base64 string: ",e),null}}(e.getAttribute("data-encoded-email"));t?e.setAttribute("href","mailto:"+t):e.style.display="none"})}();
|
99
public/js/filterCards.js
Normal file
99
public/js/filterCards.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const cards = document.querySelectorAll('.card');
|
||||||
|
const filterLinks = document.querySelectorAll('.filter-controls a');
|
||||||
|
const allProjectsFilter = document.querySelector('#all-projects-filter');
|
||||||
|
if (!cards.length || !filterLinks.length) return;
|
||||||
|
allProjectsFilter.style.display = 'block';
|
||||||
|
|
||||||
|
// Create a Map for O(1) lookups of links by filter value.
|
||||||
|
const linkMap = new Map(
|
||||||
|
Array.from(filterLinks).map(link => [link.dataset.filter, link])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pre-process cards data for faster filtering.
|
||||||
|
const cardData = Array.from(cards).map(card => ({
|
||||||
|
element: card,
|
||||||
|
tags: card.dataset.tags?.toLowerCase().split(',').filter(Boolean) ?? []
|
||||||
|
}));
|
||||||
|
|
||||||
|
function getTagSlugFromUrl(url) {
|
||||||
|
return url.split('/').filter(Boolean).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilterFromHash() {
|
||||||
|
if (!window.location.hash) return 'all';
|
||||||
|
const hash = decodeURIComponent(window.location.hash.slice(1));
|
||||||
|
const matchingLink = Array.from(filterLinks).find(link =>
|
||||||
|
getTagSlugFromUrl(link.getAttribute('href')) === hash
|
||||||
|
);
|
||||||
|
return matchingLink?.dataset.filter ?? 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveFilter(filterValue, updateHash = true) {
|
||||||
|
if (updateHash) {
|
||||||
|
if (filterValue === 'all') {
|
||||||
|
history.pushState(null, '', window.location.pathname);
|
||||||
|
} else {
|
||||||
|
const activeLink = linkMap.get(filterValue);
|
||||||
|
if (activeLink) {
|
||||||
|
const tagSlug = getTagSlugFromUrl(activeLink.getAttribute('href'));
|
||||||
|
history.pushState(null, '', `#${tagSlug}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isAll = filterValue === 'all';
|
||||||
|
const display = isAll ? '' : 'none';
|
||||||
|
const ariaHidden = isAll ? 'false' : 'true';
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
filterLinks.forEach(link => {
|
||||||
|
const isActive = link.dataset.filter === filterValue;
|
||||||
|
link.classList.toggle('active', isActive);
|
||||||
|
link.setAttribute('aria-pressed', isActive);
|
||||||
|
});
|
||||||
|
if (isAll) {
|
||||||
|
cardData.forEach(({ element }) => {
|
||||||
|
element.style.display = display;
|
||||||
|
element.setAttribute('aria-hidden', ariaHidden);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cardData.forEach(({ element, tags }) => {
|
||||||
|
const shouldShow = tags.includes(filterValue);
|
||||||
|
element.style.display = shouldShow ? '' : 'none';
|
||||||
|
element.setAttribute('aria-hidden', !shouldShow);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterContainer = filterLinks[0].parentElement.parentElement;
|
||||||
|
filterContainer.addEventListener('click', e => {
|
||||||
|
const link = e.target.closest('a');
|
||||||
|
if (!link) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const filterValue = link.dataset.filter;
|
||||||
|
if (filterValue) setActiveFilter(filterValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
filterContainer.addEventListener('keydown', e => {
|
||||||
|
const link = e.target.closest('a');
|
||||||
|
if (!link) return;
|
||||||
|
if (e.key === ' ' || e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
filterLinks.forEach(link => {
|
||||||
|
link.setAttribute('role', 'button');
|
||||||
|
link.setAttribute('aria-pressed', link.classList.contains('active'));
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
setActiveFilter(getFilterFromHash(), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialFilter = getFilterFromHash();
|
||||||
|
if (initialFilter !== 'all') {
|
||||||
|
setActiveFilter(initialFilter, false);
|
||||||
|
}
|
||||||
|
});
|
1
public/js/filterCards.min.js
vendored
Normal file
1
public/js/filterCards.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded",()=>{var t=document.querySelectorAll(".card");const l=document.querySelectorAll(".filter-controls a");var e=document.querySelector("#all-projects-filter");if(t.length&&l.length){e.style.display="block";const s=new Map(Array.from(l).map(t=>[t.dataset.filter,t])),i=Array.from(t).map(t=>({element:t,tags:t.dataset.tags?.toLowerCase().split(",").filter(Boolean)??[]}));function o(t){return t.split("/").filter(Boolean).pop()}function a(){if(!window.location.hash)return"all";const e=decodeURIComponent(window.location.hash.slice(1));return Array.from(l).find(t=>o(t.getAttribute("href"))===e)?.dataset.filter??"all"}function r(a,t=!0){t&&("all"===a?history.pushState(null,"",window.location.pathname):(t=s.get(a))&&(t=o(t.getAttribute("href")),history.pushState(null,"","#"+t)));const e="all"===a,r=e?"":"none",n=e?"false":"true";requestAnimationFrame(()=>{l.forEach(t=>{var e=t.dataset.filter===a;t.classList.toggle("active",e),t.setAttribute("aria-pressed",e)}),e?i.forEach(({element:t})=>{t.style.display=r,t.setAttribute("aria-hidden",n)}):i.forEach(({element:t,tags:e})=>{e=e.includes(a),t.style.display=e?"":"none",t.setAttribute("aria-hidden",!e)})})}(e=l[0].parentElement.parentElement).addEventListener("click",t=>{var e=t.target.closest("a");e&&(t.preventDefault(),t=e.dataset.filter)&&r(t)}),e.addEventListener("keydown",t=>{var e=t.target.closest("a");!e||" "!==t.key&&"Enter"!==t.key||(t.preventDefault(),e.click())}),l.forEach(t=>{t.setAttribute("role","button"),t.setAttribute("aria-pressed",t.classList.contains("active"))}),window.addEventListener("popstate",()=>{r(a(),!1)}),"all"!==(t=a())&&r(t,!1)}});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user