Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e9376b7
Delete themes/.gitkeep
zen0bit Oct 8, 2024
45da453
Add count themes
zen0bit Sep 12, 2025
d5fc829
Bump actions
zen0bit Sep 12, 2025
3826e4c
Big themes count
zen0bit Sep 12, 2025
fed708b
Create CNAME
zen0bit Sep 12, 2025
bbb784e
Update default.html
zen0bit Sep 12, 2025
2c6050b
Sage_Dust.theme (#1)
zen0bit Sep 13, 2025
e646d9b
Fix formatting for image links in README
zen0bit Feb 3, 2026
2e8abc9
Just upload 2 new themes
zen0bit Feb 3, 2026
1fac428
count
web-flow Feb 3, 2026
cbf3fa1
Fix links and images in README.md
zen0bit Feb 3, 2026
b562384
Enhance contribution guidelines in README
zen0bit Feb 5, 2026
4d79d5e
Update theme export instructions in README
zen0bit Feb 5, 2026
f0e211d
Add website and source code links to README
zen0bit Feb 5, 2026
3c12881
Automate adding themes to the repository (#4)
zen0bit Mar 8, 2026
dafe7f3
Fix themes and count
zen0bit May 1, 2026
0200493
count
web-flow May 1, 2026
5bd7ec4
Update Git user configuration for push step
zen0bit May 1, 2026
76cc3e6
count & ratings
oSoWoSo-bot May 1, 2026
117212b
hmmm
zen0bit May 1, 2026
3b5f4f9
count & ratings
oSoWoSo-bot May 1, 2026
906f786
Fix star doubled + screenshot is also link to theme page
zen0bit May 1, 2026
3e2ff31
count & ratings
oSoWoSo-bot May 1, 2026
0596c7e
fix
zen0bit May 1, 2026
5532190
count & ratings
oSoWoSo-bot May 1, 2026
ec06ff6
Fix URLs
zen0bit May 1, 2026
f1934a8
Fix star again
zen0bit May 1, 2026
b75aeac
count & ratings
oSoWoSo-bot May 1, 2026
df5b9ac
count & ratings
oSoWoSo-bot May 2, 2026
7f5f27e
fix
zen0bit May 2, 2026
4631b5a
count & ratings
oSoWoSo-bot May 2, 2026
3215b7a
count & ratings
oSoWoSo-bot May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 258 additions & 21 deletions .github/workflows/jekyll-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,287 @@
name: Deploy Jekyll with GitHub Pages dependencies preinstalled

on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
schedule:
- cron: '0 */6 * * *'

permissions:
contents: read
contents: write
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
cancel-in-progress: true

jobs:
# Build job
build:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Count themes
run: |
if grep -q "Themes count:" README.md; then
sed -i "/# Themes count:/c\# Themes count: $(find ./themes/ -type f -name '*.theme' | wc -l)" README.md
fi

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Fetch reactions and update ratings
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pip install pyyaml requests
python3 << 'EOF'
import os, requests, yaml, re
from pathlib import Path

headers = {
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2026-03-10'
}
token = os.environ.get('TOKEN')
if token:
headers['Authorization'] = f'Bearer {token}'

discussions = []
page = 1
while True:
r = requests.get(
'https://api.github.com/repos/oSoWoSo/SimpleX-Themes/discussions',
headers=headers,
params={'per_page': 100, 'page': page}
)
if r.status_code != 200:
break
batch = r.json()
if not batch:
break
discussions.extend(batch)
if len(batch) < 100:
break
page += 1

ratings = {}
for d in discussions:
if d.get('category', {}).get('slug') == 'announcements':
continue
reactions = d.get('reactions', {})
score = sum([
reactions.get('+1', 0),
reactions.get('heart', 0),
reactions.get('rocket', 0),
reactions.get('laugh', 0),
reactions.get('hooray', 0)
])

title = d.get('title', '').lower()
if title.startswith('[theme] '):
title = title[8:].strip()
slug = title.replace(' ', '-')
ratings[slug] = {
'score': score,
'display': f'⭐ {score}' if score > 0 else ''
}

output_file = Path('_data/ratings.yml')
output_file.parent.mkdir(exist_ok=True)
with open(output_file, 'w') as f:
yaml.dump(ratings, f, allow_unicode=True, default_flow_style=False)

# --- Update README.md stars ---

with open('README.md', 'r') as f:
content = f.read()

# Strip all existing stars
content = re.sub(r'<br>⭐ \d+', '', content)

# Match cells whose link ends in _index.md OR _index.html (handles both states)
cell_pattern = r'(?:<a href=[^>]+>)?(<img src=[^ ]+ width=[^>]+>)(?:</a>)?<br>(\[([^\]]+)\]\(([^)]+_index\.(?:md|html))\))'

def replace_cell(match):
img_tag = match.group(1)
theme_name = match.group(3)
page_path = match.group(4)

# Always read from the .md source file regardless of link extension
md_path = page_path.replace('./', '').replace('.html', '.md')
star_display = ''
page_file = Path(md_path)
if page_file.exists():
with open(page_file, 'r') as pf:
pf_content = pf.read()
giscus_match = re.search(r'giscus:\s*(\S+)', pf_content)
if giscus_match:
slug = giscus_match.group(1)
rating = ratings.get(slug, {})
display = rating.get('display', '')
if display:
star_display = f'<br>{display}'

# Canonical path: no ./ prefix, always .html
clean_path = page_path.replace('./', '').replace('.md', '.html')
linked_img = f'<a href="{clean_path}">{img_tag}</a>'
return f'{linked_img}<br>[{theme_name}]({clean_path}){star_display}'

updated_content = re.sub(cell_pattern, replace_cell, content)

with open('README.md', 'w') as f:
f.write(updated_content)

# --- Update popularity tags in every theme index page ---
# Always remove and re-add so the displayed score stays current.

for md_file in sorted(Path('resources').glob('*_index.md')):
with open(md_file, 'r') as f:
content = f.read()
giscus_match = re.search(r'giscus:\s*(\S+)', content)
if not giscus_match:
continue
slug = giscus_match.group(1)
rating = ratings.get(slug, {})
display = rating.get('display', '')

# Remove any existing popularity tag
content = re.sub(r'\n?<p class="theme-popularity">[^\n]*</p>\n?', '\n', content)

if display:
lines = content.split('\n')
new_lines = []
title_found = False
for i, line in enumerate(lines):
new_lines.append(line)
if not title_found and line.startswith('# ') and i > 0:
title_found = True
new_lines.append(f'\n<p class="theme-popularity">Popularity: {display}</p>')
content = '\n'.join(new_lines)

with open(md_file, 'w') as f:
f.write(content)

# --- Link older versions on the latest-version page ---
# Group index pages by giscus slug (= theme family).
# Pages listed in README are "current"; all others in the same family are "older".

family_map = {}
for md_file in sorted(Path('resources').glob('*_index.md')):
with open(md_file, 'r') as f:
fc = f.read()
gm = re.search(r'giscus:\s*(\S+)', fc)
hm = re.search(r'^# (.+)$', fc, re.MULTILINE)
if gm and hm:
family_map.setdefault(gm.group(1), []).append(
(md_file, hm.group(1).strip())
)

with open('README.md', 'r') as f:
readme_text = f.read()
readme_refs = set(re.findall(r'(SxC_[^)"]+_index\.html)', readme_text))

for slug, members in family_map.items():
if len(members) <= 1:
continue

current = [(f, n) for f, n in members if f.name.replace('.md', '.html') in readme_refs]
older = [(f, n) for f, n in members if f.name.replace('.md', '.html') not in readme_refs]

if not current or not older:
continue

cur_file, cur_name = current[0]

# Update latest-version page: rebuild "Older Versions" section
with open(cur_file, 'r') as f:
content = f.read()
content = re.sub(
r'\n+----\n### Older Versions\n.*?(?=\n\* \[Return Home\])',
'', content, flags=re.DOTALL
)
def ver_key(item):
name = item[0].stem
m = re.search(r'[Vv](\d+)[_.](\d+)', name)
if m:
return (int(m.group(1)), int(m.group(2)))
m = re.search(r'(?:-[Vv]|(?<=[a-zA-Z])[V])(\d+)', name)
if m:
return (int(m.group(1)), 0)
return (0, 0)

older_section = '\n\n----\n### Older Versions\n\n'
for old_file, old_name in sorted(older, key=ver_key, reverse=True):
older_section += f'* [{old_name}]({old_file.name.replace(".md", ".html")})\n'
older_section += '\n'
content = re.sub(
r'\n\* \[Return Home\]\(\.\./\)',
older_section + '\n* [Return Home](../)',
content
)
with open(cur_file, 'w') as f:
f.write(content)

# Update each older-version page: add/refresh "newer version" notice
for old_file, old_name in older:
with open(old_file, 'r') as f:
oc = f.read()
oc = re.sub(r'\n?> \*\*Newer version available:[^\n]*\n?', '', oc)
note = f'\n> **Newer version available:** [{cur_name}]({cur_file.name.replace(".md", ".html")})\n'
lines = oc.split('\n')
new_lines = []
title_found = False
for i, line in enumerate(lines):
new_lines.append(line)
if not title_found and line.startswith('# ') and i > 0:
title_found = True
new_lines.append(note)
oc = '\n'.join(new_lines)
with open(old_file, 'w') as f:
f.write(oc)

# --- Ensure CSS for popularity badge ---
css_addition = '\n\n.theme-popularity {\n margin: 10px 0;\n font-size: 1.1em;\n}\n'
with open('assets/css/style.scss', 'r') as f:
css_content = f.read()
if '.theme-popularity' not in css_content:
with open('assets/css/style.scss', 'a') as f:
f.write(css_addition)

print('Ratings updated successfully')
EOF

- name: Push changes
run: |
git config --global user.name "oSoWoSo-bot"
git config --global user.email "osowoso@disroot.org"
if git diff --exit-code HEAD; then
echo "No changes to commit."
else
git add README.md _data/ratings.yml resources/*.md assets/css/style.scss
git commit -m "count & ratings"
git push --force-with-lease && echo "sync successfull" || exit 0
fi

- name: Setup Pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@v6

- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
source: ./
destination: ./_site

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4

# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
69 changes: 69 additions & 0 deletions .github/workflows/validate-theme.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Validate Theme

on:
pull_request:
paths:
- 'themes/*.theme'

jobs:
validate-theme:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Validate theme files
run: |
# Check that each theme file has the required fields
for theme_file in themes/*.theme; do
echo "Validating $theme_file..."

# Check for base field
if ! grep -q 'base:' "$theme_file"; then
echo "Error: $theme_file missing 'base:' field"
exit 1
fi

# Check for required color fields
required_colors=("accent" "background" "menus")
for color in "${required_colors[@]}"; do
if ! grep -q "$color:" "$theme_file"; then
echo "Warning: $theme_file missing '$color:' field"
fi
done

echo "$theme_file is valid"
done

echo "All theme files validated successfully!"

- name: Check screenshots exist
run: |
echo "Checking that screenshots exist for new themes..."

# Get list of theme files
themes=$(find themes -name '*.theme' -exec basename {} .theme \;)

for theme in $themes; do
# Check if screenshots exist (either with 01-04 or v101-v104 pattern)
has_screenshots=false

# Pattern 1: SxC_themeName01.jpg
if ls screenshots/SxC_${theme}0*.jpg 1> /dev/null 2>&1; then
has_screenshots=true
fi

# Pattern 2: SxC_themeName-v101.jpg
if ls screenshots/SxC_${theme}-v1*.jpg 1> /dev/null 2>&1; then
has_screenshots=true
fi

if [ "$has_screenshots" = false ]; then
echo "Warning: No screenshots found for $theme"
fi
done
1 change: 1 addition & 0 deletions CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
themes.osowoso.org
Loading