mirror of
https://github.com/LukeHagar/omarchy.git
synced 2025-12-06 04:20:23 +00:00
Dynamic Color Match Obsidian theme to current Omarchy theme (#791)
* feat: obsidian theme to tie into Omarchy theme * better color matching for search result matches * feat: simplify install flow * Removed install/remove scripts and flow * First `omarchy-theme-set-obsidian` run will look for vaults in Documents and Dropbox folders and add them to `/.local/state/omarchy/obsidian-vaults` * Point of this is such that we assume a couple locations to look for vaults but allow people to add vaults to the file for custom locations. Subsequent theme-set invocations aren't impacted by find/search on large systems * Each `omarchy-theme-set` invocation will install the themes on all found/registered vaults if missing and update the live theme * Added an option of `omarchy-theme-set-obsidian --reset` to wipe out themes in registered vaults and remove the registry file. This would be the option to re-run to the automatic vault registration. * Added migration to trigger install immediately in Obisdian, note that you still need to pick Omarchy theme.
This commit is contained in:
@@ -38,3 +38,4 @@ omarchy-theme-set-gnome
|
|||||||
omarchy-theme-set-browser
|
omarchy-theme-set-browser
|
||||||
omarchy-theme-set-vscode
|
omarchy-theme-set-vscode
|
||||||
omarchy-theme-set-cursor
|
omarchy-theme-set-cursor
|
||||||
|
omarchy-theme-set-obsidian
|
||||||
|
|||||||
659
bin/omarchy-theme-set-obsidian
Executable file
659
bin/omarchy-theme-set-obsidian
Executable file
@@ -0,0 +1,659 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# omarchy-theme-set-obsidian: Bootstrap and update Omarchy theme for Obsidian
|
||||||
|
#
|
||||||
|
# - Ensures registry at ~/.local/state/omarchy/obsidian-vaults
|
||||||
|
# - If missing/empty, bootstraps by scanning ~/Documents/*/.obsidian and ~/Dropbox/*/.obsidian
|
||||||
|
# - For each valid vault:
|
||||||
|
# - Ensures .obsidian/themes/Omarchy/{manifest.json, theme.css}
|
||||||
|
# - Updates theme.css (uses current theme’s obsidian.css if present; otherwise generates -- see below)
|
||||||
|
|
||||||
|
# Theme automagic generation logic:
|
||||||
|
#
|
||||||
|
# - Background/foreground: read from ~/.config/omarchy/current/theme/alacritty.toml [colors.primary]
|
||||||
|
# (background/foreground). Fallbacks: bg=#1a1b26, fg=#a9b1d6. Compute bg brightness for light/dark handling.
|
||||||
|
# - Palette extraction: collect colors from Alacritty (primary/normal/bright/dim/selection), Waybar (@define-color),
|
||||||
|
# and Hyprland (col.*_border; rgba->hex). Normalize, dedupe, and count frequencies.
|
||||||
|
# - Slot ordering: remove bg/fg, sort remaining colors by frequency, then fill 13 slots by cycling. Map slots to:
|
||||||
|
# h1–h6, links, inline code, marks, interactive accent, blockquote border; muted/faint use border color.
|
||||||
|
# - Code colors: code background = closest color to bg (Euclidean RGB); if none, make a subtle bg variant (+/− RGB).
|
||||||
|
# code foreground = closest color to fg; fallback #e0e0e0.
|
||||||
|
# - Border color: from btop.theme theme[div_line]; else blended mix biased toward bg (≈ (bg+fg)/3).
|
||||||
|
# - Selection: from Alacritty [colors.selection] (background/text), honoring CellForeground/Background.
|
||||||
|
# If missing, background = 75% bg + 25% fg; text chosen for contrast vs selection background.
|
||||||
|
# - Fonts: monospace from Alacritty [font] or fontconfig monospace; UI font from fontconfig sans-serif.
|
||||||
|
|
||||||
|
VAULTS_FILE="$HOME/.local/state/omarchy/obsidian-vaults"
|
||||||
|
CURRENT_THEME_DIR="$HOME/.config/omarchy/current/theme"
|
||||||
|
|
||||||
|
# Ensure the vaults registry exists, or bootstrap from known locations
|
||||||
|
ensure_vaults_file() {
|
||||||
|
mkdir -p "$(dirname "$VAULTS_FILE")"
|
||||||
|
# If file exists (even empty), do not scan; treat as authoritative
|
||||||
|
if [ -f "$VAULTS_FILE" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local tmpfile
|
||||||
|
tmpfile="$(mktemp)"
|
||||||
|
# Scan a couple of common locations for <base>/<vault>/.obsidian
|
||||||
|
for base in "$HOME/Documents" "$HOME/Dropbox"; do
|
||||||
|
[ -d "$base" ] || continue
|
||||||
|
for d in "$base"/*/.obsidian; do
|
||||||
|
[ -d "$d" ] || continue
|
||||||
|
vault_dir="${d%/.obsidian}"
|
||||||
|
printf "%s\n" "$vault_dir" >>"$tmpfile"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
if [ -s "$tmpfile" ]; then
|
||||||
|
sort -u "$tmpfile" >"$VAULTS_FILE"
|
||||||
|
else
|
||||||
|
: >"$VAULTS_FILE"
|
||||||
|
fi
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure theme directory and minimal manifest exist in a vault
|
||||||
|
ensure_theme_scaffold() {
|
||||||
|
local vault_path="$1"
|
||||||
|
local theme_dir="$vault_path/.obsidian/themes/Omarchy"
|
||||||
|
mkdir -p "$theme_dir"
|
||||||
|
if [ ! -f "$theme_dir/manifest.json" ]; then
|
||||||
|
cat >"$theme_dir/manifest.json" <<'EOF'
|
||||||
|
{
|
||||||
|
"name": "Omarchy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"minAppVersion": "0.16.0",
|
||||||
|
"description": "Automatically syncs with your current Omarchy system theme colors and fonts",
|
||||||
|
"author": "Omarchy",
|
||||||
|
"authorUrl": "https://omarchy.org"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
[ -f "$theme_dir/theme.css" ] || : >"$theme_dir/theme.css"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to extract hex color from string
|
||||||
|
extract_hex_color() {
|
||||||
|
echo "$1" | grep -oE '#[0-9a-fA-F]{6}' | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to convert RGB/RGBA to hex
|
||||||
|
rgb_to_hex() {
|
||||||
|
local rgb_string="$1"
|
||||||
|
if [[ $rgb_string =~ rgba?\(([0-9]+),\s*([0-9]+),\s*([0-9]+) ]]; then
|
||||||
|
printf "#%02x%02x%02x\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert hex to RGB components
|
||||||
|
hex_to_rgb() {
|
||||||
|
local hex="${1#\#}"
|
||||||
|
printf "%d %d %d\n" "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate perceived brightness (0-255)
|
||||||
|
calculate_brightness() {
|
||||||
|
local hex="$1"
|
||||||
|
read -r r g b <<<"$(hex_to_rgb "$hex")"
|
||||||
|
# Use perceived brightness formula
|
||||||
|
echo $(((r * 299 + g * 587 + b * 114) / 1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate color distance (euclidean in RGB space)
|
||||||
|
color_distance() {
|
||||||
|
local hex1="$1"
|
||||||
|
local hex2="$2"
|
||||||
|
read -r r1 g1 b1 <<<"$(hex_to_rgb "$hex1")"
|
||||||
|
read -r r2 g2 b2 <<<"$(hex_to_rgb "$hex2")"
|
||||||
|
echo $(((r1 - r2) * (r1 - r2) + (g1 - g2) * (g1 - g2) + (b1 - b2) * (b1 - b2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract all colors with frequency count
|
||||||
|
extract_all_colors_with_count() {
|
||||||
|
local -A color_counts
|
||||||
|
local color
|
||||||
|
|
||||||
|
# Extract from Alacritty config
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/alacritty.toml" ]; then
|
||||||
|
# Primary colors
|
||||||
|
while IFS= read -r color; do
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[ -n "$color" ] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -E "(background|foreground|cursor|text)" "$CURRENT_THEME_DIR/alacritty.toml" | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | grep "^#")
|
||||||
|
|
||||||
|
# Normal colors
|
||||||
|
while IFS= read -r color; do
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[ -n "$color" ] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -A 20 "\[colors.normal\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep -E "(black|red|green|yellow|blue|magenta|cyan|white)" | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | grep "^#")
|
||||||
|
|
||||||
|
# Bright colors
|
||||||
|
while IFS= read -r color; do
|
||||||
|
# Add # if missing
|
||||||
|
[[ "$color" =~ ^[0-9a-fA-F]{6}$ ]] && color="#$color"
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[[ "$color" =~ ^#[0-9a-f]{6}$ ]] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -A 20 "\[colors.bright\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep -E "(black|red|green|yellow|blue|magenta|cyan|white)" | sed "s/.*[\"']//;s/[\"'].*//")
|
||||||
|
|
||||||
|
# Dim colors if present
|
||||||
|
while IFS= read -r color; do
|
||||||
|
# Add # if missing
|
||||||
|
[[ "$color" =~ ^[0-9a-fA-F]{6}$ ]] && color="#$color"
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[[ "$color" =~ ^#[0-9a-f]{6}$ ]] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -A 20 "\[colors.dim\]" "$CURRENT_THEME_DIR/alacritty.toml" 2>/dev/null | grep -E "(black|red|green|yellow|blue|magenta|cyan|white)" | sed "s/.*[\"']//;s/[\"'].*//")
|
||||||
|
|
||||||
|
# Selection colors
|
||||||
|
while IFS= read -r color; do
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[ -n "$color" ] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -A 5 "\[colors.selection\]" "$CURRENT_THEME_DIR/alacritty.toml" 2>/dev/null | grep -E "(background|text)" | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | grep "^#")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract from Waybar CSS
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/waybar.css" ]; then
|
||||||
|
while IFS= read -r color; do
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[ -n "$color" ] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -oE '@define-color [a-z_-]+ #[0-9a-fA-F]{6}' "$CURRENT_THEME_DIR/waybar.css" | grep -oE '#[0-9a-fA-F]{6}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract from Hyprland config
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/hyprland.conf" ]; then
|
||||||
|
while IFS= read -r color; do
|
||||||
|
if [[ $color == rgba* ]] || [[ $color == rgb* ]]; then
|
||||||
|
color=$(rgb_to_hex "$color")
|
||||||
|
fi
|
||||||
|
color=$(echo "$color" | tr '[:upper:]' '[:lower:]') # Lowercase for consistency
|
||||||
|
[ -n "$color" ] && ((color_counts["$color"]++))
|
||||||
|
done < <(grep -E "col\.(active|inactive)_border" "$CURRENT_THEME_DIR/hyprland.conf" | grep -oE 'rgba?\([^)]+\)|#[0-9a-fA-F]{6,8}' | sed 's/ff$//')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output colors with their counts
|
||||||
|
for color in "${!color_counts[@]}"; do
|
||||||
|
echo "${color_counts[$color]} $color"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sort colors by frequency
|
||||||
|
sort_colors_by_frequency() {
|
||||||
|
# Input is already "count color" format
|
||||||
|
sort -rn | cut -d' ' -f2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fill color slots with cycling if needed
|
||||||
|
fill_color_slots() {
|
||||||
|
local -a colors=("$@")
|
||||||
|
local -a slots
|
||||||
|
local num_colors=${#colors[@]}
|
||||||
|
|
||||||
|
# Need 13 slots total (code colors are handled separately)
|
||||||
|
local slots_needed=13
|
||||||
|
|
||||||
|
if [ $num_colors -eq 0 ]; then
|
||||||
|
# No colors available, use defaults
|
||||||
|
colors=("#3d3d3d" "#5d5d5d" "#7d7d7d" "#9d9d9d" "#bd93f9" "#50fa7b")
|
||||||
|
num_colors=6
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fill slots, cycling if necessary
|
||||||
|
for ((i = 0; i < slots_needed; i++)); do
|
||||||
|
slots[$i]="${colors[$((i % num_colors))]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "${slots[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main color extraction and theme generation
|
||||||
|
extract_theme_data() {
|
||||||
|
# Get primary colors from Alacritty
|
||||||
|
local bg_color="#1a1b26"
|
||||||
|
local fg_color="#a9b1d6"
|
||||||
|
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/alacritty.toml" ]; then
|
||||||
|
local extracted_bg=$(grep -A 5 "\[colors.primary\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep "^background = " | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | head -1 | tr '[:upper:]' '[:lower:]')
|
||||||
|
local extracted_fg=$(grep -A 5 "\[colors.primary\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep "^foreground = " | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | head -1 | tr '[:upper:]' '[:lower:]')
|
||||||
|
[ -n "$extracted_bg" ] && bg_color="$extracted_bg"
|
||||||
|
[ -n "$extracted_fg" ] && fg_color="$extracted_fg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine if light or dark theme
|
||||||
|
local bg_brightness=$(calculate_brightness "$bg_color")
|
||||||
|
local is_light_theme=false
|
||||||
|
[ $bg_brightness -gt 127 ] && is_light_theme=true
|
||||||
|
|
||||||
|
# Extract all colors with counts
|
||||||
|
local color_data=$(extract_all_colors_with_count)
|
||||||
|
|
||||||
|
# Filter out background and foreground colors for the main array
|
||||||
|
local filtered_data=$(echo "$color_data" | grep -v "$bg_color" | grep -v "$fg_color")
|
||||||
|
|
||||||
|
# Get all unique colors (including bg/fg) for distance calculations
|
||||||
|
local -a all_unique_colors
|
||||||
|
readarray -t all_unique_colors < <(echo "$color_data" | cut -d' ' -f2 | sort -u)
|
||||||
|
|
||||||
|
# Find the 3 closest colors to background for background variations
|
||||||
|
local -a bg_distances
|
||||||
|
for color in "${all_unique_colors[@]}"; do
|
||||||
|
if [ "$color" != "$bg_color" ]; then
|
||||||
|
distance=$(color_distance "$color" "$bg_color")
|
||||||
|
bg_distances+=("$distance:$color")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Sort by distance and get the closest color for code background
|
||||||
|
local -a closest_to_bg
|
||||||
|
readarray -t closest_to_bg < <(printf '%s\n' "${bg_distances[@]}" | sort -n | head -1 | cut -d: -f2)
|
||||||
|
|
||||||
|
# All background variations use the same as primary background
|
||||||
|
local bg_primary_alt="$bg_color"
|
||||||
|
local bg_secondary="$bg_color"
|
||||||
|
local bg_secondary_alt="$bg_color"
|
||||||
|
|
||||||
|
# Code block background uses the closest different color
|
||||||
|
local code_bg="${closest_to_bg[0]}"
|
||||||
|
|
||||||
|
# If no different color available, create a subtle variant for code blocks
|
||||||
|
if [ -z "$code_bg" ]; then
|
||||||
|
read -r r g b <<<"$(hex_to_rgb "$bg_color")"
|
||||||
|
if [ $bg_brightness -gt 127 ]; then
|
||||||
|
r=$((r - 10))
|
||||||
|
g=$((g - 10))
|
||||||
|
b=$((b - 10))
|
||||||
|
else
|
||||||
|
r=$((r + 15))
|
||||||
|
g=$((g + 15))
|
||||||
|
b=$((b + 15))
|
||||||
|
fi
|
||||||
|
[ $r -lt 0 ] && r=0
|
||||||
|
[ $r -gt 255 ] && r=255
|
||||||
|
[ $g -lt 0 ] && g=0
|
||||||
|
[ $g -gt 255 ] && g=255
|
||||||
|
[ $b -lt 0 ] && b=0
|
||||||
|
[ $b -gt 255 ] && b=255
|
||||||
|
code_bg=$(printf "#%02x%02x%02x" "$r" "$g" "$b")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find closest color to foreground for code block text
|
||||||
|
local code_fg=""
|
||||||
|
min_distance=999999999
|
||||||
|
for color in "${all_unique_colors[@]}"; do
|
||||||
|
if [ "$color" != "$fg_color" ]; then
|
||||||
|
distance=$(color_distance "$color" "$fg_color")
|
||||||
|
if [ $distance -lt $min_distance ]; then
|
||||||
|
min_distance=$distance
|
||||||
|
code_fg="$color"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -z "$code_fg" ] && code_fg="#e0e0e0" # Fallback
|
||||||
|
|
||||||
|
# Extract text selection colors from Alacritty
|
||||||
|
local selection_bg=""
|
||||||
|
local selection_fg=""
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/alacritty.toml" ]; then
|
||||||
|
selection_bg=$(grep -A 5 "\[colors.selection\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep "^background = " | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | head -1 | tr '[:upper:]' '[:lower:]')
|
||||||
|
local selection_text=$(grep -A 5 "\[colors.selection\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep "^text = " | sed "s/.*[\"']0x/#/;s/.*[\"']#/#/;s/[\"'].*//;s/.*#\([0-9a-fA-F]\{6\}\).*/\#\1/" | head -1 | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
# If text is set to CellForeground/CellBackground, use the appropriate color
|
||||||
|
if [ -z "$selection_text" ] || [[ "$(grep -A 5 "\[colors.selection\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep "^text = ")" == *"CellForeground"* ]]; then
|
||||||
|
selection_fg="$fg_color"
|
||||||
|
elif [[ "$(grep -A 5 "\[colors.selection\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep "^text = ")" == *"CellBackground"* ]]; then
|
||||||
|
selection_fg="$bg_color"
|
||||||
|
else
|
||||||
|
selection_fg="$selection_text"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback if no selection colors found
|
||||||
|
if [ -z "$selection_bg" ]; then
|
||||||
|
read -r r1 g1 b1 <<<"$(hex_to_rgb "$bg_color")"
|
||||||
|
read -r r2 g2 b2 <<<"$(hex_to_rgb "$fg_color")"
|
||||||
|
local r=$(((r1 * 3 + r2) / 4)) # 75% background, 25% foreground
|
||||||
|
local g=$(((g1 * 3 + g2) / 4))
|
||||||
|
local b=$(((b1 * 3 + b2) / 4))
|
||||||
|
selection_bg=$(printf "#%02x%02x%02x" "$r" "$g" "$b")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use contrasting color for selection text if not defined
|
||||||
|
if [ -z "$selection_fg" ]; then
|
||||||
|
# Calculate brightness of selection background
|
||||||
|
local sel_brightness=$(calculate_brightness "$selection_bg")
|
||||||
|
if [ $sel_brightness -gt 127 ]; then
|
||||||
|
selection_fg="$bg_color" # Dark text on light selection
|
||||||
|
else
|
||||||
|
selection_fg="$fg_color" # Light text on dark selection
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract border color from btop theme
|
||||||
|
local border_color=""
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/btop.theme" ]; then
|
||||||
|
# Look for theme[div_line] in btop theme
|
||||||
|
local btop_divline=$(grep 'theme\[div_line\]' "$CURRENT_THEME_DIR/btop.theme" | head -1)
|
||||||
|
|
||||||
|
if [ -n "$btop_divline" ]; then
|
||||||
|
# Extract the color value after the = sign
|
||||||
|
local extracted=$(echo "$btop_divline" | sed 's/.*=//' | xargs)
|
||||||
|
|
||||||
|
# Check if it's a hex color and lowercase it
|
||||||
|
if [[ $extracted =~ ^#[0-9a-fA-F]{6}$ ]]; then
|
||||||
|
border_color=$(echo "$extracted" | tr '[:upper:]' '[:lower:]')
|
||||||
|
elif [[ $extracted =~ ^[0-9a-fA-F]{6}$ ]]; then
|
||||||
|
# Add # if missing and lowercase
|
||||||
|
border_color=$(echo "#$extracted" | tr '[:upper:]' '[:lower:]')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback if no border color found
|
||||||
|
if [ -z "$border_color" ]; then
|
||||||
|
# Use a color between bg and fg
|
||||||
|
read -r r1 g1 b1 <<<"$(hex_to_rgb "$bg_color")"
|
||||||
|
read -r r2 g2 b2 <<<"$(hex_to_rgb "$fg_color")"
|
||||||
|
local r=$(((r1 + r2) / 3)) # Closer to background
|
||||||
|
local g=$(((g1 + g2) / 3))
|
||||||
|
local b=$(((b1 + b2) / 3))
|
||||||
|
border_color=$(printf "#%02x%02x%02x" "$r" "$g" "$b")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get unique colors array (without bg/fg) sorted by frequency
|
||||||
|
local -a unique_colors
|
||||||
|
readarray -t unique_colors < <(echo "$filtered_data" | sort_colors_by_frequency)
|
||||||
|
|
||||||
|
# Fill the 13 color slots (code colors handled separately)
|
||||||
|
local -a color_slots
|
||||||
|
readarray -t color_slots < <(fill_color_slots "${unique_colors[@]}" | tr ' ' '\n')
|
||||||
|
|
||||||
|
# Extract fonts
|
||||||
|
local monospace_font="CaskaydiaMono Nerd Font"
|
||||||
|
local ui_font="Liberation Sans"
|
||||||
|
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/alacritty.toml" ]; then
|
||||||
|
local alacritty_font=$(grep -A 5 "\[font\]" "$CURRENT_THEME_DIR/alacritty.toml" | grep 'family = ' | head -1 | cut -d'"' -f2)
|
||||||
|
[ -n "$alacritty_font" ] && monospace_font="$alacritty_font"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.config/fontconfig/fonts.conf" ]; then
|
||||||
|
local fontconfig_mono=$(xmlstarlet sel -t -v '//match[@target="pattern"][test/string="monospace"]/edit[@name="family"]/string' "$HOME/.config/fontconfig/fonts.conf" 2>/dev/null || true)
|
||||||
|
[ -n "$fontconfig_mono" ] && monospace_font="$fontconfig_mono"
|
||||||
|
|
||||||
|
local fontconfig_sans=$(xmlstarlet sel -t -v '//match[@target="pattern"][test/string="sans-serif"]/edit[@name="family"]/string' "$HOME/.config/fontconfig/fonts.conf" 2>/dev/null || true)
|
||||||
|
[ -n "$fontconfig_sans" ] && ui_font="$fontconfig_sans"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate CSS with 14-slot system
|
||||||
|
cat <<EOF
|
||||||
|
/* Omarchy Theme for Obsidian */
|
||||||
|
/* Generated on $(date) from theme: $(basename "$(readlink "$CURRENT_THEME_DIR" 2>/dev/null || echo "unknown")") */
|
||||||
|
/* Colors sorted by frequency, backgrounds by distance */
|
||||||
|
|
||||||
|
.theme-dark, .theme-light {
|
||||||
|
/* Core colors */
|
||||||
|
--background-primary: $bg_color;
|
||||||
|
--text-normal: $fg_color;
|
||||||
|
|
||||||
|
/* Background variations (always distance-based) */
|
||||||
|
--background-primary-alt: $bg_primary_alt;
|
||||||
|
--background-secondary: $bg_secondary;
|
||||||
|
--background-secondary-alt: $bg_secondary_alt;
|
||||||
|
|
||||||
|
/* Code block colors (always distance-based) */
|
||||||
|
--code-background: $code_bg;
|
||||||
|
--code-foreground: $code_fg;
|
||||||
|
|
||||||
|
/* Border color from btop theme */
|
||||||
|
--border-color: $border_color;
|
||||||
|
|
||||||
|
/* Selection colors from Alacritty */
|
||||||
|
--text-selection: $selection_bg;
|
||||||
|
--text-selection-fg: $selection_fg;
|
||||||
|
|
||||||
|
/* 13-slot color system for remaining elements */
|
||||||
|
--text-title-h1: ${color_slots[0]};
|
||||||
|
--text-title-h2: ${color_slots[1]};
|
||||||
|
--text-title-h3: ${color_slots[2]};
|
||||||
|
--text-title-h4: ${color_slots[3]};
|
||||||
|
--text-title-h5: ${color_slots[4]};
|
||||||
|
--text-title-h6: ${color_slots[4]}; /* Same as h5 */
|
||||||
|
--text-link: ${color_slots[5]};
|
||||||
|
--markup-code: ${color_slots[6]};
|
||||||
|
--text-mark: ${color_slots[7]};
|
||||||
|
--interactive-accent: ${color_slots[8]};
|
||||||
|
--blockquote-border: ${color_slots[9]};
|
||||||
|
--text-muted: $border_color; /* Use border color for muted text */
|
||||||
|
--text-faint: $border_color; /* Use border color for faint text */
|
||||||
|
|
||||||
|
/* Additional mappings */
|
||||||
|
--text-accent: var(--interactive-accent);
|
||||||
|
--text-accent-hover: var(--interactive-accent);
|
||||||
|
--text-error: var(--text-title-h1);
|
||||||
|
--text-error-hover: var(--text-title-h1);
|
||||||
|
--text-highlight-bg: $fg_color; /* Use text color as highlight background */
|
||||||
|
--text-on-accent: $bg_color;
|
||||||
|
|
||||||
|
--interactive-normal: var(--code-background);
|
||||||
|
--interactive-hover: var(--interactive-accent);
|
||||||
|
--interactive-accent-hover: var(--interactive-accent);
|
||||||
|
--interactive-success: var(--text-title-h2);
|
||||||
|
|
||||||
|
--scrollbar-bg: var(--background-primary);
|
||||||
|
--scrollbar-thumb-bg: var(--code-background);
|
||||||
|
--scrollbar-active-thumb-bg: var(--interactive-accent);
|
||||||
|
|
||||||
|
--background-modifier-border: var(--border-color);
|
||||||
|
--background-modifier-form-field: var(--code-background);
|
||||||
|
--background-modifier-form-field-highlighted: var(--code-background);
|
||||||
|
--background-modifier-box-shadow: rgba(0, 0, 0, 0.3);
|
||||||
|
--background-modifier-success: var(--interactive-success);
|
||||||
|
--background-modifier-error: var(--text-error);
|
||||||
|
--background-modifier-error-hover: var(--text-error);
|
||||||
|
--background-modifier-cover: rgba(0, 0, 0, 0.8);
|
||||||
|
|
||||||
|
--link-color: var(--text-link);
|
||||||
|
--link-color-hover: var(--text-link);
|
||||||
|
--link-unresolved-color: var(--text-muted);
|
||||||
|
--link-unresolved-opacity: 0.7;
|
||||||
|
|
||||||
|
--tag-color: var(--text-title-h3);
|
||||||
|
--tag-background: var(--code-background);
|
||||||
|
|
||||||
|
--graph-line: var(--text-muted);
|
||||||
|
--graph-node: var(--interactive-accent);
|
||||||
|
--graph-node-unresolved: var(--text-muted);
|
||||||
|
--graph-node-focused: var(--text-link);
|
||||||
|
--graph-node-tag: var(--text-title-h3);
|
||||||
|
--graph-node-attachment: var(--text-title-h2);
|
||||||
|
|
||||||
|
/* Fonts */
|
||||||
|
--font-interface-theme: "$ui_font";
|
||||||
|
--font-text-theme: "$ui_font";
|
||||||
|
--font-monospace-theme: "$monospace_font";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headers */
|
||||||
|
.cm-header-1, .markdown-rendered h1 { color: var(--text-title-h1); }
|
||||||
|
.cm-header-2, .markdown-rendered h2 { color: var(--text-title-h2); }
|
||||||
|
.cm-header-3, .markdown-rendered h3 { color: var(--text-title-h3); }
|
||||||
|
.cm-header-4, .markdown-rendered h4 { color: var(--text-title-h4); }
|
||||||
|
.cm-header-5, .markdown-rendered h5 { color: var(--text-title-h5); }
|
||||||
|
.cm-header-6, .markdown-rendered h6 { color: var(--text-title-h6); }
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
.markdown-rendered code {
|
||||||
|
font-family: var(--font-monospace-theme);
|
||||||
|
background-color: var(--code-background);
|
||||||
|
color: var(--markup-code);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-rendered pre {
|
||||||
|
background-color: var(--code-background);
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-rendered pre code {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--code-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syntax highlighting */
|
||||||
|
.cm-s-obsidian span.cm-keyword { color: var(--text-title-h1); }
|
||||||
|
.cm-s-obsidian span.cm-string { color: var(--text-title-h2); }
|
||||||
|
.cm-s-obsidian span.cm-number { color: var(--text-title-h3); }
|
||||||
|
.cm-s-obsidian span.cm-comment { color: var(--text-muted); }
|
||||||
|
.cm-s-obsidian span.cm-operator { color: var(--text-link); }
|
||||||
|
.cm-s-obsidian span.cm-variable { color: var(--text-normal); }
|
||||||
|
.cm-s-obsidian span.cm-def { color: var(--text-link); }
|
||||||
|
|
||||||
|
/* Highlighted text */
|
||||||
|
.markdown-rendered mark,
|
||||||
|
.cm-s-obsidian span.cm-highlight,
|
||||||
|
mark {
|
||||||
|
background-color: var(--text-highlight-bg) !important;
|
||||||
|
color: var(--code-background) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
.markdown-rendered a {
|
||||||
|
color: var(--text-link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blockquotes */
|
||||||
|
.markdown-rendered blockquote {
|
||||||
|
border-left: 4px solid var(--blockquote-border);
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status bar */
|
||||||
|
.status-bar {
|
||||||
|
background-color: var(--code-background);
|
||||||
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active file */
|
||||||
|
.workspace-leaf.mod-active .workspace-leaf-header-title {
|
||||||
|
color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-file-title.is-active {
|
||||||
|
background-color: var(--code-background);
|
||||||
|
color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text selection */
|
||||||
|
::selection {
|
||||||
|
background-color: var(--text-selection);
|
||||||
|
color: var(--text-selection-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search results */
|
||||||
|
.search-result-file-title {
|
||||||
|
color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-file-match {
|
||||||
|
background-color: var(--code-background);
|
||||||
|
color: var(--text-normal);
|
||||||
|
border-left: 3px solid var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-file-matched-text {
|
||||||
|
color: var(--code-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.markdown-rendered table {
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-rendered th {
|
||||||
|
background-color: var(--code-background);
|
||||||
|
color: var(--text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-rendered td {
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callouts */
|
||||||
|
.callout {
|
||||||
|
border-left: 4px solid var(--interactive-accent);
|
||||||
|
background-color: var(--code-background);
|
||||||
|
}
|
||||||
|
.callout * {
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal dialogs */
|
||||||
|
.modal {
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
border: 2px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings */
|
||||||
|
.vertical-tab-header-group-title {
|
||||||
|
color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-tab-nav-item.is-active {
|
||||||
|
background-color: var(--code-background);
|
||||||
|
color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Option handling
|
||||||
|
if [ "${1:-}" = "--reset" ]; then
|
||||||
|
echo "♻️ Resetting Omarchy themes and registry..."
|
||||||
|
if [ -f "$VAULTS_FILE" ] && [ -s "$VAULTS_FILE" ]; then
|
||||||
|
while IFS= read -r vault_path || [ -n "$vault_path" ]; do
|
||||||
|
case "$vault_path" in ""|\#*) continue ;; esac
|
||||||
|
vault_path="${vault_path%/}"
|
||||||
|
vault_name=$(basename "$vault_path")
|
||||||
|
theme_dir="$vault_path/.obsidian/themes/Omarchy"
|
||||||
|
if [ -d "$theme_dir" ]; then
|
||||||
|
rm -rf "$theme_dir"
|
||||||
|
echo " ✅ $vault_name (theme removed)"
|
||||||
|
else
|
||||||
|
echo " ℹ️ $vault_name (no theme present)"
|
||||||
|
fi
|
||||||
|
done <"$VAULTS_FILE"
|
||||||
|
fi
|
||||||
|
rm -f "$VAULTS_FILE"
|
||||||
|
echo "✅ Registry removed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Main update logic
|
||||||
|
echo "🔄 Updating Obsidian vaults..."
|
||||||
|
|
||||||
|
# Step 1: ensure registry exists (bootstrap if needed)
|
||||||
|
ensure_vaults_file
|
||||||
|
|
||||||
|
while IFS= read -r vault_path || [ -n "$vault_path" ]; do
|
||||||
|
case "$vault_path" in "" | \#*) continue ;; esac
|
||||||
|
vault_path="${vault_path%/}"
|
||||||
|
vault_name=$(basename "$vault_path")
|
||||||
|
|
||||||
|
# Step 2: verify path exists; log/skip gracefully if invalid
|
||||||
|
if [ ! -d "$vault_path" ] || [ ! -d "$vault_path/.obsidian" ]; then
|
||||||
|
echo " ❌ $vault_name (invalid entry: missing directory or .obsidian)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure theme files exist for this vault
|
||||||
|
ensure_theme_scaffold "$vault_path"
|
||||||
|
THEME_DIR="$vault_path/.obsidian/themes/Omarchy"
|
||||||
|
|
||||||
|
# Step 3: update theme.css
|
||||||
|
if [ -f "$CURRENT_THEME_DIR/obsidian.css" ]; then
|
||||||
|
cp "$CURRENT_THEME_DIR/obsidian.css" "$THEME_DIR/theme.css"
|
||||||
|
echo " ✅ $vault_name (custom theme)"
|
||||||
|
else
|
||||||
|
extract_theme_data >"$THEME_DIR/theme.css"
|
||||||
|
echo " ✅ $vault_name (generated theme)"
|
||||||
|
fi
|
||||||
|
done <"$VAULTS_FILE"
|
||||||
3
migrations/1760441237.sh
Normal file
3
migrations/1760441237.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
echo "Install Omarchy theme on Obsidian vaults"
|
||||||
|
|
||||||
|
omarchy-theme-set-obsidian
|
||||||
Reference in New Issue
Block a user