diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml
index e31d81c..35180b6 100644
--- a/.github/workflows/jekyll-gh-pages.yml
+++ b/.github/workflows/jekyll-gh-pages.yml
@@ -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'
⭐ \d+', '', content)
+
+ # Match cells whose link ends in _index.md OR _index.html (handles both states)
+ cell_pattern = r'(?:]+>)?(]+>)(?:)?
(\[([^\]]+)\]\(([^)]+_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'
{display}'
+
+ # Canonical path: no ./ prefix, always .html
+ clean_path = page_path.replace('./', '').replace('.md', '.html')
+ linked_img = f'{img_tag}'
+ return f'{linked_img}
[{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?
[^\n]*
\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'\nPopularity: {display}
') + 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 \ No newline at end of file diff --git a/.github/workflows/validate-theme.yml b/.github/workflows/validate-theme.yml new file mode 100644 index 0000000..0d25585 --- /dev/null +++ b/.github/workflows/validate-theme.yml @@ -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 diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..30a438b --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +themes.osowoso.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3888986 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contributing to SimpleX Themes + +Thank you for your interest in contributing a theme! This guide will help you add a new theme to the repository. + +## Ways to Contribute + +### 1. GitHub Pull Request (Recommended) + +1. Fork the repository +2. Create a new branch: `git checkout -b add-theme-name` +3. Add your theme file to `themes/` directory +4. Add 4 screenshots to `screenshots/` directory +5. Generate the index.md file using the automation script +6. Update README.md to include your theme +7. Submit a pull request + +### 2. Send a Patch by Email + +See [git-send-email.io](https://git-send-email.io/) for instructions. + +### 3. Manual Upload + +1. Create your theme in the SimpleX Chat app +2. Export your theme to a file +3. Join the SimpleX Themes group and upload your theme + +## Theme File Requirements + +### Naming Convention + +Theme files should follow this pattern: +- `SxC_themeName.theme` (e.g., `SxC_myTheme.theme`) +- Use underscores instead of spaces +- Maximum length: 50 characters for the theme name + +### Theme File Format + +```yaml +base: "BLACK" # or "WHITE" +colors: + accent: "#ffa698b4" + accentVariant: "#ff584858" + secondary: "#ffecece1" + secondaryVariant: "#ff9c6e9c" + background: "#ff180818" + menus: "#ff281028" + title: "#ff807088" + accentVariant2: "#ff98a8a8" + sentMessage: "#e5503858" + sentReply: "#ff281028" + receivedMessage: "#e287758b" + receivedReply: "#ff3e293e" +wallpaper: + scale: 1.0 + scaleType: "fill" + background: "#ff070707" + tint: "#00ffffff" +``` + +## Automating Theme Addition + +We've created a script to help automate adding new themes: + +```bash +python3 scripts/add_theme.py themes/SxC_yourTheme.theme +``` + +This script will: +- Validate your theme file format +- Generate the `resources/SxC_yourTheme_index.md` file +- Provide instructions for the next steps + +### Screenshot Requirements + +Each theme needs 4 screenshots (JPG format, 120px width in the index): + +**Naming patterns supported:** +- `SxC_themeName01.jpg`, `SxC_themeName02.jpg`, etc. +- `SxC_themeName-v101.jpg`, `SxC_themeName-v102.jpg`, etc. + +### Taking Screenshots + +To take screenshots for your theme: + +1. **Android Emulator**: Use the built-in screen recording or take screenshots +2. **Physical Device**: Use your device's screenshot functionality +3. **SimpleX Chat**: Navigate through the app to capture all color variations + +## Theme Validation + +When you submit a pull request, our GitHub Actions workflow will automatically: + +- Validate that theme files have the required `base:` field +- Check for required color fields +- Verify that screenshots exist for new themes + +## Need Help? + +- Join the [SimpleX Themes group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FjwFqICow91mcVNxBF2GXXF5Uq4H27goC%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAOYs_RwIB67iDC_ORPmBpp-oED4Ric3oYkID4kdkMdGs%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jpatHRdLkjwNmbWBc-VWcg%3D%3D%22%7D) for support +- Open an issue on GitHub if you encounter problems diff --git a/README.md b/README.md index 6f27d7a..5172118 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,27 @@  -You can submit your themes to the repository by joining the [SimpleX Themes group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FjwFqICow91mcVNxBF2GXXF5Uq4H27goC%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAOYs_RwIB67iDC_ORPmBpp-oED4Ric3oYkID4kdkMdGs%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jpatHRdLkjwNmbWBc-VWcg%3D%3D%22%7D) and uploading your theme file. +[website](https://themes.osowoso.org) + +# Themes count: 103 + +You can submit your themes to the repository by joining the [SimpleX Themes group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FjwFqICow91mcVNxBF2GXXF5Uq4H27goC%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAOYs_RwIB67iDC_ORPmBpp-oED4Ric3oYkID4kdkMdGs%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jpatHRdLkjwNmbWBc-VWcg%3D%3D%22%7D) and uploading your theme file. ## How to contribute a theme -1. Create your theme in the SimpleX Chat app. +### GitHub PR - easiest for us +Look for other theme files in repository +create PR through github + +### Send patch by email +[How to](https://git-send-email.io/) + +### Manual +1. Create your theme in the SimpleX Chat app. 2. Export your theme to file and give it a descriptive name in the following format – e.g., `SxC_themeName.theme`. If your theme name has a space, use an underscore (_) — e.g., `SxC_theme_name.theme` -3. Join the [SimpleX Themes group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALR%40smp5.simplex.im%2FjwFqICow91mcVNxBF2GXXF5Uq4H27goC%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAOYs_RwIB67iDC_ORPmBpp-oED4Ric3oYkID4kdkMdGs%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jpatHRdLkjwNmbWBc-VWcg%3D%3D%22%7D) +3. Join the [SimpleX Themes group](https://simplex.chat/contact#/?v=2-7&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FjwFqICow91mcVNxBF2GXXF5Uq4H27goC%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAOYs_RwIB67iDC_ORPmBpp-oED4Ric3oYkID4kdkMdGs%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22jpatHRdLkjwNmbWBc-VWcg%3D%3D%22%7D) 4. Upload your theme file to the group. -Your theme file will be transferred to the Theme Archive and made available for download. +Your theme file will be transferred to the Theme Archive and made available for download. ## Download Themes @@ -17,34 +29,32 @@ Click a theme name to view screenshots and download: | | | | |:---------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------:| -|





































































































































































The SimpleX Theme Archive is an independent, community project not affiliated with SimpleX Chat.
SimpleX Chat, SxC, and the SimpleX Chat logo are a trademark of SimpleX Chat, Ltd.
-The archive team can be contacted in the SimpleX Themes group, or at themes@slcw.unbox.at.
+The archive team can be contacted in the SimpleX Themes group, or at zenobit@duck.com.
SimpleX Theme Archive v1.0