├── .rubocop_todo.yml ├── .prettierignore ├── .Gemfile.lock.un~ ├── lib ├── jekyll-cloudinary.rb └── jekyll │ ├── cloudinary │ └── version.rb │ └── cloudinary.rb ├── .trunk ├── .gitignore ├── config │ ├── .shellcheckrc │ └── .markdownlint.yaml └── trunk.yaml ├── Rakefile ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ ├── gem-push.yml │ ├── rubocop.yml │ └── codeql-analysis.yml ├── script └── fmt ├── _release.sh ├── .vscode └── settings.json ├── Gemfile ├── _config.yml ├── .rubocop.yml ├── LICENSE ├── .gitignore ├── CONTRIBUTING.md ├── jekyll-cloudinary.gemspec ├── RELEASES.md ├── CODE_OF_CONDUCT.md ├── Gemfile.lock~ ├── Gemfile.lock └── README.md /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /.Gemfile.lock.un~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/jekyll-cloudinary/master/.Gemfile.lock.un~ -------------------------------------------------------------------------------- /lib/jekyll-cloudinary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll/cloudinary" 4 | -------------------------------------------------------------------------------- /.trunk/.gitignore: -------------------------------------------------------------------------------- 1 | *out 2 | *logs 3 | *actions 4 | *notifications 5 | plugins 6 | user_trunk.yaml 7 | user.yaml 8 | -------------------------------------------------------------------------------- /lib/jekyll/cloudinary/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Cloudinary 5 | VERSION = "1.21" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rubocop/rake_task" 5 | 6 | RuboCop::RakeTask.new 7 | 8 | task default: :rubocop 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.trunk/config/.shellcheckrc: -------------------------------------------------------------------------------- 1 | enable=all 2 | source-path=SCRIPTDIR 3 | disable=SC2154 4 | 5 | # If you're having issues with shellcheck following source, disable the errors via: 6 | # disable=SC1090 7 | # disable=SC1091 8 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Rubocop $(bundle exec rubocop --version)" 4 | bundle exec rubocop -S -D -E $@ 5 | success=$? 6 | if ((success != 0)); then 7 | echo -e "\nTry running \`script/fmt -a\` to automatically fix errors" 8 | fi 9 | exit $success 10 | -------------------------------------------------------------------------------- /_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # https://github.com/svenfuchs/gem-release 3 | 4 | if [ -z "$1" ]; then 5 | echo "Usage: provide the release type (patch, minor, major)." 6 | exit -1 7 | else 8 | release_type="$@" 9 | fi 10 | 11 | gem bump --version "$release_type" --tag --release 12 | -------------------------------------------------------------------------------- /.trunk/config/.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # Autoformatter friendly markdownlint config (all formatting rules disabled) 2 | default: true 3 | blank_lines: false 4 | bullet: false 5 | html: false 6 | indentation: false 7 | line_length: false 8 | spaces: false 9 | url: false 10 | whitespace: false 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [ 3 | "en" 4 | ], 5 | "spellright.documentTypes": [ 6 | "markdown", 7 | "latex", 8 | "plaintext" 9 | ], 10 | "ruby.intellisense": "rubyLocate", 11 | "ruby.codeCompletion": "rcodetools", 12 | "ruby.useBundler": true, 13 | "ruby.useLanguageServer": true 14 | } -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in jekyll-cloudinary.gemspec 6 | gemspec 7 | gem "jekyll", "~> 4.3" 8 | gem "cloudinary", "~> 1.26" 9 | gem "rake", "~> 13.0.6" 10 | 11 | group :rubocop do 12 | gem "rubocop", "~> 1.50", require: false 13 | gem 'rubocop-minitest', require: false 14 | gem "rubocop-rake", require: false 15 | gem "rubocop-performance", require: false 16 | gem "rubocop-rails", require: false 17 | end 18 | 19 | -------------------------------------------------------------------------------- /.trunk/trunk.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | cli: 3 | version: 1.0.1 4 | plugins: 5 | sources: 6 | - id: trunk 7 | ref: v0.0.5 8 | uri: https://github.com/trunk-io/plugins 9 | lint: 10 | enabled: 11 | - git-diff-check 12 | - shellcheck@0.8.0 13 | - rubocop@1.30.1 14 | - markdownlint@0.32.2 15 | - shfmt@3.5.0 16 | - actionlint@1.6.21 17 | - gitleaks@8.15.0 18 | - prettier@2.7.1 19 | runtimes: 20 | enabled: 21 | - go@1.18.3 22 | - node@16.14.2 23 | - ruby@3.1.0 24 | actions: 25 | enabled: 26 | - trunk-announce 27 | - trunk-check-pre-push 28 | - trunk-fmt-pre-commit 29 | - trunk-upgrade-available 30 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | title: Jekyll Cloudinary Plugin 2 | description: Jekyll plugin providing Cloudinary-powered responsive image generation 3 | 4 | timezone: America/Vancouver 5 | 6 | exclude: ["vendor/", "node_modules/", "*.gemspec", "*.gem", "Gemfile", "Gemfile.lock", "package.json", "package-lock.json", "script/", "LICENSE.txt", "lib/", "bin/", "README.md", "Rakefile","normalize.scss.md"] 7 | 8 | remote_theme: pmarsceill/just-the-docs 9 | 10 | color_scheme: dark 11 | 12 | # Aux links for the upper right navigation 13 | aux_links: 14 | "Source for Jekyll Cloudinary Plugin": 15 | - "//github.com/mavaddat/jekyll-cloudinary" 16 | 17 | # Makes Aux links open in a new tab. Default is false 18 | aux_links_new_tab: false 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Get version from tag 13 | id: tag_name 14 | run: | 15 | echo ::set-output name=current_version::${GITHUB_REF#refs/tags/v} 16 | shell: bash 17 | - name: Get notes 18 | id: generate_notes 19 | uses: anmarkoulis/commitizen-changelog-reader@master 20 | with: 21 | tag_name: ${{ github.ref }} 22 | changelog: ./RELEASES.md 23 | - uses: actions/checkout@master 24 | - name: Create a Release ${{ github.ref }} 25 | uses: elgohr/Github-Release-Action@master 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} 28 | with: 29 | args: ${{join(fromJson(steps.generate_notes.outputs.notes).notes, '')}} 30 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | inherit_gem: 4 | jekyll: .rubocop.yml 5 | 6 | AllCops: 7 | TargetRubyVersion: 3.2.0 8 | NewCops: enable 9 | Exclude: 10 | - script/**/* 11 | - vendor/**/* 12 | 13 | Style/StringLiterals: 14 | Enabled: true 15 | EnforcedStyle: double_quotes 16 | 17 | Style/StringLiteralsInInterpolation: 18 | Enabled: true 19 | EnforcedStyle: double_quotes 20 | 21 | Layout/LineLength: 22 | Exclude: 23 | - lib/jekyll/cloudinary.rb 24 | Max: 120 25 | 26 | Lint/UselessAssignment: 27 | Exclude: 28 | - lib/jekyll/cloudinary.rb 29 | 30 | Metrics/AbcSize: 31 | Exclude: 32 | - lib/jekyll/cloudinary.rb 33 | Metrics/BlockNesting: 34 | Exclude: 35 | - lib/jekyll/cloudinary.rb 36 | Metrics/CyclomaticComplexity: 37 | Exclude: 38 | - lib/jekyll/cloudinary.rb 39 | Metrics/MethodLength: 40 | Exclude: 41 | - lib/jekyll/cloudinary.rb 42 | Metrics/PerceivedComplexity: 43 | Exclude: 44 | - lib/jekyll/cloudinary.rb 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Mavaddat Javid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | ## Specific to RubyMotion: 20 | .dat* 21 | .repl_history 22 | build/ 23 | *.bridgesupport 24 | build-iPhoneOS/ 25 | build-iPhoneSimulator/ 26 | 27 | ## Specific to RubyMotion (use of CocoaPods): 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 32 | # 33 | # vendor/Pods/ 34 | 35 | ## Documentation cache and generated files: 36 | /.yardoc/ 37 | /_yardoc/ 38 | /doc/ 39 | /rdoc/ 40 | 41 | ## Environment normalization: 42 | /.bundle/ 43 | /vendor/bundle 44 | /vendor/cache 45 | /lib/bundler/man/ 46 | 47 | # for a library or gem, you might want to ignore these files since the code is 48 | # intended to run in multiple environments; otherwise, check them in: 49 | # Gemfile.lock 50 | # .ruby-version 51 | # .ruby-gemset 52 | 53 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 54 | .rvmrc 55 | 56 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 57 | # .rubocop-https?--* -------------------------------------------------------------------------------- /.github/workflows/gem-push.yml: -------------------------------------------------------------------------------- 1 | name: Ruby Gem 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | name: Build + Publish 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | packages: write 16 | strategy: 17 | matrix: 18 | ruby-version: [head] 19 | 20 | steps: 21 | - name: Query Ruby Version 22 | id: get_version 23 | run: | 24 | echo "ruby_version=$(curl -s https://api.github.com/repos/ruby/ruby/tags | jq -r '.[0].name')" >> $GITHUB_ENV 25 | - uses: actions/checkout@v3 26 | - name: Set up Ruby ${{ env.ruby_version }} 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby-version }} 30 | 31 | - name: Publish to GPR 32 | run: | 33 | mkdir -p $HOME/.gem 34 | touch $HOME/.gem/credentials 35 | chmod 0600 $HOME/.gem/credentials 36 | printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 37 | gem build *.gemspec 38 | gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem 39 | env: 40 | GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}" 41 | OWNER: ${{ github.repository_owner }} 42 | 43 | - name: Publish to RubyGems 44 | run: | 45 | mkdir -p $HOME/.gem 46 | touch $HOME/.gem/credentials 47 | chmod 0600 $HOME/.gem/credentials 48 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 49 | gem build *.gemspec 50 | gem push *.gem 51 | env: 52 | GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}" 53 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: RuboCop 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | ruby-version: [head, 2.7.4] 12 | os: [ubuntu-latest, windows-latest, macOS-latest] 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up Ruby ${{ matrix.ruby-version }} 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: ${{ matrix.ruby-version }} 20 | bundler-cache: true 21 | - name: Cache gems 22 | uses: actions/cache@v3 23 | with: 24 | path: vendor/bundle 25 | key: ${{ matrix.os }}-rubocop-${{ hashFiles('**/Gemfile.lock') }} 26 | restore-keys: | 27 | ${{ matrix.os }}-rubocop- 28 | - name: Install gems 29 | run: | 30 | wget https://raw.githubusercontent.com/jekyll/jekyll/master/.rubocop_todo.yml --directory-prefix="$(bundle show jekyll)"; 31 | bundle install --jobs $(nproc) --retry 3 32 | bundle exec rspec 33 | bundle exec rake 34 | bundle exec rubocop --parallel 35 | if: matrix.os == 'ubuntu-latest' 36 | - name: Install gems 37 | run: | 38 | wget https://raw.githubusercontent.com/jekyll/jekyll/master/.rubocop_todo.yml --directory-prefix="$(bundle show jekyll)"; 39 | bundle install --jobs $env:NUMBER_OF_PROCESSORS --retry 3 40 | bundle exec rspec 41 | bundle exec rake 42 | bundle exec rubocop --parallel 43 | if: matrix.os == 'windows-latest' 44 | - name: Install gems 45 | run: | 46 | wget https://raw.githubusercontent.com/jekyll/jekyll/master/.rubocop_todo.yml --directory-prefix="$(bundle show jekyll)"; 47 | bundle install --jobs $(sysctl -n hw.logicalcpu) --retry 3 48 | bundle exec rspec 49 | bundle exec rake 50 | bundle exec rubocop --parallel 51 | if: matrix.os == 'macOS-latest' 52 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | ## Introduction 4 | 5 | First, thank you for considering contributing to jekyll-cloudinary! It's people like you that make the open source community such a great community! 😊 6 | 7 | We welcome any type of contribution, not only code. You can help with 8 | - **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open) 9 | - **Marketing**: writing blog posts, howto's, printing stickers, ... 10 | - **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ... 11 | - **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them. 12 | 13 | ## Your First Contribution 14 | 15 | Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 16 | 17 | ## Submitting code 18 | 19 | Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests. 20 | 21 | ## Code review process 22 | 23 | The bigger the pull request, the longer it will take to review and merge. Try to [break down large pull requests in smaller chunks](https://oncletom.io/2013/the-55-commits-syndrome/) that are easier to review and merge. 24 | 25 | It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? 26 | 27 | ## Questions 28 | 29 | If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!). 30 | 31 | ## Credits 32 | 33 | ### Contributors 34 | 35 | Thank you to [all the people who have already contributed](https://github.com/mavaddat/jekyll-cloudinary/graphs/contributors) to jekyll-cloudinary! 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '30 9 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'ruby' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /jekyll-cloudinary.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/jekyll/cloudinary/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.version = Jekyll::Cloudinary::VERSION 7 | spec.name = "jekyll-cloudinary" 8 | spec.version = Jekyll::Cloudinary::VERSION 9 | spec.authors = ["Nicolas Hoizey", "Mavaddat Javid"] 10 | spec.email = ["nicolas@hoizey.com","info@mavaddat.ca"] 11 | spec.files = %w(Rakefile Gemfile README.md RELEASES.md LICENSE) + Dir["lib/**/*"] 12 | spec.summary = "Jekyll plugin providing Cloudinary-powered responsive image generation" 13 | spec.description = <<-DESC 14 | A plugin to enable a Jekyll-native (Liquid markup) `Cloudinary` tag that generates responsive images srcsets from an optimal number of versions for every image. It does this by finding the minimum number of image versions per file size reductions between each version. The set of breakpoints are based on a difference in the actual image file size at different widths. This means: 15 | - Deciding which image resolutions to select 16 | - Calculating how many different image versions to include 17 | Devs call these 'responsive breakpoints' or 'responsive image breakpoints'. 18 | 19 | Breakpoints for responsive design allow the same images to be displayed in various dimensions. One image for all screen resolutions and different devices is leads to cache or packet inefficiencies. This tool uploads one image and dynamically resizes it to match different screen sizes. 20 | DESC 21 | spec.homepage = "https://mavaddat.github.io/jekyll-cloudinary/" 22 | spec.license = "MIT" 23 | spec.required_ruby_version = ">= 2.6.0" 24 | 25 | spec.metadata["homepage_uri"] = spec.homepage 26 | spec.metadata["source_code_uri"] = "https://github.com/mavaddat/jekyll-cloudinary" 27 | spec.metadata["changelog_uri"] = "https://github.com/mavaddat/jekyll-cloudinary/blob/main/RELEASES.md" 28 | 29 | # Specify which files should be added to the gem when it is released. 30 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 31 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 32 | `git ls-files -z`.split("\x0").reject do |f| 33 | (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) 34 | end 35 | end 36 | spec.bindir = "exe" 37 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 38 | spec.require_paths = ["lib"] 39 | 40 | spec.add_runtime_dependency "fastimage", "~> 2.2" 41 | spec.add_runtime_dependency "jekyll", "~> 4.2" 42 | spec.add_development_dependency "rake", "~> 13.0.6" 43 | spec.add_development_dependency "rubocop", "~> 1.50.0" 44 | spec.add_development_dependency "rspec", "~> 3.10" 45 | spec.add_development_dependency "rubocop-rspec", "~> 2.5" 46 | spec.add_development_dependency "rubocop-rails", "~> 2.13" 47 | spec.add_development_dependency "rubocop-performance", "~> 1.13" 48 | spec.add_development_dependency "cloudinary", "~> 1.21" 49 | spec.add_development_dependency "bundler" 50 | end 51 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | 3 | ## [v1.21](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.21) 4 | 5 | Bump to Bundler 2.2.30. Update Rubocop idioms. 6 | 7 | ## [v1.14.1](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.14.1) 8 | 9 | Fix the loading attribute handling. 10 | 11 | Add compatibility with Jekyll 4 (thanks [@DirtyF](https://github.com/DirtyF)) 12 | 13 | ## [v1.14.0](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.14.0) 14 | 15 | Add support for the loading attribute, for [native lazy loading](https://addyosmani.com/blog/lazy-loading/) (Thanks [@borisschapira](https://github.com/borisschapira)) 16 | 17 | Read [the documentation for the loading attribute](https://github.com/mavaddat/jekyll-cloudinary/blob/master/README.md#loading-attribute). 18 | 19 | ## [v1.13.0](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.13.0) 20 | 21 | Add cross-origin support to prevent opaque responses in Service Workers. 22 | 23 | See https://cloudfour.com/thinks/when-7-kb-equals-7-mb/ 24 | 25 | ## [v1.12.4](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.12.4) 26 | 27 | Improving gem summary and description to help people find it. There was no mention of "plugin" in it… 🤔 28 | 29 | ## [v1.12.3](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.12.3) 30 | 31 | Strings are now immutable by default, be careful. ([ca68ba7](https://github.com/mavaddat/jekyll-cloudinary/commit/ca68ba7743b69983836b993761d1004494197795)) 32 | 33 | ## [v1.12.2](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.12.2) 34 | 35 | Match jekyll's coding style thanks to [@DirtyF](https://github.com/DirtyF) with a little help from [Rubocop](http://rubocop.readthedocs.io/). 36 | 37 | ## [v1.12.1](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.12.1) 38 | 39 | Break early if there is no `cloud_name` in `_config.yml`. 40 | 41 | ## [v1.12.0](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.12.0) 42 | 43 | Thanks to [@suprafly](https://github.com/suprafly)'s [Pull Request](https://github.com/mavaddat/jekyll-cloudinary/pull/29), you can now host your source images on an origin domain different from your website domain. 44 | 45 | ## [v1.11.0](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.11.0) 46 | 47 | Thanks to [Pascal Brokmeier](https://github.com/pascalwhoop)'s [Pull Request](https://github.com/mavaddat/jekyll-cloudinary/pull/34), you can now have responsive images HTML and Cloudinary URLs generated only when you build for production. 48 | 49 | Just make sure to: 50 | 51 | - set the new option `only_prod` to `true` 52 | - and set the environment to `production` before building: `JEKYLL_ENV=production bundle exec jekyll build` 53 | 54 | ## [v1.10.0](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.10.0) 55 | 56 | Fixes an issue caused by Jekyll 3.8.1 introducing a change to content's path, adding an `/#excerpt` at the end in case of an excerpt. 57 | 58 | See https://github.com/mavaddat/jekyll-cloudinary/commit/5372e37e4d31bf1934d90665692b9e14f2ac2147 59 | 60 | ## [v1.9.1](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.9.1) 61 | 62 | Better warning message when the local source image is missing. 63 | 64 | ## [v1.9.0](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.9.0) 65 | 66 | Get the dimensions of the picture from the source path instead of the destination one ([#19](https://github.com/mavaddat/jekyll-cloudinary/issues/19)) 67 | 68 | ## [v1.8.1](https://github.com/mavaddat/jekyll-cloudinary/releases/tag/v1.8.1) 69 | 70 | Fixes an issue with local images. 71 | 72 | ## v1.8.0 73 | 74 | Image size detection now uses FastImage instead of RMagick to remove imagemagick dependency, thanks to [@aarongustafson](https://github.com/aarongustafson) ([#25](https://github.com/mavaddat/jekyll-cloudinary/issues/25)) 75 | 76 | ## v1.7.0 77 | 78 | It is now possible to use all effects and transformations from the Cloudinary API, thanks to [@aarongustafson](https://github.com/aarongustafson) ([#24](https://github.com/mavaddat/jekyll-cloudinary/issues/24)) 79 | 80 | ## v1.4.0 81 | 82 | Now supports sites with baseurl ([#10](https://github.com/mavaddat/jekyll-cloudinary/issues/10)) 83 | 84 | ## v1.3.1 85 | 86 | Restores natural width if some computed ones are missing. 87 | 88 | ## v1.3.0 89 | 90 | Restores `width` and `height` attributes. 91 | 92 | ## v1.2.17 93 | 94 | Fixes a little typo with huge impact… 95 | 96 | ## v1.2.16 97 | 98 | Code improvements thanks to Rubocop, initialized by [@DirtyF](https://github.com/DirtyF) 99 | 100 | ## v1.2.15 101 | 102 | Fixed bugs: 103 | - Don’t add `` `height` & `width` attributes ([#7](https://github.com/mavaddat/jekyll-cloudinary/issues/7)) 104 | - Dont’t wrap `alt` text in `

` ([#8](https://github.com/mavaddat/jekyll-cloudinary/issues/8)) 105 | - `require 'rmagick'` tripped me up ([#11](https://github.com/mavaddat/jekyll-cloudinary/issues/11)) 106 | 107 | Thanks [@eeeps](https://github.com/eeeps) for catching these issues! 108 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at support@mavaddat.ca. All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /Gemfile.lock~: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | jekyll-cloudinary (1.21) 5 | fastimage (~> 2.2) 6 | jekyll (~> 4.2) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | activesupport (6.1.7) 12 | concurrent-ruby (~> 1.0, >= 1.0.2) 13 | i18n (>= 1.6, < 2) 14 | minitest (>= 5.1) 15 | tzinfo (~> 2.0) 16 | zeitwerk (~> 2.3) 17 | addressable (2.8.1) 18 | public_suffix (>= 2.0.2, < 6.0) 19 | ast (2.4.2) 20 | aws_cf_signer (0.1.3) 21 | cloudinary (1.24.0) 22 | aws_cf_signer 23 | rest-client (>= 2.0.0) 24 | colorator (1.1.0) 25 | concurrent-ruby (1.1.10) 26 | diff-lcs (1.5.0) 27 | domain_name (0.5.20190701) 28 | unf (>= 0.0.5, < 1.0.0) 29 | em-websocket (0.5.3) 30 | eventmachine (>= 0.12.9) 31 | http_parser.rb (~> 0) 32 | eventmachine (1.2.7) 33 | eventmachine (1.2.7-x64-mingw32) 34 | fastimage (2.2.6) 35 | ffi (1.15.5) 36 | ffi (1.15.5-x64-mingw-ucrt) 37 | ffi (1.15.5-x64-mingw32) 38 | forwardable-extended (2.6.0) 39 | http-accept (1.7.0) 40 | http-cookie (1.0.5) 41 | domain_name (~> 0.5) 42 | http_parser.rb (0.8.0) 43 | i18n (1.12.0) 44 | concurrent-ruby (~> 1.0) 45 | jekyll (4.3.1) 46 | addressable (~> 2.4) 47 | colorator (~> 1.0) 48 | em-websocket (~> 0.5) 49 | i18n (~> 1.0) 50 | jekyll-sass-converter (>= 2.0, < 4.0) 51 | jekyll-watch (~> 2.0) 52 | kramdown (~> 2.3, >= 2.3.1) 53 | kramdown-parser-gfm (~> 1.0) 54 | liquid (~> 4.0) 55 | mercenary (>= 0.3.6, < 0.5) 56 | pathutil (~> 0.9) 57 | rouge (>= 3.0, < 5.0) 58 | safe_yaml (~> 1.0) 59 | terminal-table (>= 1.8, < 4.0) 60 | webrick (~> 1.7) 61 | jekyll-sass-converter (2.2.0) 62 | sassc (> 2.0.1, < 3.0) 63 | jekyll-watch (2.2.1) 64 | listen (~> 3.0) 65 | json (2.6.3) 66 | kramdown (2.4.0) 67 | rexml 68 | kramdown-parser-gfm (1.1.0) 69 | kramdown (~> 2.0) 70 | liquid (4.0.3) 71 | listen (3.7.1) 72 | rb-fsevent (~> 0.10, >= 0.10.3) 73 | rb-inotify (~> 0.9, >= 0.9.10) 74 | mercenary (0.4.0) 75 | mime-types (3.4.1) 76 | mime-types-data (~> 3.2015) 77 | mime-types-data (3.2022.0105) 78 | minitest (5.16.3) 79 | netrc (0.11.0) 80 | parallel (1.22.1) 81 | parser (3.1.3.0) 82 | ast (~> 2.4.1) 83 | pathutil (0.16.2) 84 | forwardable-extended (~> 2.6) 85 | public_suffix (5.0.0) 86 | rack (3.0.1) 87 | rainbow (3.1.1) 88 | rake (13.0.6) 89 | rb-fsevent (0.11.2) 90 | rb-inotify (0.10.1) 91 | ffi (~> 1.0) 92 | regexp_parser (2.6.1) 93 | rest-client (2.1.0) 94 | http-accept (>= 1.7.0, < 2.0) 95 | http-cookie (>= 1.0.2, < 2.0) 96 | mime-types (>= 1.16, < 4.0) 97 | netrc (~> 0.8) 98 | rest-client (2.1.0-x64-mingw32) 99 | ffi (~> 1.9) 100 | http-accept (>= 1.7.0, < 2.0) 101 | http-cookie (>= 1.0.2, < 2.0) 102 | mime-types (>= 1.16, < 4.0) 103 | netrc (~> 0.8) 104 | rexml (3.2.5) 105 | rouge (3.30.0) 106 | rspec (3.12.0) 107 | rspec-core (~> 3.12.0) 108 | rspec-expectations (~> 3.12.0) 109 | rspec-mocks (~> 3.12.0) 110 | rspec-core (3.12.0) 111 | rspec-support (~> 3.12.0) 112 | rspec-expectations (3.12.0) 113 | diff-lcs (>= 1.2.0, < 2.0) 114 | rspec-support (~> 3.12.0) 115 | rspec-mocks (3.12.0) 116 | diff-lcs (>= 1.2.0, < 2.0) 117 | rspec-support (~> 3.12.0) 118 | rspec-support (3.12.0) 119 | rubocop (1.41.0) 120 | json (~> 2.3) 121 | parallel (~> 1.10) 122 | parser (>= 3.1.2.1) 123 | rainbow (>= 2.2.2, < 4.0) 124 | regexp_parser (>= 1.8, < 3.0) 125 | rexml (>= 3.2.5, < 4.0) 126 | rubocop-ast (>= 1.23.0, < 2.0) 127 | ruby-progressbar (~> 1.7) 128 | unicode-display_width (>= 1.4.0, < 3.0) 129 | rubocop-ast (1.24.0) 130 | parser (>= 3.1.1.0) 131 | <<<<<<< HEAD 132 | rubocop-minitest (0.25.0) 133 | ======= 134 | rubocop-minitest (0.24.0) 135 | >>>>>>> upstream/master 136 | rubocop (>= 0.90, < 2.0) 137 | rubocop-performance (1.15.1) 138 | rubocop (>= 1.7.0, < 2.0) 139 | rubocop-ast (>= 0.4.0) 140 | rubocop-rails (2.17.3) 141 | activesupport (>= 4.2.0) 142 | rack (>= 1.1) 143 | rubocop (>= 1.33.0, < 2.0) 144 | rubocop-rake (0.6.0) 145 | rubocop (~> 1.0) 146 | rubocop-rspec (2.16.0) 147 | rubocop (~> 1.33) 148 | ruby-progressbar (1.11.0) 149 | safe_yaml (1.0.5) 150 | sassc (2.4.0) 151 | ffi (~> 1.9) 152 | sassc (2.4.0-x64-mingw32) 153 | ffi (~> 1.9) 154 | terminal-table (3.0.2) 155 | unicode-display_width (>= 1.1.1, < 3) 156 | tzinfo (2.0.5) 157 | concurrent-ruby (~> 1.0) 158 | unf (0.1.4) 159 | unf_ext 160 | unf_ext (0.0.8.2) 161 | unicode-display_width (2.3.0) 162 | webrick (1.7.0) 163 | zeitwerk (2.6.6) 164 | 165 | PLATFORMS 166 | x64-mingw-ucrt 167 | x64-mingw32 168 | x86_64-darwin-19 169 | x86_64-linux 170 | 171 | DEPENDENCIES 172 | bundler 173 | cloudinary (~> 1.24) 174 | jekyll (~> 4.2) 175 | jekyll-cloudinary! 176 | rake (~> 13.0.6) 177 | rspec (~> 3.10) 178 | rubocop (~> 1.41) 179 | rubocop-minitest 180 | rubocop-performance 181 | rubocop-rails 182 | rubocop-rake 183 | rubocop-rspec (~> 2.5) 184 | 185 | BUNDLED WITH 186 | 2.3.0 187 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | jekyll-cloudinary (1.21) 5 | fastimage (~> 2.2) 6 | jekyll (~> 4.2) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | activesupport (6.1.7.3) 12 | concurrent-ruby (~> 1.0, >= 1.0.2) 13 | i18n (>= 1.6, < 2) 14 | minitest (>= 5.1) 15 | tzinfo (~> 2.0) 16 | zeitwerk (~> 2.3) 17 | addressable (2.8.1) 18 | public_suffix (>= 2.0.2, < 6.0) 19 | ast (2.4.2) 20 | aws_cf_signer (0.1.3) 21 | cloudinary (1.26.0) 22 | aws_cf_signer 23 | rest-client (>= 2.0.0) 24 | colorator (1.1.0) 25 | concurrent-ruby (1.2.2) 26 | diff-lcs (1.5.0) 27 | domain_name (0.5.20190701) 28 | unf (>= 0.0.5, < 1.0.0) 29 | em-websocket (0.5.3) 30 | eventmachine (>= 0.12.9) 31 | http_parser.rb (~> 0) 32 | eventmachine (1.2.7) 33 | eventmachine (1.2.7-x64-mingw32) 34 | fastimage (2.2.7) 35 | ffi (1.15.5) 36 | ffi (1.15.5-x64-mingw-ucrt) 37 | ffi (1.15.5-x64-mingw32) 38 | forwardable-extended (2.6.0) 39 | google-protobuf (3.21.12-x64-mingw-ucrt) 40 | google-protobuf (3.21.12-x64-mingw32) 41 | google-protobuf (3.21.12-x86_64-darwin) 42 | google-protobuf (3.21.12-x86_64-linux) 43 | http-accept (1.7.0) 44 | http-cookie (1.0.5) 45 | domain_name (~> 0.5) 46 | http_parser.rb (0.8.0) 47 | i18n (1.12.0) 48 | concurrent-ruby (~> 1.0) 49 | jekyll (4.3.2) 50 | addressable (~> 2.4) 51 | colorator (~> 1.0) 52 | em-websocket (~> 0.5) 53 | i18n (~> 1.0) 54 | jekyll-sass-converter (>= 2.0, < 4.0) 55 | jekyll-watch (~> 2.0) 56 | kramdown (~> 2.3, >= 2.3.1) 57 | kramdown-parser-gfm (~> 1.0) 58 | liquid (~> 4.0) 59 | mercenary (>= 0.3.6, < 0.5) 60 | pathutil (~> 0.9) 61 | rouge (>= 3.0, < 5.0) 62 | safe_yaml (~> 1.0) 63 | terminal-table (>= 1.8, < 4.0) 64 | webrick (~> 1.7) 65 | jekyll-sass-converter (3.0.0) 66 | sass-embedded (~> 1.54) 67 | jekyll-watch (2.2.1) 68 | listen (~> 3.0) 69 | json (2.6.3) 70 | kramdown (2.4.0) 71 | rexml 72 | kramdown-parser-gfm (1.1.0) 73 | kramdown (~> 2.0) 74 | liquid (4.0.4) 75 | listen (3.8.0) 76 | rb-fsevent (~> 0.10, >= 0.10.3) 77 | rb-inotify (~> 0.9, >= 0.9.10) 78 | mercenary (0.4.0) 79 | mime-types (3.4.1) 80 | mime-types-data (~> 3.2015) 81 | mime-types-data (3.2023.0218.1) 82 | minitest (5.18.0) 83 | netrc (0.11.0) 84 | parallel (1.23.0) 85 | parser (3.2.2.1) 86 | ast (~> 2.4.1) 87 | pathutil (0.16.2) 88 | forwardable-extended (~> 2.6) 89 | public_suffix (5.0.1) 90 | rack (3.0.7) 91 | rainbow (3.1.1) 92 | rake (13.0.6) 93 | rb-fsevent (0.11.2) 94 | rb-inotify (0.10.1) 95 | ffi (~> 1.0) 96 | regexp_parser (2.8.0) 97 | rest-client (2.1.0) 98 | http-accept (>= 1.7.0, < 2.0) 99 | http-cookie (>= 1.0.2, < 2.0) 100 | mime-types (>= 1.16, < 4.0) 101 | netrc (~> 0.8) 102 | rest-client (2.1.0-x64-mingw32) 103 | ffi (~> 1.9) 104 | http-accept (>= 1.7.0, < 2.0) 105 | http-cookie (>= 1.0.2, < 2.0) 106 | mime-types (>= 1.16, < 4.0) 107 | netrc (~> 0.8) 108 | rexml (3.2.5) 109 | rouge (3.30.0) 110 | rspec (3.12.0) 111 | rspec-core (~> 3.12.0) 112 | rspec-expectations (~> 3.12.0) 113 | rspec-mocks (~> 3.12.0) 114 | rspec-core (3.12.0) 115 | rspec-support (~> 3.12.0) 116 | rspec-expectations (3.12.0) 117 | diff-lcs (>= 1.2.0, < 2.0) 118 | rspec-support (~> 3.12.0) 119 | rspec-mocks (3.12.0) 120 | diff-lcs (>= 1.2.0, < 2.0) 121 | rspec-support (~> 3.12.0) 122 | rspec-support (3.12.0) 123 | rubocop (1.50.2) 124 | json (~> 2.3) 125 | parallel (~> 1.10) 126 | parser (>= 3.2.0.0) 127 | rainbow (>= 2.2.2, < 4.0) 128 | regexp_parser (>= 1.8, < 3.0) 129 | rexml (>= 3.2.5, < 4.0) 130 | rubocop-ast (>= 1.28.0, < 2.0) 131 | ruby-progressbar (~> 1.7) 132 | unicode-display_width (>= 2.4.0, < 3.0) 133 | rubocop-ast (1.28.1) 134 | parser (>= 3.2.1.0) 135 | rubocop-capybara (2.17.1) 136 | rubocop (~> 1.41) 137 | rubocop-minitest (0.31.0) 138 | rubocop (>= 1.39, < 2.0) 139 | rubocop-performance (1.17.1) 140 | rubocop (>= 1.7.0, < 2.0) 141 | rubocop-ast (>= 0.4.0) 142 | rubocop-rails (2.19.1) 143 | activesupport (>= 4.2.0) 144 | rack (>= 1.1) 145 | rubocop (>= 1.33.0, < 2.0) 146 | rubocop-rake (0.6.0) 147 | rubocop (~> 1.0) 148 | rubocop-rspec (2.20.0) 149 | rubocop (~> 1.33) 150 | rubocop-capybara (~> 2.17) 151 | ruby-progressbar (1.13.0) 152 | safe_yaml (1.0.5) 153 | sass-embedded (1.57.1-x64-mingw-ucrt) 154 | google-protobuf (~> 3.21) 155 | sass-embedded (1.57.1-x64-mingw32) 156 | google-protobuf (~> 3.21) 157 | sass-embedded (1.57.1-x86_64-darwin) 158 | google-protobuf (~> 3.21) 159 | sass-embedded (1.57.1-x86_64-linux-gnu) 160 | google-protobuf (~> 3.21) 161 | terminal-table (3.0.2) 162 | unicode-display_width (>= 1.1.1, < 3) 163 | tzinfo (2.0.6) 164 | concurrent-ruby (~> 1.0) 165 | unf (0.1.4) 166 | unf_ext 167 | unf_ext (0.0.8.2) 168 | unf_ext (0.0.8.2-x64-mingw-ucrt) 169 | unf_ext (0.0.8.2-x64-mingw32) 170 | unicode-display_width (2.4.2) 171 | webrick (1.7.0) 172 | zeitwerk (2.6.7) 173 | 174 | PLATFORMS 175 | x64-mingw-ucrt 176 | x64-mingw32 177 | x86_64-darwin-19 178 | x86_64-linux 179 | 180 | DEPENDENCIES 181 | bundler 182 | cloudinary (~> 1.26) 183 | jekyll (~> 4.3) 184 | jekyll-cloudinary! 185 | rake (~> 13.0.6) 186 | rspec (~> 3.10) 187 | rubocop (~> 1.50) 188 | rubocop-minitest 189 | rubocop-performance 190 | rubocop-rails 191 | rubocop-rake 192 | rubocop-rspec (~> 2.5) 193 | 194 | BUNDLED WITH 195 | 2.3.0 196 | -------------------------------------------------------------------------------- /lib/jekyll/cloudinary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Cloudinary 5 | 6 | class CloudinaryTag < Liquid::Tag 7 | require "fastimage" 8 | 9 | def initialize(tag_name, markup, tokens) 10 | @markup = markup 11 | super 12 | end 13 | 14 | def render(context) 15 | # Default settings 16 | settings_defaults = { 17 | "cloud_name" => "", 18 | "only_prod" => false, 19 | "verbose" => false, 20 | } 21 | preset_defaults = { 22 | "min_width" => 320, 23 | "max_width" => 1200, 24 | "fallback_max_width" => 1200, 25 | "steps" => 5, 26 | "sizes" => "100vw", 27 | "figure" => "auto", 28 | "attributes" => {}, 29 | "width_height" => true, 30 | # Cloudinary transformations 31 | "height" => false, 32 | "crop" => "limit", 33 | "aspect_ratio" => false, 34 | "gravity" => false, 35 | "zoom" => false, 36 | "x" => false, 37 | "y" => false, 38 | "format" => false, 39 | "fetch_format" => "auto", 40 | "quality" => "auto", 41 | "radius" => false, 42 | "angle" => false, 43 | "effect" => false, 44 | "opacity" => false, 45 | "border" => false, 46 | "background" => false, 47 | "overlay" => false, 48 | "underlay" => false, 49 | "default_image" => false, 50 | "delay" => false, 51 | "color" => false, 52 | "color_space" => false, 53 | "dpr" => false, 54 | "page" => false, 55 | "density" => false, 56 | "flags" => false, 57 | "transformation" => false, 58 | } 59 | 60 | # TODO: Add validation for these parameters 61 | transformation_options = { 62 | "height" => "h", 63 | "crop" => "c", # can include add-on: imagga_scale 64 | "aspect_ratio" => "ar", 65 | "gravity" => "g", 66 | "zoom" => "z", 67 | "x" => "x", 68 | "y" => "y", 69 | "fetch_format" => "f", 70 | "quality" => "q", # can include add-on: jpegmini 71 | "radius" => "r", 72 | "angle" => "a", 73 | "effect" => "e", # can include add-on: viesus_correct 74 | "opacity" => "o", 75 | "border" => "bo", 76 | "background" => "b", 77 | "overlay" => "l", 78 | "underlay" => "u", 79 | "default_image" => "d", 80 | "delay" => "dl", 81 | "color" => "co", 82 | "color_space" => "cs", 83 | "dpr" => "dpr", 84 | "page" => "pg", 85 | "density" => "dn", 86 | "flags" => "fl", 87 | "transformation" => "t", 88 | } 89 | 90 | # Settings 91 | site = context.registers[:site] 92 | site_url = site.config["url"] || "" 93 | site_baseurl = site.config["baseurl"] || "" 94 | if site.config["cloudinary"].nil? 95 | Jekyll.logger.abort_with("[Cloudinary]", "You must set your cloud_name in _config.yml") 96 | end 97 | settings = settings_defaults.merge(site.config["cloudinary"]) 98 | if settings["cloud_name"] == "" 99 | Jekyll.logger.abort_with("[Cloudinary]", "You must set your cloud_name in _config.yml") 100 | end 101 | url = settings["origin_url"] || (site_url + site_baseurl) 102 | 103 | # Get Markdown converter 104 | markdown_converter = site.find_converter_instance(::Jekyll::Converters::Markdown) 105 | 106 | preset_user_defaults = {} 107 | if settings["presets"] 108 | if settings["presets"]["default"] 109 | preset_user_defaults = settings["presets"]["default"] 110 | end 111 | end 112 | 113 | preset = preset_defaults.merge(preset_user_defaults) 114 | 115 | # Render any liquid variables in tag arguments and unescape template code 116 | rendered_markup = Liquid::Template 117 | .parse(@markup) 118 | .render(context) 119 | .gsub(%r!\\\{\\\{|\\\{\\%!, '\{\{' => "{{", '\{\%' => "{%") 120 | 121 | # Extract tag segments 122 | markup = 123 | %r!^(?:(?[^\s.:\/]+)\s+)?(?[^\s]+\.[a-zA-Z0-9]{3,4})\s*(?[\s\S]+)?$! 124 | .match(rendered_markup) 125 | 126 | unless markup 127 | Jekyll.logger.abort_with("[Cloudinary]", "Can't read this tag: #{@markup}") 128 | end 129 | 130 | image_src = markup[:image_src] 131 | 132 | # Dynamic image type 133 | type = "fetch" 134 | # TODO: URL2PNG requires signed URLs... need to investigate more 135 | # if /^url2png\:/.match(image_src) 136 | # type = "url2png" 137 | # image_src.gsub! "url2png:", "" 138 | # end 139 | 140 | if markup[:preset] 141 | if settings["presets"][markup[:preset]] 142 | preset = preset.merge(settings["presets"][markup[:preset]]) 143 | elsif settings["verbose"] 144 | Jekyll.logger.warn( 145 | "[Cloudinary]", 146 | "'#{markup[:preset]}' preset for the Cloudinary plugin doesn't exist, \ 147 | using the default one" 148 | ) 149 | end 150 | end 151 | 152 | attributes = preset["attributes"] 153 | 154 | # Deep copy preset for single instance manipulation 155 | instance = Marshal.load(Marshal.dump(preset)) 156 | 157 | # Process attributes 158 | html_attr = if markup[:html_attr] 159 | Hash[ *markup[:html_attr].scan(%r!(?[^\s="]+)(?:="(?[^"]+)")?\s?!).flatten ] 160 | else 161 | {} 162 | end 163 | 164 | if instance["attr"] 165 | html_attr = instance.delete("attr").merge(html_attr) 166 | end 167 | 168 | # Classes from the tag should complete, not replace, the ones from the preset 169 | if html_attr["class"] && attributes["class"] 170 | html_attr["class"] << " #{attributes["class"]}" 171 | end 172 | html_attr = attributes.merge(html_attr) 173 | 174 | # Deal with the "caption" attribute as a true

175 | if html_attr["caption"] 176 | caption = markdown_converter.convert(html_attr["caption"]) 177 | html_attr.delete("caption") 178 | end 179 | 180 | # alt and title attributes should go only to the even when there is a caption 181 | img_attr = "".dup 182 | if html_attr["alt"] 183 | img_attr << " alt=\"#{html_attr["alt"]}\"" 184 | html_attr.delete("alt") 185 | end 186 | if html_attr["title"] 187 | img_attr << " title=\"#{html_attr["title"]}\"" 188 | html_attr.delete("title") 189 | end 190 | if html_attr["loading"] 191 | img_attr << " loading=\"#{html_attr["loading"]}\"" 192 | html_attr.delete("loading") 193 | end 194 | 195 | attr_string = html_attr.map { |a, v| "#{a}=\"#{v}\"" }.join(" ") 196 | 197 | # Figure out the Cloudinary transformations 198 | transformations = [] 199 | transformations_string = "" 200 | transformation_options.each do |key, shortcode| 201 | if preset[key] 202 | transformations << "#{shortcode}_#{preset[key]}" 203 | end 204 | end 205 | unless transformations.empty? 206 | transformations_string = transformations.compact.reject(&:empty?).join(",") + "," 207 | end 208 | 209 | # Build source image URL 210 | is_image_remote = %r!^https?!.match(image_src) 211 | if is_image_remote 212 | # It's remote 213 | image_dest_path = image_src 214 | image_dest_url = image_src 215 | natural_width, natural_height = FastImage.size(image_dest_url) 216 | if natural_width.nil? 217 | Jekyll.logger.warn("remote url doesn't exists " + image_dest_url) 218 | return "" 219 | end 220 | width_height = "width=\"#{natural_width}\" height=\"#{natural_height}\"" 221 | fallback_url = "https://res.cloudinary.com/#{settings["cloud_name"]}/image/#{type}/#{transformations_string}w_#{preset["fallback_max_width"]}/#{image_dest_url}" 222 | else 223 | # It's a local image 224 | is_image_src_absolute = %r!^/.*$!.match(image_src) 225 | if is_image_src_absolute 226 | image_src_path = File.join( 227 | site.config["source"], 228 | image_src 229 | ) 230 | image_dest_path = File.join( 231 | site.config["destination"], 232 | image_src 233 | ) 234 | image_dest_url = File.join( 235 | url, 236 | image_src 237 | ) 238 | else 239 | image_src_path = File.join( 240 | site.config["source"], 241 | File.dirname(context["page"]["path"].chomp("/#excerpt")), 242 | image_src 243 | ) 244 | image_dest_path = File.join( 245 | site.config["destination"], 246 | File.dirname(context["page"]["url"]), 247 | image_src 248 | ) 249 | image_dest_url = File.join( 250 | url, 251 | File.dirname(context["page"]["url"]), 252 | image_src 253 | ) 254 | end 255 | if File.exist?(image_src_path) 256 | natural_width, natural_height = FastImage.size(image_src_path) 257 | width_height = "width=\"#{natural_width}\" height=\"#{natural_height}\"" 258 | fallback_url = "https://res.cloudinary.com/#{settings["cloud_name"]}/image/#{type}/#{transformations_string}w_#{preset["fallback_max_width"]}/#{image_dest_url}" 259 | else 260 | natural_width = 100_000 261 | width_height = "" 262 | Jekyll.logger.warn( 263 | "[Cloudinary]", 264 | "Couldn't find this image to check its width: #{image_src_path}." 265 | ) 266 | fallback_url = image_dest_url 267 | end 268 | end 269 | 270 | # Don't generate responsive image HTML and Cloudinary URLs for local development 271 | if settings["only_prod"] && ENV["JEKYLL_ENV"] != "production" 272 | return "" 273 | end 274 | 275 | srcset = [] 276 | steps = preset["steps"].to_i 277 | min_width = preset["min_width"].to_i 278 | max_width = preset["max_width"].to_i 279 | step_width = (max_width - min_width) / (steps - 1) 280 | sizes = preset["sizes"] 281 | 282 | if natural_width < min_width 283 | if settings["verbose"] 284 | Jekyll.logger.warn( 285 | "[Cloudinary]", 286 | "Width of source image '#{File.basename(image_src)}' (#{natural_width}px) \ 287 | in #{context["page"]["path"]} not enough for ANY srcset version" 288 | ) 289 | end 290 | srcset << "https://res.cloudinary.com/#{settings["cloud_name"]}/image/#{type}/#{transformations_string}w_#{natural_width}/#{image_dest_url} #{natural_width}w" 291 | else 292 | missed_sizes = [] 293 | (1..steps).each do |factor| 294 | width = min_width + (factor - 1) * step_width 295 | if width <= natural_width 296 | srcset << "https://res.cloudinary.com/#{settings["cloud_name"]}/image/#{type}/#{transformations_string}w_#{width}/#{image_dest_url} #{width}w" 297 | else 298 | missed_sizes.push(width) 299 | end 300 | end 301 | unless missed_sizes.empty? 302 | srcset << "https://res.cloudinary.com/#{settings["cloud_name"]}/image/#{type}/#{transformations_string}w_#{natural_width}/#{image_dest_url} #{natural_width}w" 303 | if settings["verbose"] 304 | Jekyll.logger.warn( 305 | "[Cloudinary]", 306 | "Width of source image '#{File.basename(image_src)}' (#{natural_width}px) \ 307 | in #{context["page"]["path"]} not enough for #{missed_sizes.join("px, ")}px \ 308 | version#{missed_sizes.length > 1 ? "s" : ""}" 309 | ) 310 | end 311 | end 312 | end 313 | srcset_string = srcset.join(",\n") 314 | 315 | # preset['figure'] can be 'never', 'auto' or 'always' 316 | if (caption || preset["figure"] == "always") && preset["figure"] != "never" 317 | "\n
\n\n
#{caption}
\n
\n" 318 | else 319 | "" 320 | end 321 | end 322 | end 323 | 324 | end 325 | end 326 | 327 | Liquid::Template.register_tag("cloudinary", Jekyll::Cloudinary::CloudinaryTag) 328 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jekyll Cloudinary Liquid tag 2 | [![Build](https://github.com/nhoizey/jekyll-cloudinary/actions/workflows/rubocop.yml/badge.svg)](https://github.com/nhoizey/jekyll-cloudinary/actions/workflows/rubocop.yml) 3 | 6 | 7 | `jekyll-cloudinary` is a [Jekyll](http://jekyllrb.com/) plugin adding a [Liquid](http://liquidmarkup.org) tag to ease the use of [Cloudinary](https://nho.io/cloudinary-signup) for responsive images in your Markdown/[Kramdown](http://kramdown.gettalong.org/) posts. 8 | 9 | It builds the HTML for responsive images in the posts, using the `srcset` and `sizes` attributes for the `` tag (see [the "varying size and density" section of this post](https://jakearchibald.com/2015/anatomy-of-responsive-images/#varying-size-and-density) if this is new for you, and why it's recommended to [not use `` most of the time](https://cloudfour.com/thinks/dont-use-picture-most-of-the-time/)). URLs in the `srcset` are cloudinary URLs that [fetch on-the-fly](http://cloudinary.com/features#fetch) the post's images and resize them to several sizes. 10 | 11 | You are in full control of the number of generated images and their sizes, and the `sizes` attribute that helps the browser decide which image to download. See the complete configuration options for details. 12 | 13 | Here is the general syntax of this Liquid tag: 14 | 15 | 16 | ```liquid 17 | {% cloudinary cloudflare.png alt="Un schéma montrant l'apport de Cloudflare" caption="Un schéma montrant l'apport de Cloudflare" loading="lazy" %} 18 | ``` 19 | 20 | 21 | 22 | 23 | ## Table of contents 24 | 25 | - [Jekyll Cloudinary Liquid tag](#jekyll-cloudinary-liquid-tag) 26 | - [Table of contents](#table-of-contents) 27 | - [Installation](#installation) 28 | - [Configuration](#configuration) 29 | - [Mandatory settings](#mandatory-settings) 30 | - [Optional global settings](#optional-global-settings) 31 | - [`only_prod` (default: `false`)](#only_prod-default-false) 32 | - [`verbose` (default: `false`)](#verbose-default-false) 33 | - [`origin_url`](#origin_url) 34 | - [Optional (but highly recommended) presets](#optional-but-highly-recommended-presets) 35 | - [Default preset](#default-preset) 36 | - [Additional presets](#additional-presets) 37 | - [Detailed preset settings](#detailed-preset-settings) 38 | - [`figure` (default: `auto`)](#figure-default-auto) 39 | - [`min_width` (default: `320`)](#min_width-default-320) 40 | - [`max_width` (default: `1200`)](#max_width-default-1200) 41 | - [`fallback_max_width` (defaut: `1200`)](#fallback_max_width-defaut-1200) 42 | - [`steps` (default: `5`)](#steps-default-5) 43 | - [`sizes` (default: `"100vw"`)](#sizes-default-100vw) 44 | - [`attributes` (default: none)](#attributes-default-none) 45 | - [Liquid tag values](#liquid-tag-values) 46 | - [Liquid tag attributes](#liquid-tag-attributes) 47 | - [Recommended attributes](#recommended-attributes) 48 | - [Loading attribute](#loading-attribute) 49 | - [Other interesting attributes](#other-interesting-attributes) 50 | - [Live example](#live-example) 51 | - [Contributing](#contributing) 52 | - [Do you use the plugin on a live site?](#do-you-use-the-plugin-on-a-live-site) 53 | 54 | 55 | 56 | ## Installation 57 | 58 | [Sign up **for free** on Cloudinary!](https://nho.io/cloudinary-signup) The free account should be enough for most blogs. 59 | 60 | Add `gem 'jekyll-cloudinary'` to the `jekyll_plugin` group in your `Gemfile`: 61 | 62 | ```ruby 63 | source 'https://rubygems.org' 64 | 65 | gem 'jekyll' 66 | 67 | group :jekyll_plugins do 68 | gem 'jekyll-cloudinary' 69 | end 70 | ``` 71 | 72 | Then run `bundle` to install the gem. 73 | 74 | ## Configuration 75 | 76 | ### Mandatory settings 77 | 78 | Add `cloudinary` to your `_config.yml` and your Cloudinary "Cloud name" (find it in your [Cloudinary dashboard](https://cloudinary.com/console)): 79 | 80 | ```yaml 81 | cloudinary: 82 | cloud_name: 83 | ``` 84 | 85 | ### Optional global settings 86 | 87 | You can now define some global settings 88 | 89 | ```yaml 90 | cloudinary: 91 | … 92 | only_prod: true 93 | verbose: true 94 | origin_url: https://another-domain.com 95 | ``` 96 | 97 | #### `only_prod` (default: `false`) 98 | 99 | When set to `true`, this setting implies that responsive image HTML and Cloudinary URLs are generated only if the environment is `production`. 100 | 101 | For example: 102 | 103 | - if you run `JEKYLL_ENV=production bundle exec jekyll build`, you'll get the code to deploy, with `srcset` and Cloudinary URLs. 104 | - if you run `JEKYLL_ENV=development bundle exec jekyll serve`, you'll get code for local development, with standard `` code and local URLs. 105 | 106 | [`JEKYLL_ENV=development` is the default value](https://jekyllrb.com/docs/configuration/#specifying-a-jekyll-environment-at-build-time). 107 | 108 | If you don't set `only_prod` or set it to `false`, responsive image HTML and Cloudinary URLs are always generated, whatever the environment. jekyll-cloudinary had only this behavior before version 1.11.0. 109 | 110 | #### `verbose` (default: `false`) 111 | 112 | When set to `true`, this setting will show messages in the console when something goes wrong, such as: 113 | 114 | ``` 115 | [Cloudinary] Couldn't find this image to check its width: /path/to/jekyll/_site/assets/img.jpg 116 | ``` 117 | 118 | or 119 | 120 | ``` 121 | [Cloudinary] Natural width of source image 'img.jpg' (720px) in _posts/2016-06-09-post.md not enough for creating 1600px version 122 | ``` 123 | 124 | #### `origin_url` 125 | 126 | When `origin_url` is set, **jekyll-cloudinary** will use this URL rather than `site.url` as origin of the source images. 127 | 128 | This allows you to store your source image on a different domain than your website. 129 | 130 | ### Optional (but highly recommended) presets 131 | 132 | You can now define the presets you need for your posts' images, starting with the default one: 133 | 134 | #### Default preset 135 | 136 | The default preset is the one you don't even have to mention when using the Liquid tag, and that will be used if a preset you use in the tag doesn't exist. 137 | 138 | ```yaml 139 | cloudinary: 140 | … 141 | presets: 142 | default: 143 | min_width: 320 144 | max_width: 1600 145 | fallback_max_width: 800 146 | steps: 5 147 | sizes: "(min-width: 50rem) 50rem, 90vw" 148 | ``` 149 | 150 | This preset will generate five images 320 to 1600 pixels wide in the `srcset` and define `sizes` as `"(min-width: 50rem) 50rem, 90vw"`. The fallback image defined in the `src` will have a width of 800 pixels. 151 | 152 | With this preset, you only have to write this in your Markdown post: 153 | 154 | 155 | ```liquid 156 | {% cloudinary /assets/img.jpg alt="beautiful!" %} 157 | ``` 158 | 159 | 160 | To get this HTML: 161 | 162 | ```html 163 | beautiful! 177 | ``` 178 | 179 | There is a true default `default` preset, but you're strongly encouraged to override it with your own default preset. 180 | 181 | #### Additional presets 182 | 183 | You can add other presets if you need several image sizes in your posts. 184 | 185 | Here is an example for images that take only one third of the post width: 186 | 187 | ```yaml 188 | cloudinary: 189 | … 190 | presets: 191 | … 192 | onethird: 193 | min_width: 110 194 | max_width: 535 195 | fallback_max_width: 300 196 | steps: 3 197 | sizes: "(min-width: 50rem) 17rem, 30vw" 198 | attributes: 199 | class: "one3rd" 200 | loading: "lazy" 201 | ``` 202 | 203 | To use this additional preset, you will have to write this in your Markdown post: 204 | 205 | 206 | ```liquid 207 | {% cloudinary onethird /assets/img.jpg %} 208 | ``` 209 | 210 | 211 | The generated element will also get a `class="one3rd"` that can be useful for example with this CSS: 212 | 213 | ```css 214 | .one3rd { 215 | max-width: 33%; 216 | float: right; 217 | margin: 0 0 1em 1em; 218 | } 219 | ``` 220 | 221 | ### Detailed preset settings 222 | 223 | #### `figure` (default: `auto`) 224 | 225 | This setting lets you decide what to do when there is a `caption` attribute in the Cloudinary Liquid tag. 226 | 227 | The value can be: 228 | 229 | - `auto` (default): will generate a `
` and `
` only if there's a caption 230 | - `never`: will always generate a ``, losing the caption 231 | - `always`: will always generate a `
` and `
`, even if there's no `caption` attribute 232 | 233 | If a `
` is generated and there are attributes (in the preset or the Liquid tag), they are added to the `` if they are `alt`, `title` or `loading`, or to the `
`. 234 | 235 | #### `min_width` (default: `320`) 236 | 237 | #### `max_width` (default: `1200`) 238 | 239 | #### `fallback_max_width` (defaut: `1200`) 240 | 241 | #### `steps` (default: `5`) 242 | 243 | #### `sizes` (default: `"100vw"`) 244 | 245 | #### `attributes` (default: none) 246 | 247 | You can define attributes that will be added to all images using this preset. Attributes are added without transformation to the generated element. 248 | 249 | You should obviously not add to preset attributes that should have different values for each image, such as `alt`, `caption`, `title`, etc. 250 | 251 | You can set a `class`, `aria-*` attributes for enhanced accessibility, or even `data-*` attributes you would like to use later with CSS or JavaScript. 252 | 253 | ## Liquid tag values 254 | 255 | You can use liquid variables inside the liquid tag. 256 | 257 | For example, if you have the picture path in a `thumbnail` attribute of the YAML Front Matter, you can use it in the tag: 258 | 259 | 260 | ```liquid 261 | {% cloudinary {{ page.thumbnail }} alt="{{ page.title }} image" %} 262 | ``` 263 | 264 | 265 | ## Liquid tag attributes 266 | 267 | You can add attributes to the liquid tag, after the image path: 268 | 269 | 270 | ```liquid 271 | {% cloudinary onethird /assets/selfie.jpg alt="My selfie" loading="eager" %} 272 | ``` 273 | 274 | 275 | Just like the ones from the preset settings, inline attributes are added without transformation to the generated element. 276 | 277 | ### Recommended attributes 278 | 279 | You should obviously define the `alt` attribute, mandatory for accessibility. 280 | 281 | If you want the image to be inside a `figure` element, you probably also want to add a `caption` attribute. This is the only one that can act differently than other attributes, depending on [the `figure` setting](#figure-default-auto). 282 | 283 | You can also set a `title` attribute, but there are really few use cases for it on images. 284 | 285 | `alt`, `caption` and `title` attributes can contain Markdown. 286 | 287 | ### Loading attribute 288 | 289 | The `loading` attribute allows you to tell the browser how you want it to load this image. 290 | 291 | From [this article written by Addy Osmani](https://addyosmani.com/blog/lazy-loading/): 292 | 293 | > The loading attribute allows a browser to defer loading offscreen images and iframes until users scroll near them. loading supports three values: 294 | > - `lazy`: is a good candidate for lazy loading. 295 | > - `eager`: is not a good candidate for lazy loading. Load right away. 296 | > - `auto`: browser will determine whether or not to lazily load. 297 | > 298 | > Not specifying the attribute at all will have the same impact as setting loading=auto. 299 | 300 | ### Other interesting attributes 301 | 302 | You can also use attributes to add a `class`, `aria-*` attributes for enhanced accessibility, or even `data-*` attributes you would like to use later with CSS or JavaScript. 303 | 304 | ## Live example 305 | 306 | Go to this post: [https://nicolas-hoizey.com/2016/07/tout-change-rien-ne-change.html](https://nicolas-hoizey.com/2016/07/tout-change-rien-ne-change.html). 307 | 308 | The source Markdown is here: [https://github.com/nhoizey/nicolas-hoizey.com/blob/master/_posts/2016/07/13-tout-change-rien-ne-change/2016-07-13-tout-change-rien-ne-change.md](https://github.com/nhoizey/nicolas-hoizey.com/blob/master/_posts/2016/07/13-tout-change-rien-ne-change/2016-07-13-tout-change-rien-ne-change.md). 309 | 310 | The content is in french, yes, but look only at the images if you don't understand. 311 | 312 | You'll find here: 313 | 314 | - 2 logos floating on the right of the text (or centered on smaller screens): [Jekyll](http://jekyllrb.com/) and [Cloudinary](https://nho.io/cloudinary-signup) 315 | - 2 screenshots taking the whole width of the content: the [Cloudinary pricing table](http://cloudinary.com/pricing), and [Dareboost](https://www.dareboost.com/en/home)'s performance monitoring graph 316 | 317 | These image types need different settings to deal with different sizes and position: 318 | 319 | - screenshot always use the full content width, if they're wide enough 320 | - logos are centered and take one half of the content width on small screens, and are floated and take one fourth of the content width on larger screens 321 | 322 | This is how I use the Cloudinary Liquid tag for the Cloudinary logo and prices table screenshot: 323 | 324 | 325 | ```liquid 326 | {% cloudinary logo /assets/logos/cloudinary.png alt="Logo de Cloudinary" %} 327 | {% cloudinary cloudinary-pricing.png alt="Les tarifs de Cloudinary" caption="Les tarifs de Cloudinary, dont l'offre gratuite déjà généreuse" %} 328 | ``` 329 | 330 | 331 | The only difference is that I explicitly use the `logo` preset for the logo. The other image uses the `default` preset. 332 | 333 | Here is the necessary configuration for this: 334 | 335 | ```yaml 336 | cloudinary: 337 | cloud_name: … 338 | verbose: false 339 | presets: 340 | default: 341 | min_width: 320 342 | max_width: 1600 343 | fallback_max_width: 800 344 | steps: 5 345 | sizes: '(min-width: 50rem) 50rem, 90vw' 346 | figure: always 347 | logo: 348 | min_width: 80 349 | max_width: 400 350 | fallback_max_width: 200 351 | steps: 3 352 | sizes: '(min-width: 50rem) 13rem, (min-width: 40rem) 25vw, 45vw' 353 | figure: never 354 | attributes: 355 | class: logo 356 | ``` 357 | 358 | It generates these HTML fragments (pretty printed here), for the logo: 359 | 360 | ```html 361 | 376 | ``` 377 | 378 | And for the screenshot: 379 | 380 | ```html 381 |
382 | Les tarifs de Cloudinary 394 |
Les tarifs de Cloudinary, dont l'offre gratuite déjà généreuse
395 |
396 | ``` 397 | 398 | There are only 4 version in the `srcset` here because 2 of the 5 expected sizes are larger than the source image, and are replaced by one using the native source image width. 399 | 400 | And here are the relevant parts of the accompanying CSS (in Sass form): 401 | 402 | ```sass 403 | article { 404 | figure, img { 405 | margin: 2em auto; 406 | display: block; 407 | max-width: 100%; 408 | height: auto; 409 | } 410 | } 411 | 412 | .logo { 413 | display: block; 414 | margin: 1em auto; 415 | max-width: 50%; 416 | height: auto; 417 | 418 | @media (min-width: 40em) { 419 | max-width: 25%; 420 | float: right; 421 | margin: 0 0 1em 1em; 422 | } 423 | } 424 | ``` 425 | 426 | ## Contributing 427 | 428 | Thanks for your interest in contributing! There are many ways to contribute to this project. [Get started here](./CONTRIBUTING.md). 429 | 430 | ## Do you use the plugin on a live site? 431 | 432 | Add it to [the "Sites" page of the wiki](./wiki/Sites) and please let us know on Twitter: [@mavaddat](https://twitter.com/mavaddat) [@nhoizey](https://twitter.com/nhoizey) 433 | --------------------------------------------------------------------------------