diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..41eae31 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + pull_request: + branches: [ main, master ] + push: + branches: [ main, master ] + +jobs: + validate-and-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Validate YAML files + run: ruby scripts/validate_yaml.rb + + - name: Validate posts + run: bash scripts/validate_posts.sh + + - name: Build Jekyll site + run: bundle exec jekyll build + env: + JEKYLL_ENV: production + + - name: Test with html-proofer + run: | + bundle exec htmlproofer ./_site \ + --disable-external \ + --ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/" \ + --allow-hash-href + diff --git a/.gitignore b/.gitignore index 9f3684f..a649d97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,30 @@ +# Include your project-specific ignores in this file +# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files +# Useful .gitignore templates: https://github.com/github/gitignore + +# Node.js +node_modules +dist +.cache + +# Jekyll _site/ +.sass-cache/ +.jekyll-cache/ +.jekyll-metadata + +# Ruby +.bundle/ +vendor/bundle/ +*.gem + +# OS .DS_Store -Gemfile.lock +Thumbs.db + +# Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..0c6173b --- /dev/null +++ b/.ruby-version @@ -0,0 +1,2 @@ +3.1.0 + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..137baba --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,212 @@ +# Contributing to Deep-MI Website + +Thank you for contributing to the Deep-MI lab website! This guide will help you add content correctly. + +## Table of Contents + +- [Creating News Posts](#creating-news-posts) +- [Adding Members](#adding-members) +- [Adding Publications](#adding-publications) +- [Validation Before Committing](#validation-before-committing) +- [Local Development](#local-development) + +## Creating News Posts + +1. **Create a new file** in `_posts/` directory +2. **Name it**: `YYYY-MM-DD-Short-Title.md` (e.g., `2026-02-13-New-Grant.md`) +3. **Use the template** from `_templates/post-template.md` + +### Required Front Matter + +```yaml +--- +title: Your Article Title +author: First Lastname +layout: post +group: news +--- +``` + +### Tips + +- Use Markdown for formatting +- Include images in `/static/img/` directory +- Keep image file sizes reasonable (< 500KB) + +## Adding Members + +1. **Add member info** to `_data/members.yml` +2. **Prepare photo**: 365×365 pixels, 72 DPI, JPEG format +3. **Save photo** to `/static/img/members/lastname.jpg` +4. **Use the template** from `_templates/member-template.md` + +### Required Fields + +- `name`: Full name +- `image`: Path to photo (e.g., `/static/img/members/lastname.jpg`) +- `position`: Job title + +### Optional Fields + +- `email`: Email address +- `scholar`: Google Scholar ID +- `twitter`: Twitter handle +- `orcid`: ORCID identifier +- `github`: GitHub username +- `description`: Biography (use single quotes for multi-line) + +## Adding Publications + +1. **Add publication info** to `_data/publications.yml` +2. **Prepare teaser image**: Max 200px height, JPEG, max 100KB +3. **Save files**: + - Image: `/static/pub/paper_id.jpg` + - BibTeX: `/static/pub/paper_id.bib` + - PDF (optional): `/static/pub/paper_id.pdf` +4. **Use the template** from `_templates/publication-template.md` + +### Required Fields + +- `id`: Unique identifier (usually `lastname_year`) +- `authors`: Author list (bold lab members: `**Name**`) +- `title`: Paper title +- `journal`: Journal/conference name +- `type`: One of: journal, conference, workshop, abstract, poster, preprint +- `year`: Publication year + +### Recommended Fields + +- `doi`: Digital Object Identifier +- `image`: Teaser image path +- `bibtex`: BibTeX file path +- `pdf`: Link to open access PDF or local file + +## Validation Before Committing + +Always validate your changes before committing: + +```bash +# Validate YAML files +./scripts/validate_yaml.rb + +# Validate post filenames and format +./scripts/validate_posts.sh + +# Test the site locally +bundle exec jekyll serve +``` + +Visit http://localhost:4000 to preview your changes. + +## Automation Tools + +### Image Optimization + +Optimize images to meet size requirements: + +```bash +# Optimize a single member photo (365x365, 72 DPI) +./scripts/optimize_images.sh member static/img/members/lastname.jpg + +# Optimize a single publication teaser (max 200px height, max 100KB) +./scripts/optimize_images.sh pub static/pub/paper_id.jpg + +# Optimize all member photos at once +./scripts/optimize_images.sh all-members + +# Optimize all publication teasers at once +./scripts/optimize_images.sh all-pubs +``` + +**Requirements**: ImageMagick + +**Installation**: +- macOS: `brew install imagemagick` +- Ubuntu/Debian: `sudo apt-get install imagemagick` +- Fedora/RHEL: `sudo yum install imagemagick` or `sudo dnf install imagemagick` +- Arch Linux: `sudo pacman -S imagemagick` +- Windows: Download from [imagemagick.org](https://imagemagick.org/script/download.php#windows) or use `choco install imagemagick` + +### Pre-commit Hook + +Install a pre-commit hook to automatically validate changes: + +```bash +cp scripts/pre-commit.sh .git/hooks/pre-commit +chmod +x .git/hooks/pre-commit +``` + +This will automatically run validation scripts before each commit. + +## Local Development + +### First-Time Setup + +```bash +# Install bundler (without sudo) +gem install bundler --user-install + +# Install dependencies +bundle install +``` + +### Running Locally + +```bash +# Start development server +bundle exec jekyll serve + +# Site available at: http://localhost:4000 +``` + +### Testing + +```bash +# Build site +bundle exec jekyll build + +# Check for broken links +bundle exec htmlproofer ./_site --disable-external +``` + +## Style Guidelines + +### Writing + +- Use clear, concise language +- Check spelling and grammar +- Use proper citations for published work + +### Images + +- Optimize images before uploading +- Use descriptive filenames +- Keep file sizes small: + - Member photos: max 100KB + - Publication teasers: max 100KB + - Post images: max 500KB + +### Code + +- Follow Jekyll conventions +- Keep YAML files properly indented (2 spaces) +- Use single quotes for strings in YAML +- Escape single quotes with double single quotes: `Alzheimer''s` + +## Questions? + +If you have questions or run into issues, please: +1. Check the templates in `_templates/` +2. Review existing examples in the data files +3. Contact the lab's web administrator + +## Continuous Integration + +All pull requests are automatically tested using GitHub Actions: +- Jekyll build verification +- HTML validation +- Link checking + +Make sure your changes pass these tests before requesting review. + + diff --git a/Gemfile b/Gemfile index 62d9e3b..8e3aa4e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,12 @@ source 'https://rubygems.org' -gem "jekyll" + +# Specify Ruby version for consistency +ruby '>= 2.7.0' + +# GitHub Pages gem (includes Jekyll and plugins used by GitHub Pages) gem 'github-pages' -gem 'jekyll-paginate' +# HTML validation and link checking for CI/CD gem "html-proofer" + +# Required for Ruby 3.x to run Jekyll server gem "webrick" diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..9fb316c --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,144 @@ +# Deep-MI Website - Quick Reference + +## Quick Start + +```bash +# First time setup +gem install bundler --user-install +bundle install + +# Run locally +bundle exec jekyll serve +# Visit: http://localhost:4000 + +# Validate before committing +./scripts/validate_yaml.rb +./scripts/validate_posts.sh +``` + +## Adding Content + +### News Post (5 minutes) + +1. Create: `_posts/YYYY-MM-DD-Title.md` +2. Copy template from: `_templates/post-template.md` +3. Add images to: `/static/img/` +4. Validate: `./scripts/validate_posts.sh` + +### Team Member (10 minutes) + +1. Prepare photo: 365×365px, JPG +2. Optimize: `./scripts/optimize_images.sh member photo.jpg` +3. Save to: `/static/img/members/lastname.jpg` +4. Add entry to: `_data/members.yml` (see `_templates/member-template.md`) +5. Validate: `./scripts/validate_yaml.rb` + +### Publication (15 minutes) + +1. Prepare teaser: Max 200px height, JPG, max 100KB +2. Optimize: `./scripts/optimize_images.sh pub image.jpg` +3. Save files: + - Image: `/static/pub/id.jpg` + - BibTeX: `/static/pub/id.bib` + - PDF (optional): `/static/pub/id.pdf` +4. Add entry to: `_data/publications.yml` (see `_templates/publication-template.md`) +5. Validate: `./scripts/validate_yaml.rb` + +## File Locations + +| Content Type | Data File | Assets | Template | +|--------------|-----------|--------|----------| +| News Posts | `_posts/*.md` | `/static/img/` | `_templates/post-template.md` | +| Members | `_data/members.yml` | `/static/img/members/` | `_templates/member-template.md` | +| Publications | `_data/publications.yml` | `/static/pub/` | `_templates/publication-template.md` | +| Alumni | `_data/alumni.yml` | - | - | +| Visitors | `_data/visitors.yml` | - | - | + +## Image Requirements + +| Type | Size | Format | Max Size | +|------|------|--------|----------| +| Member Photo | 365×365px, 72 DPI | JPG | 100KB | +| Publication Teaser | Max 200px height | JPG | 100KB | +| Post Images | Reasonable | JPG/PNG | 500KB | + +## Validation Scripts + +```bash +# YAML data files (members, publications, etc.) +./scripts/validate_yaml.rb + +# Post filename format (YYYY-MM-DD-title.md) +./scripts/validate_posts.sh + +# Image optimization +./scripts/optimize_images.sh member +./scripts/optimize_images.sh pub +``` + +## Common Issues + +### Jekyll won't start +```bash +bundle install +bundle update +``` + +### Ruby version error +```bash +# Install correct Ruby version +rbenv install 3.1.0 +rbenv local 3.1.0 +``` + +### YAML validation fails +- Check indentation (2 spaces) +- Use single quotes for strings +- Escape quotes: `Alzheimer''s` (two single quotes) +- Required fields must be present + +### Images too large +```bash +# Optimize automatically (requires ImageMagick) +./scripts/optimize_images.sh member static/img/members/photo.jpg +./scripts/optimize_images.sh pub static/pub/teaser.jpg +``` + +**Install ImageMagick**: +- macOS: `brew install imagemagick` +- Linux: `sudo apt-get install imagemagick` or `sudo yum install imagemagick` +- Windows: [imagemagick.org](https://imagemagick.org/script/download.php#windows) + +## Useful Commands + +```bash +# Full build and test +bundle exec jekyll build +bundle exec htmlproofer ./_site --disable-external + +# Clean build +bundle exec jekyll clean +bundle exec jekyll build + +# Update dependencies +bundle update + +# Install pre-commit hook +cp scripts/pre-commit.sh .git/hooks/pre-commit +chmod +x .git/hooks/pre-commit +``` + +## Resources + +- [Jekyll Documentation](https://jekyllrb.com/docs/) +- [Markdown Guide](https://www.markdownguide.org/) +- [YAML Syntax](https://yaml.org/) +- [Bootstrap 4.4 Docs](https://getbootstrap.com/docs/4.4/) + +## Support + +- Check `CONTRIBUTING.md` for detailed guidelines +- Review templates in `_templates/` +- See existing examples in data files +- Contact lab web administrator + diff --git a/README.md b/README.md index 4e54d2d..6a77f73 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,14 @@ group: news Your text goes here, with images, links etc.... ``` +See `_templates/post-template.md` for a complete template. + ## Add Members - add member info to _data/members.yml (see file for format) - put photos (size 365 x 365, 72 dpi) into /static/img/members (jpg compressed) +See `_templates/member-template.md` for a complete template. ## Add Publication @@ -30,21 +33,96 @@ Your text goes here, with images, links etc.... - link to openaccess PDF (journal) or put at /static/pub/paper_id.pdf - paper_id is the id field in the yml +See `_templates/publication-template.md` for a complete template. + +## Validation + +Before committing changes, validate your content: + +```bash +# Validate YAML data files +./scripts/validate_yaml.rb + +# Validate post filenames and format +./scripts/validate_posts.sh +``` + +See `CONTRIBUTING.md` for detailed contribution guidelines. + +## Tools and Scripts + +### Validation + +```bash +# Validate YAML files (checks structure, required fields, file existence) +./scripts/validate_yaml.rb + +# Validate post filenames and format +./scripts/validate_posts.sh +``` + +### Image Optimization + +```bash +# Optimize member photos and publication teasers +./scripts/optimize_images.sh member static/img/members/lastname.jpg +./scripts/optimize_images.sh pub static/pub/paper_id.jpg +``` + +**Requirements**: ImageMagick +- macOS: `brew install imagemagick` +- Linux: `sudo apt-get install imagemagick` or `sudo yum install imagemagick` +- Windows: See [imagemagick.org](https://imagemagick.org/script/download.php#windows) + +### Pre-commit Hook + +```bash +# Install pre-commit hook for automatic validation +cp scripts/pre-commit.sh .git/hooks/pre-commit +chmod +x .git/hooks/pre-commit +``` + ## Sources This website was built using the Fraser Lab [website](http://fraserlab.com/) as a template. James Fraser's website is open-source and available on [Github](https://github.com/fraser-lab/fraser-lab.github.io) -Technologies this website uses: +Technologies this website uses: - Jekyll - Github Pages + Jekyll + Github Pages Twitter Bootstrap 4.4.1 -Before pushing changes, please check that they will work on your system first with the plugins included in the Gemfile using the bundler tool (results served at [0.0.0.0:4000](0.0.0.0:4000)): +## Local Development + +Before pushing changes, please check that they will work on your system first with the plugins included in the Gemfile using the bundler tool. + +### Setup (First Time) + +```bash +# Install bundler (without sudo for security) +gem install bundler --user-install + +# Install dependencies +bundle install +``` + +### Running the Site Locally + +```bash +# Start the Jekyll server +bundle exec jekyll serve + +# Site will be available at: http://localhost:4000 +# Or: http://0.0.0.0:4000 +``` + +### Testing + +```bash +# Check for broken links and HTML issues +bundle exec jekyll build +bundle exec htmlproofer ./_site --disable-external +``` - sudo gem install bundler - bundle install - bundle exec jekyll serve - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5e80e85 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,91 @@ +# Security Policy + +## Supported Versions + +This website uses Jekyll and GitHub Pages. We recommend always using the latest versions: + +| Component | Version | Supported | +| --------- | ------- | --------- | +| Jekyll | Latest | ✅ | +| Ruby | 3.x | ✅ | +| Ruby | 2.7.x | ✅ | +| Ruby | < 2.7 | ❌ | + +## Security Best Practices + +### Dependencies + +- Always run `bundle update` regularly to get security patches +- Review GitHub Dependabot alerts when they appear +- Never commit `Gemfile.lock` with known vulnerabilities + +### Content Security + +- Never commit sensitive information (API keys, passwords, etc.) +- Review all external links before publishing +- Validate user-contributed content +- Optimize and scan images before uploading + +### Access Control + +- Limit write access to the repository +- Use branch protection rules for main/master branch +- Require pull request reviews for sensitive changes +- Enable 2FA for all contributors + +## Reporting a Vulnerability + +If you discover a security vulnerability in this website, please report it by: + +1. **Email**: Contact the lab administrators directly (do not open a public issue) +2. **Expected Response**: Within 48 hours +3. **Updates**: We'll keep you informed of the progress + +### What to Include + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if applicable) + +## Security Measures + +### Automated Checks + +- GitHub Actions CI runs on all pull requests +- HTML validation and link checking +- YAML syntax validation +- Post format validation + +### Manual Reviews + +- All content changes reviewed before merging +- Regular audits of dependencies +- Periodic security assessments + +## Known Limitations + +### GitHub Pages + +This site is hosted on GitHub Pages, which has certain security constraints: + +- No server-side code execution +- Static content only +- HTTPS enforced automatically +- Limited control over server headers + +### Third-Party Content + +We include content from: +- Google Scholar (user profiles) +- Twitter (social media links) +- External publication PDFs + +Users should be aware these are external resources not under our direct control. + +## Updates + +This security policy is reviewed and updated quarterly. + +Last updated: February 2026 + diff --git a/_data/publications.yml b/_data/publications.yml index 1d6d041..91ebf11 100644 --- a/_data/publications.yml +++ b/_data/publications.yml @@ -24,7 +24,7 @@ # arxiv: 1904.02082 # doi: 10.1002/mrm.28022 # pdf: '/static/pub/lastname_year_ifnecessarymore.pdf' -# github: /deep-mi/project +# github: deep-mi/project # bibtex: '/static/pub/lastname_year_ifnecessarymore.bib' # links: # - name: 'Some coauthor Lab' @@ -42,7 +42,7 @@ arxiv: 'https://arxiv.org/abs/2505.02048' doi: '10.1109/TMI.2025.3650412' pdf: 'https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=11322583' - github: 'deep-MI/YODA' + github: 'deep-mi/YODA' - id: 'pollak_2025' authors: '**Pollak C**, **Kügler D**, Bauer T, Rüber T, **Reuter M**' @@ -54,7 +54,7 @@ bibtex: '/static/pub/pollak_2025.bib' doi: 10.1162/imag_a_00446 pmcid: 'PMC11917724' - github: 'deep-MI/LIT' + github: 'deep-mi/LIT' - id: 'peng_2025' authors: 'Xu P, **Estrada S**, Etteldorf R, Liu D, Shahid M, Zeng W, Früh D, **Reuter M**, Breteler MMB, Aziz NA' @@ -89,7 +89,7 @@ pdf: 'https://direct.mit.edu/imag/article-pdf/doi/10.1162/imag_a_00180/2375595/imag_a_00180.pdf' bibtex: '/static/pub/henschel_2024.bib' doi: 10.1162/imag_a_00180 - github: /deep-mi/VINNA4neonates + github: deep-mi/VINNA4neonates - id: 'ewert_2024a' authors: '**Ewert C\***, **Kügler D\***, Stirnberg R, Koch A, Yendiki A, **Reuter M**. (\*co-first)' @@ -102,8 +102,8 @@ bibtex: '/static/pub/ewert_2024a.bib' doi: 10.1162/imag_a_00121 pdf: 'https://direct.mit.edu/imag/article-pdf/doi/10.1162/imag_a_00121/2358010/imag_a_00121.pdf' - github: /deep-mi/DISCUS - + github: deep-mi/DISCUS + - id: 'estrada2023' authors: '**Estrada S**, **Kügler D**, Bahrami E, Xu P, Mousa D, Breteler MMB, Aziz NA, **Reuter M**' title: 'FastSurfer-HypVINN: Automated sub-segmentation of the hypothalamus and adjacent structures on high-resolutional brain MRI' @@ -112,11 +112,11 @@ image: '/static/pub/estrada_2023.jpg' year: 2023 bibtex: '/static/pub/estrada_2023.bib' - arxiv: 2308.12736 + arxiv: 2308.12736 pmcid: 'PMC11576934' doi: 10.1162/imag_a_00034 pdf: 'https://direct.mit.edu/imag/article-pdf/doi/10.1162/imag_a_00034/2182290/imag_a_00034.pdf' - github: /deep-mi/FastSurfer + github: deep-mi/FastSurfer - id: 'diers_2023' authors: '**Diers K**, Baumeister H, Jessen F, Düzel E, Berron D, **Reuter M**' @@ -128,8 +128,8 @@ bibtex: '/static/pub/diers_2023.bib' doi: 10.1016/j.neuroimage.2023.120182 pdf: 'https://doi.org/10.1016/j.neuroimage.2023.120182' - github: 'deep-MI/Hipsta' - + github: 'deep-mi/Hipsta' + - id: 'pollak_2023b' authors: '**Pollak C**, **Kügler D**, Breteler MMB, **Reuter M**' title: 'Quantifying MR head motion in the Rhineland Study – A robust method for population cohorts' @@ -140,7 +140,7 @@ bibtex: '/static/pub/pollak_2023b.bib' doi: 10.1016/j.neuroimage.2023.120176 pdf: 'https://www.sciencedirect.com/science/article/pii/S1053811923003270/pdfft?isDTMRedir=true&download=true' - github: 'deep-MI/head-motion-tools' + github: 'deep-mi/head-motion-tools' - id: 'pollak_2023a' @@ -162,7 +162,7 @@ journal: '*NeuroImage*' type: 'journal' year: 2022 - github: /deep-mi/FastSurfer + github: deep-mi/FastSurfer doi: 10.1016/j.neuroimage.2022.119703 pmcid: 'PMC9771831' image: '/static/pub/faber_2022.jpg' @@ -187,7 +187,7 @@ journal: '*Medical Imaging with Deep Learning, Conference (MIDL)*' type: 'journal' year: 2022 - github: /deep-mi/3d-neuro-seg + github: deep-mi/3d-neuro-seg image: '/static/pub/roy_2022.png' pdf: 'https://openreview.net/pdf?id=Ob62JPB_CDF' bibtex: '/static/pub/roy_2022.bib' @@ -203,7 +203,7 @@ arxiv: 2112.09654 pmcid: 'PMC9801435' image: '/static/pub/henschel_2022.jpg' - github: /deep-mi/FastSurfer + github: deep-mi/FastSurfer doi: 10.1016/j.neuroimage.2022.118933 pdf: 'https://www.sciencedirect.com/science/article/pii/S1053811922000623/pdfft?md5=0522f6a35232c9e9e57674816ce19a7a&pid=1-s2.0-S1053811922000623-main.pdf' bibtex: '/static/pub/henschel_2022.bib' @@ -277,7 +277,7 @@ doi: 10.1002/hbm.25377 pdf: 'https://onlinelibrary.wiley.com/doi/epdf/10.1002/hbm.25377' bibtex: '/static/pub/lu_2021.bib' - + - id: 'henschel_2020b' authors: '**Henschel L**, **Reuter M**' title: 'Parameter Space CNN for Cortical Surface Segmentation' @@ -605,6 +605,7 @@ pages: '11 - 22' volume: 127 journal: '*NeuroImage*' + type: 'journal' pdf: 'https://www.ncbi.nlm.nih.gov/pmc/articles/pmid/26654788/' doi: 10.1016/j.neuroimage.2015.11.054 pmcid: PMC4754677 diff --git a/_templates/member-template.md b/_templates/member-template.md new file mode 100644 index 0000000..715ee3a --- /dev/null +++ b/_templates/member-template.md @@ -0,0 +1,32 @@ +# Member Template + +Copy this template when adding a new member to `_data/members.yml` + +```yaml +- name: Full Name + image: /static/img/members/lastname.jpg + position: Job Title (e.g., PhD Student, Postdoc, Research Assistant) + email: name.surname (at) institution.edu + scholar: GoogleScholarID # optional + twitter: twitterhandle # optional + orcid: 0000-0000-0000-0000 # optional + github: githubusername # optional + description: 'Brief biography and research interests. + + + Can span multiple paragraphs using blank lines between them.' +``` + +## Requirements + +1. **Photo**: 365×365 pixels, 72 DPI, JPEG format (compressed) +2. **Location**: `/static/img/members/lastname.jpg` +3. **Required fields**: name, image, position +4. **Optional fields**: email, scholar, twitter, orcid, github, description + +## Tips + +- Use single quotes for multi-line descriptions +- Keep file size to max 100KB (optimization script helps with this) +- Use consistent naming: lastname.jpg + diff --git a/_templates/post-template.md b/_templates/post-template.md new file mode 100644 index 0000000..33a3538 --- /dev/null +++ b/_templates/post-template.md @@ -0,0 +1,41 @@ +--- +title: Your Article Title Here +author: First Lastname +layout: post +group: news +--- + +Write your news post content here. You can use Markdown formatting: + +## Subheadings + +Use **bold** and *italic* text for emphasis. + +### Lists + +- Bullet point 1 +- Bullet point 2 +- Bullet point 3 + +### Links and Images + +[Link text](https://example.com) + +![Alt text](/static/img/your-image.jpg) + +### Code + +Inline `code` or code blocks: + +```python +def hello_world(): + print("Hello, Deep-MI!") +``` + +--- + +Remember: +- Filename must be: YYYY-MM-DD-Short-Title.md +- Place in: _posts/ +- Required front matter fields: title, author, layout, group + diff --git a/_templates/publication-template.md b/_templates/publication-template.md new file mode 100644 index 0000000..57da04b --- /dev/null +++ b/_templates/publication-template.md @@ -0,0 +1,57 @@ +# Publication Template + +Copy this template when adding a new publication to `_data/publications.yml` + +```yaml +- id: 'lastname_year' + authors: '**Labmember A**, Someone B, **Labmember C**, Lastauthor D' + title: 'The title of this paper (use two single quotes to escape: Alzheimer''s)' + journal: 'Journal Name or *under review*' + type: 'journal' # journal, conference, workshop, abstract, poster, preprint + year: 2026 + month: 2 # optional + pages: '123-145' # optional + volume: '42' # optional + number: '3' # optional + image: '/static/pub/lastname_year.jpg' + pmcid: 'PMC1234567' # optional - PubMed Central ID + pmid: '12345678' # optional - PubMed ID + biorxiv: '2024.01.01.123456' # optional - bioRxiv ID + arxiv: '2401.12345' # optional - arXiv ID + doi: '10.1234/journal.2024.123456' # strongly recommended + pdf: 'https://journal.com/paper.pdf' # or '/static/pub/lastname_year.pdf' + github: 'deep-mi/repository-name' # optional - format: org/repo (no leading slash) + bibtex: '/static/pub/lastname_year.bib' + links: # optional - additional links + - name: 'Project Website' + url: 'https://project.example.com' + - name: 'Dataset' + url: 'https://data.example.com' +``` + +## Requirements + +1. **Teaser Image**: Max 200px height, JPEG format, max 100KB + - Location: `/static/pub/paper_id.jpg` +2. **BibTeX file**: `/static/pub/paper_id.bib` +3. **PDF**: Link to open access or local file at `/static/pub/paper_id.pdf` +4. **paper_id**: Should match the `id` field + +## Required Fields +- id (unique identifier) +- authors (bold lab members with `**name**`) +- title +- journal +- type +- year + +## Tips + +- Use single quotes for strings with special characters +- Always include DOI if available +- Bold current and former lab members in author list +- Keep image file size small (max 100KB) +- GitHub format: `'deep-mi/repository-name'` (lowercase org, no leading slash) + - Rendered as: `https://github.com/deep-mi/repository-name` +- Test that all file paths exist before committing + diff --git a/favicon.ico b/favicon.ico index c425789..be74abd 100644 Binary files a/favicon.ico and b/favicon.ico differ diff --git a/scripts/optimize_images.sh b/scripts/optimize_images.sh new file mode 100755 index 0000000..54782b4 --- /dev/null +++ b/scripts/optimize_images.sh @@ -0,0 +1,332 @@ +#!/bin/bash +# Image optimization script for Deep-MI website +# Requires ImageMagick: brew install imagemagick + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Array to track temporary files for cleanup +TEMP_FILES=() + +# Array to track backup files +BACKUP_FILES=() + +# Cleanup function +cleanup() { + if [ ${#TEMP_FILES[@]} -gt 0 ]; then + for temp_file in "${TEMP_FILES[@]}"; do + if [ -f "$temp_file" ]; then + rm -f "$temp_file" + fi + done + fi +} + +# Set up trap to cleanup on exit or error +trap cleanup EXIT ERR INT TERM + +# Function to create backup +create_backup() { + local input_file="$1" + local backup_file="${input_file}.backup" + + if cp "$input_file" "$backup_file" 2>/dev/null; then + BACKUP_FILES+=("$backup_file") + return 0 + else + echo -e " ${RED}✗ Error: Failed to create backup${NC}" + return 1 + fi +} + +# Function to restore from backup +restore_backup() { + local input_file="$1" + local backup_file="${input_file}.backup" + + if [ -f "$backup_file" ]; then + mv "$backup_file" "$input_file" + # Remove from backup tracking + BACKUP_FILES=("${BACKUP_FILES[@]/$backup_file}") + return 0 + fi + return 1 +} + +# Function to remove backup +remove_backup() { + local input_file="$1" + local backup_file="${input_file}.backup" + + if [ -f "$backup_file" ]; then + rm -f "$backup_file" + # Remove from backup tracking + BACKUP_FILES=("${BACKUP_FILES[@]/$backup_file}") + fi +} + +# Check if ImageMagick is installed +if ! command -v magick &> /dev/null && ! command -v convert &> /dev/null; then + echo -e "${RED}Error: ImageMagick is not installed${NC}" + echo "" + echo "Please install ImageMagick for your platform:" + echo "" + echo " macOS:" + echo " brew install imagemagick" + echo "" + echo " Ubuntu/Debian Linux:" + echo " sudo apt-get update" + echo " sudo apt-get install imagemagick" + echo "" + echo " Fedora/RHEL/CentOS Linux:" + echo " sudo yum install imagemagick" + echo " # or on newer systems:" + echo " sudo dnf install imagemagick" + echo "" + echo " Arch Linux:" + echo " sudo pacman -S imagemagick" + echo "" + echo " Windows:" + echo " Download from: https://imagemagick.org/script/download.php#windows" + echo " Or use Chocolatey: choco install imagemagick" + echo " Or use Scoop: scoop install imagemagick" + echo "" + exit 1 +fi + +# Determine the correct command (magick or convert) +if command -v magick &> /dev/null; then + CONVERT_CMD="magick" +else + CONVERT_CMD="convert" +fi + +echo "Image Optimization Script" +echo "==========================" +echo "" + +# Function to optimize member photos +optimize_member_photo() { + local input_file="$1" + local filename=$(basename "$input_file") + local temp_file="${input_file}.tmp.jpg" + + # Add to cleanup list + TEMP_FILES+=("$temp_file") + + echo -e "${YELLOW}Processing member photo: $filename${NC}" + + # Check if input file exists and is readable + if [ ! -f "$input_file" ]; then + echo -e " ${RED}✗ Error: File not found: $input_file${NC}" + return 1 + fi + + if [ ! -r "$input_file" ]; then + echo -e " ${RED}✗ Error: File not readable: $input_file${NC}" + return 1 + fi + + # Create backup before processing + if ! create_backup "$input_file"; then + return 1 + fi + + # Resize to 365x365 if needed, set to 72 DPI, and compress + if ! $CONVERT_CMD "$input_file" \ + -resize 365x365^ \ + -gravity center \ + -extent 365x365 \ + -density 72 \ + -quality 85 \ + -strip \ + "$temp_file" 2>/dev/null; then + echo -e " ${RED}✗ Error: Failed to process image (possibly corrupt or invalid format)${NC}" + rm -f "$temp_file" + restore_backup "$input_file" + return 1 + fi + + # Check file size + size=$(wc -c < "$temp_file" | tr -d ' ') + size_kb=$((size / 1024)) + + if [ $size_kb -gt 100 ]; then + echo -e " ${YELLOW}Warning: ${filename} is ${size_kb}KB (max 100KB)${NC}" + echo -e " Attempting further compression..." + if ! $CONVERT_CMD "$temp_file" -quality 75 "$temp_file" 2>/dev/null; then + echo -e " ${RED}✗ Error: Failed to compress image${NC}" + rm -f "$temp_file" + restore_backup "$input_file" + return 1 + fi + size=$(wc -c < "$temp_file" | tr -d ' ') + size_kb=$((size / 1024)) + fi + + mv "$temp_file" "$input_file" + # Remove from cleanup list after successful move + TEMP_FILES=("${TEMP_FILES[@]/$temp_file}") + # Remove backup after successful optimization + remove_backup "$input_file" + echo -e " ${GREEN}✓ Optimized: ${size_kb}KB (backup removed)${NC}" + return 0 +} + +# Function to optimize publication teasers +optimize_pub_teaser() { + local input_file="$1" + local filename=$(basename "$input_file") + local temp_file="${input_file}.tmp.jpg" + + # Add to cleanup list + TEMP_FILES+=("$temp_file") + + echo -e "${YELLOW}Processing publication teaser: $filename${NC}" + + # Check if input file exists and is readable + if [ ! -f "$input_file" ]; then + echo -e " ${RED}✗ Error: File not found: $input_file${NC}" + return 1 + fi + + if [ ! -r "$input_file" ]; then + echo -e " ${RED}✗ Error: File not readable: $input_file${NC}" + return 1 + fi + + # Create backup before processing + if ! create_backup "$input_file"; then + return 1 + fi + + # Resize to max height 200px and compress + if ! $CONVERT_CMD "$input_file" \ + -resize x200\> \ + -quality 85 \ + -strip \ + "$temp_file" 2>/dev/null; then + echo -e " ${RED}✗ Error: Failed to process image (possibly corrupt or invalid format)${NC}" + rm -f "$temp_file" + restore_backup "$input_file" + return 1 + fi + + # Check file size + size=$(wc -c < "$temp_file" | tr -d ' ') + size_kb=$((size / 1024)) + + if [ $size_kb -gt 100 ]; then + echo -e " ${YELLOW}Warning: ${filename} is ${size_kb}KB (max 100KB)${NC}" + echo -e " Applying maximum compression..." + if ! $CONVERT_CMD "$temp_file" -quality 70 "$temp_file" 2>/dev/null; then + echo -e " ${RED}✗ Error: Failed to compress image${NC}" + rm -f "$temp_file" + restore_backup "$input_file" + return 1 + fi + size=$(wc -c < "$temp_file" | tr -d ' ') + size_kb=$((size / 1024)) + fi + + mv "$temp_file" "$input_file" + # Remove from cleanup list after successful move + TEMP_FILES=("${TEMP_FILES[@]/$temp_file}") + # Remove backup after successful optimization + remove_backup "$input_file" + echo -e " ${GREEN}✓ Optimized: ${size_kb}KB (backup removed)${NC}" + return 0 +} + +# Main script +case "$1" in + member) + if [ -z "$2" ]; then + echo "Usage: $0 member " + echo "Example: $0 member static/img/members/doe.jpg" + exit 1 + fi + optimize_member_photo "$2" + ;; + + pub) + if [ -z "$2" ]; then + echo "Usage: $0 pub " + echo "Example: $0 pub static/pub/doe_2026.jpg" + exit 1 + fi + optimize_pub_teaser "$2" + ;; + + all-members) + echo "Optimizing all member photos..." + echo "" + success_count=0 + error_count=0 + for img in static/img/members/*.jpg; do + if [ -f "$img" ]; then + if optimize_member_photo "$img"; then + ((success_count++)) + else + ((error_count++)) + fi + fi + done + echo "" + if [ $error_count -eq 0 ]; then + echo -e "${GREEN}✓ All member photos optimized! ($success_count processed)${NC}" + else + echo -e "${YELLOW}⚠ Completed with errors: $success_count succeeded, $error_count failed${NC}" + exit 1 + fi + ;; + + all-pubs) + echo "Optimizing all publication teasers..." + echo "" + success_count=0 + error_count=0 + for img in static/pub/*.jpg; do + if [ -f "$img" ]; then + if optimize_pub_teaser "$img"; then + ((success_count++)) + else + ((error_count++)) + fi + fi + done + echo "" + if [ $error_count -eq 0 ]; then + echo -e "${GREEN}✓ All publication teasers optimized! ($success_count processed)${NC}" + else + echo -e "${YELLOW}⚠ Completed with errors: $success_count succeeded, $error_count failed${NC}" + if [ ${#BACKUP_FILES[@]} -gt 0 ]; then + echo -e "${YELLOW}⚠ ${#BACKUP_FILES[@]} backup file(s) retained due to errors${NC}" + echo -e " To restore, run: mv .backup " + fi + exit 1 + fi + ;; + + *) + echo "Image Optimization Script" + echo "" + echo "Usage:" + echo " $0 member Optimize a member photo (365x365)" + echo " $0 pub Optimize a publication teaser (max 200px height)" + echo " $0 all-members Optimize all member photos" + echo " $0 all-pubs Optimize all publication teasers" + echo "" + echo "Examples:" + echo " $0 member static/img/members/doe.jpg" + echo " $0 pub static/pub/doe_2026.jpg" + echo " $0 all-members" + exit 1 + ;; +esac + diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh new file mode 100755 index 0000000..0f2b639 --- /dev/null +++ b/scripts/pre-commit.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Pre-commit hook for Deep-MI website +# Install: cp scripts/pre-commit.sh .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit + +set -e + +echo "Running pre-commit checks..." + +# Check if validation scripts exist +if [ ! -f "scripts/validate_yaml.rb" ] || [ ! -f "scripts/validate_posts.sh" ]; then + echo "⚠️ Validation scripts not found. Skipping validation." + exit 0 +fi + +# Validate YAML files +echo "→ Validating YAML files..." +if ! ./scripts/validate_yaml.rb; then + echo "" + echo "✗ YAML validation failed!" + echo " Fix the errors above before committing." + exit 1 +fi + +# Validate posts +echo "" +echo "→ Validating posts..." +if ! ./scripts/validate_posts.sh; then + echo "" + echo "✗ Post validation failed!" + echo " Fix the errors above before committing." + exit 1 +fi + +# Check for large files +echo "" +echo "→ Checking for large files..." +large_files=$(git diff --cached --name-only --diff-filter=ACM | while read file; do + if [ -f "$file" ]; then + size=$(wc -c < "$file") + if [ $size -gt 1048576 ]; then # 1MB + echo "$file ($(($size / 1024))KB)" + fi + fi +done) + +if [ -n "$large_files" ]; then + echo "⚠️ Warning: Large files detected:" + echo "$large_files" + echo "" + echo "Consider optimizing images before committing." + # Only prompt if running interactively and not explicitly skipped + if [ -t 0 ] && [ -z "$SKIP_LARGE_FILE_PROMPT" ]; then + echo "Press Enter to continue anyway, or Ctrl+C to abort." + read + fi +fi + +echo "" +echo "✓ All pre-commit checks passed!" +exit 0 diff --git a/scripts/validate_posts.sh b/scripts/validate_posts.sh new file mode 100755 index 0000000..006ce8d --- /dev/null +++ b/scripts/validate_posts.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# Validate that post filenames follow the YYYY-MM-DD-title.md convention + +set -e + +echo "Validating post filenames..." + +error_count=0 + +for file in _posts/*.md; do + if [ ! -f "$file" ]; then + continue + fi + + filename=$(basename "$file") + + # Check naming convention: YYYY-MM-DD-*.md + if [[ ! $filename =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}-.+\.md$ ]]; then + echo "✗ Invalid post filename: $filename" + echo " Expected format: YYYY-MM-DD-title.md" + ((error_count++)) + continue + fi + + # Extract and validate date + # Try GNU date (Linux) or BSD date (macOS) - either one succeeding is valid + date_part="${filename:0:10}" + if ! ( date -d "$date_part" "+%Y-%m-%d" >/dev/null 2>&1 || \ + date -j -f "%Y-%m-%d" "$date_part" >/dev/null 2>&1 ); then + echo "✗ Invalid date in filename: $filename" + ((error_count++)) + continue + fi + + # Check for front matter + if ! head -n 1 "$file" | grep -q "^---$"; then + echo "✗ Missing front matter in: $filename" + ((error_count++)) + continue + fi + + echo "✓ $filename" +done + +if [ $error_count -eq 0 ]; then + echo "" + echo "✓ All post files are valid!" + exit 0 +else + echo "" + echo "✗ Found $error_count error(s)" + exit 1 +fi + diff --git a/scripts/validate_yaml.rb b/scripts/validate_yaml.rb new file mode 100755 index 0000000..c505296 --- /dev/null +++ b/scripts/validate_yaml.rb @@ -0,0 +1,118 @@ +#!/usr/bin/env ruby +# Validate YAML files in _data directory + +require 'yaml' +require 'date' + +# Get the project root directory (parent of scripts directory) +PROJECT_ROOT = File.expand_path('..', __dir__) + +def validate_file(filepath) + puts "Validating #{filepath}..." + + begin + # Use safe_load_file for Ruby >= 3.1, fall back to load_file for older versions + data = if YAML.respond_to?(:safe_load_file) + YAML.safe_load_file(filepath, permitted_classes: [Date, Time], aliases: true) + else + YAML.load_file(filepath) + end + + # File-specific validations + case File.basename(filepath) + when 'members.yml' + validate_members(data, filepath) + when 'publications.yml' + validate_publications(data, filepath) + end + + puts "✓ #{filepath} is valid" + true + rescue => e + puts "✗ Error in #{filepath}: #{e.message}" + false + end +end + +def validate_members(data, filepath) + required_fields = ['name', 'image', 'position'] + + data.each_with_index do |member, idx| + required_fields.each do |field| + unless member[field] + raise "Member at index #{idx} missing required field: #{field}" + end + end + + # Check if image file exists (resolve relative to project root) + image_path = File.expand_path(member['image'], PROJECT_ROOT) + unless File.exist?(image_path) + puts " Warning: Image not found for #{member['name']}: #{member['image']}" + end + end +end + +def validate_publications(data, filepath) + required_fields = ['id', 'authors', 'title', 'journal', 'type', 'year'] + valid_types = ['journal', 'conference', 'workshop', 'abstract', 'poster', 'preprint'] + + ids_seen = {} + + data.each_with_index do |pub, idx| + # Check required fields + required_fields.each do |field| + unless pub[field] + raise "Publication at index #{idx} missing required field: #{field}" + end + end + + # Check for duplicate IDs + if ids_seen[pub['id']] + raise "Duplicate publication ID: #{pub['id']}" + end + ids_seen[pub['id']] = true + + # Validate type + unless valid_types.include?(pub['type']) + raise "Publication #{pub['id']} has invalid type: #{pub['type']}" + end + + # Validate year + year = pub['year'].to_i + unless year >= 1900 && year <= Date.today.year + 1 + raise "Publication #{pub['id']} has invalid year: #{pub['year']}" + end + + # Check if files exist (resolve relative to project root) + ['image', 'bibtex', 'pdf'].each do |file_field| + next unless pub[file_field] + next if pub[file_field].start_with?('http') + + file_path = File.expand_path(pub[file_field], PROJECT_ROOT) + unless File.exist?(file_path) + puts " Warning: File not found for #{pub['id']}: #{pub[file_field]}" + end + end + end +end + +# Main execution +exit_code = 0 + +# Change to project root directory to find _data files +Dir.chdir(PROJECT_ROOT) + +Dir.glob('_data/*.yml').each do |file| + unless validate_file(file) + exit_code = 1 + end +end + +if exit_code == 0 + puts "\n✓ All YAML files are valid!" +else + puts "\n✗ Some YAML files have errors" +end + +exit exit_code +