diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e99baefce..5e176f7325 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: MYSQL_PASSWORD: password MYSQL_DATABASE: alchemy_cms_dummy_test MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - uses: actions/checkout@v1 - name: Set up Ruby @@ -60,15 +60,15 @@ jobs: if: matrix.database == 'postgresql' run: | mkdir -p /home/runner/apt/cache - sudo apt-get update -qq - sudo apt-get install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache" + sudo apt update -qq + sudo apt install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache" sudo chown -R runner /home/runner/apt/cache - name: Install MySQL headers if: matrix.database == 'mysql' run: | mkdir -p /home/runner/apt/cache - sudo apt-get update -qq - sudo apt-get install -qq --fix-missing libmysqlclient-dev -o dir::cache::archives="/home/runner/apt/cache" + sudo apt update -qq + sudo apt install -qq --fix-missing libmysqlclient-dev -o dir::cache::archives="/home/runner/apt/cache" sudo chown -R runner /home/runner/apt/cache - name: Install bundler run: | @@ -85,11 +85,19 @@ jobs: timeout-minutes: 10 run: | bundle install --jobs 4 --retry 3 --path vendor/bundle + - name: Restore node modules cache + id: yarn-cache + uses: actions/cache@preview + with: + path: spec/dummy/node_modules + key: ${{ runner.os }}-yarn-dummy-${{ hashFiles('./package.json') }} + restore-keys: | + ${{ runner.os }}-yarn-dummy- - name: Prepare database run: | bundle exec rake alchemy:spec:prepare - name: Run tests & publish code coverage - uses: paambaati/codeclimate-action@v2.5.5 + uses: paambaati/codeclimate-action@v2.5.7 env: CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 with: @@ -99,3 +107,28 @@ jobs: with: name: Screenshots path: spec/dummy/tmp/screenshots + Jest: + runs-on: ubuntu-latest + env: + NODE_ENV: test + steps: + - uses: actions/checkout@v1 + - name: Restore node modules cache + uses: actions/cache@preview + with: + path: node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('./package.json') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install yarn + run: yarn install + - name: Run jest + run: yarn jest + - name: Run jest & publish code coverage + uses: paambaati/codeclimate-action@v2.5.7 + env: + CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 + with: + coverageLocations: + ./coverage/lcov.info:lcov + coverageCommand: yarn jest --collectCoverage --coverageDirectory=coverage diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 75f22b4762..262e51d5a9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,5 +13,5 @@ jobs: - uses: actions/stale@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue has not seen any activity in a long time. If the issue descriped still exists in recent versions of Alchemy, please open a new issue or preferably open a PR with a fix. Thanks for reporting.' + stale-issue-message: 'This issue has not seen any activity in a long time. If the issue described still exists in recent versions of Alchemy, please open a new issue or preferably open a PR with a fix. Thanks for reporting.' stale-pr-message: 'This pull request has not seen any activiy in a long time. Probably because of missing tests or a necessary rebase. Please open a new PR to latest master if you want to continue working on this. Thanks for the contribution.' diff --git a/.gitignore b/.gitignore index 3a28ffe484..a49a90c1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,22 @@ pkg tmp log .sass-cache -spec/dummy/uploads/ +spec/dummy/.browserslistrc +spec/dummy/app/assets/stylesheets/alchemy/ +spec/dummy/app/javascript/ +spec/dummy/babel.config.js +spec/dummy/bin/webpack +spec/dummy/bin/webpack-dev-server +spec/dummy/config/alchemy/config.yml +spec/dummy/config/webpack/ +spec/dummy/config/webpacker.yml spec/dummy/db/*.sqlite3* -spec/dummy/public/assets +spec/dummy/package.json +spec/dummy/postcss.config.js +spec/dummy/public/assets/ +spec/dummy/public/packs/ +spec/dummy/public/packs-test/ +spec/dummy/uploads/ .rvmrc /coverage/ *.gem @@ -22,3 +35,8 @@ spec/dummy/public/assets .ruby-version .env .rspec +node_modules +yarn-error.log +yarn-debug.log* +.yarn-integrity +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..348cb46f7b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "trailingComma": "none", + "vueIndentScriptAndStyle": true, + "arrowParens": "always" +} diff --git a/.rubocop.yml b/.rubocop.yml index 076dc5c8a0..05dd2cc324 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ # Relaxed.Ruby.Style AllCops: - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.4 Exclude: - 'bin/rspec' - 'vendor/**/*' @@ -9,6 +9,7 @@ AllCops: - 'spec/dummy/config/**/*' - 'alchemy_cms.gemspec' - 'Rakefile' + - 'node_modules/**/*' # Really, rubocop? Bundler/OrderedGems: @@ -220,8 +221,19 @@ Style/SpecialGlobalVars: StyleGuide: http://relaxed.ruby.style/#stylespecialglobalvars Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/TrailingCommaInArguments: Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylestringliterals + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: consistent_comma Style/WhileUntilModifier: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index f9002419dc..0e62bef2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,74 @@ ## 5.0.0 (unreleased) +- Language Factory: Create default language in host app's locale [#1884](https://github.com/AlchemyCMS/alchemy_cms/pull/1884) ([mamhoff](https://github.com/mamhoff)) +- Respect filter and tagging params in picture archive size buttons [#1880](https://github.com/AlchemyCMS/alchemy_cms/pull/1880) ([tvdeyen](https://github.com/tvdeyen)) +- Extract picture thumbnail sizes in a constant [#1879](https://github.com/AlchemyCMS/alchemy_cms/pull/1879) ([tvdeyen](https://github.com/tvdeyen)) +- Configurable Image Preprocessor [#1878](https://github.com/AlchemyCMS/alchemy_cms/pull/1878) ([tvdeyen](https://github.com/tvdeyen)) +- Configure edit page preview per site [#1877](https://github.com/AlchemyCMS/alchemy_cms/pull/1877) ([tvdeyen](https://github.com/tvdeyen)) +- Fix Page tree sorting after root page removal [#1876](https://github.com/AlchemyCMS/alchemy_cms/pull/1876) ([tvdeyen](https://github.com/tvdeyen)) +- 5.0 Upgrader fixes [#1874](https://github.com/AlchemyCMS/alchemy_cms/pull/1874) ([tvdeyen](https://github.com/tvdeyen)) +- Remove url_nesting config [#1872](https://github.com/AlchemyCMS/alchemy_cms/pull/1872) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade sassc to version 2.4.0 [#1871](https://github.com/AlchemyCMS/alchemy_cms/pull/1871) ([depfu](https://github.com/apps/depfu)) +- fix GitHub Actions spelling [#1869](https://github.com/AlchemyCMS/alchemy_cms/pull/1869) ([alexanderadam](https://github.com/alexanderadam)) +- Remove Page#visible [#1868](https://github.com/AlchemyCMS/alchemy_cms/pull/1868) ([tvdeyen](https://github.com/tvdeyen)) +- 4.6 backports for master [#1867](https://github.com/AlchemyCMS/alchemy_cms/pull/1867) ([tvdeyen](https://github.com/tvdeyen)) +- Use apt update instead of apt-get in GH action [#1866](https://github.com/AlchemyCMS/alchemy_cms/pull/1866) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade rubocop to version 0.85.0 [#1863](https://github.com/AlchemyCMS/alchemy_cms/pull/1863) ([depfu](https://github.com/apps/depfu)) +- Remove active_record_5_1? method [#1854](https://github.com/AlchemyCMS/alchemy_cms/pull/1854) ([tvdeyen](https://github.com/tvdeyen)) +- Use Alchemy npm package instead of hacking webpacker [#1853](https://github.com/AlchemyCMS/alchemy_cms/pull/1853) ([tvdeyen](https://github.com/tvdeyen)) +- Fix node select ES5 syntax [#1851](https://github.com/AlchemyCMS/alchemy_cms/pull/1851) ([tvdeyen](https://github.com/tvdeyen)) +- Run yarn:install after installing webpacker in install generator [#1850](https://github.com/AlchemyCMS/alchemy_cms/pull/1850) ([mamhoff](https://github.com/mamhoff)) +- Remove male sign after emoji [#1849](https://github.com/AlchemyCMS/alchemy_cms/pull/1849) ([mamhoff](https://github.com/mamhoff)) +- Do not use ES6 Syntax in Node Selector [#1846](https://github.com/AlchemyCMS/alchemy_cms/pull/1846) ([mamhoff](https://github.com/mamhoff)) +- [ruby] Upgrade rubocop to version 0.84.0 [#1845](https://github.com/AlchemyCMS/alchemy_cms/pull/1845) ([depfu](https://github.com/apps/depfu)) +- Always create nested urls [#1844](https://github.com/AlchemyCMS/alchemy_cms/pull/1844) ([tvdeyen](https://github.com/tvdeyen)) +- Fix: Add indifferent access to default options in encoded_image [#1840](https://github.com/AlchemyCMS/alchemy_cms/pull/1840) ([mickenorlen](https://github.com/mickenorlen)) +- Set proper nested set scope on page [#1837](https://github.com/AlchemyCMS/alchemy_cms/pull/1837) ([tvdeyen](https://github.com/tvdeyen)) +- Install Webpacker in install generator [#1835](https://github.com/AlchemyCMS/alchemy_cms/pull/1835) ([mamhoff](https://github.com/mamhoff)) +- Fix deleting an EssenceNode from a content [#1834](https://github.com/AlchemyCMS/alchemy_cms/pull/1834) ([mamhoff](https://github.com/mamhoff)) +- Use Rails standards for deleting pages from EssencePage [#1833](https://github.com/AlchemyCMS/alchemy_cms/pull/1833) ([mamhoff](https://github.com/mamhoff)) +- Scope has one site [#1832](https://github.com/AlchemyCMS/alchemy_cms/pull/1832) ([mamhoff](https://github.com/mamhoff)) +- Render nodes [#1831](https://github.com/AlchemyCMS/alchemy_cms/pull/1831) ([mamhoff](https://github.com/mamhoff)) +- Add errors when node cant be deleted [#1828](https://github.com/AlchemyCMS/alchemy_cms/pull/1828) ([mamhoff](https://github.com/mamhoff)) +- Add error flash to resource controller [#1827](https://github.com/AlchemyCMS/alchemy_cms/pull/1827) ([mamhoff](https://github.com/mamhoff)) +- Fix Association between Nodes and EssenceNodes [#1826](https://github.com/AlchemyCMS/alchemy_cms/pull/1826) ([mamhoff](https://github.com/mamhoff)) +- Translated root menus [#1825](https://github.com/AlchemyCMS/alchemy_cms/pull/1825) ([mamhoff](https://github.com/mamhoff)) +- Use rails root in install generator [#1822](https://github.com/AlchemyCMS/alchemy_cms/pull/1822) ([tvdeyen](https://github.com/tvdeyen)) +- Add a quick Node select [#1821](https://github.com/AlchemyCMS/alchemy_cms/pull/1821) ([mamhoff](https://github.com/mamhoff)) +- Add has_one association for root page [#1820](https://github.com/AlchemyCMS/alchemy_cms/pull/1820) ([mamhoff](https://github.com/mamhoff)) +- [js] Upgrade babel-jest to version 26.0.1 [#1819](https://github.com/AlchemyCMS/alchemy_cms/pull/1819) ([depfu](https://github.com/apps/depfu)) +- Make page language mandatory [#1818](https://github.com/AlchemyCMS/alchemy_cms/pull/1818) ([tvdeyen](https://github.com/tvdeyen)) +- Remove root page [#1817](https://github.com/AlchemyCMS/alchemy_cms/pull/1817) ([tvdeyen](https://github.com/tvdeyen)) +- Fix page unlock page icon replacement [#1816](https://github.com/AlchemyCMS/alchemy_cms/pull/1816) ([tvdeyen](https://github.com/tvdeyen)) +- Invoke rake task in upgrader instead of system call [#1815](https://github.com/AlchemyCMS/alchemy_cms/pull/1815) ([tvdeyen](https://github.com/tvdeyen)) +- Remove old 4.4 upgrader class [#1814](https://github.com/AlchemyCMS/alchemy_cms/pull/1814) ([tvdeyen](https://github.com/tvdeyen)) +- Remove Page.ancestors_for [#1813](https://github.com/AlchemyCMS/alchemy_cms/pull/1813) ([tvdeyen](https://github.com/tvdeyen)) +- Remove layout root pages [#1812](https://github.com/AlchemyCMS/alchemy_cms/pull/1812) ([tvdeyen](https://github.com/tvdeyen)) +- Use timestamps in migration [#1811](https://github.com/AlchemyCMS/alchemy_cms/pull/1811) ([tvdeyen](https://github.com/tvdeyen)) +- Remove legacy element serializer [#1810](https://github.com/AlchemyCMS/alchemy_cms/pull/1810) ([tvdeyen](https://github.com/tvdeyen)) +- Remove timestamps from essences and contents [#1809](https://github.com/AlchemyCMS/alchemy_cms/pull/1809) ([tvdeyen](https://github.com/tvdeyen)) +- Remove stamper from contents [#1808](https://github.com/AlchemyCMS/alchemy_cms/pull/1808) ([tvdeyen](https://github.com/tvdeyen)) +- Remove Site ID from nodes [#1807](https://github.com/AlchemyCMS/alchemy_cms/pull/1807) ([mamhoff](https://github.com/mamhoff)) +- Add Alchemy::Language.has_many :nodes [#1806](https://github.com/AlchemyCMS/alchemy_cms/pull/1806) ([mamhoff](https://github.com/mamhoff)) +- Drop Rails 5.0 and 5.1 support [#1805](https://github.com/AlchemyCMS/alchemy_cms/pull/1805) ([tvdeyen](https://github.com/tvdeyen)) +- Remove enforce_ssl [#1804](https://github.com/AlchemyCMS/alchemy_cms/pull/1804) ([tvdeyen](https://github.com/tvdeyen)) +- Make the preview url configurable [#1803](https://github.com/AlchemyCMS/alchemy_cms/pull/1803) ([tvdeyen](https://github.com/tvdeyen)) +- Remove stamper from essences [#1802](https://github.com/AlchemyCMS/alchemy_cms/pull/1802) ([tvdeyen](https://github.com/tvdeyen)) +- Use Rufo to format all files in a consistent way [#1799](https://github.com/AlchemyCMS/alchemy_cms/pull/1799) ([tvdeyen](https://github.com/tvdeyen)) +- Remove acts_as_list from Content [#1798](https://github.com/AlchemyCMS/alchemy_cms/pull/1798) ([tvdeyen](https://github.com/tvdeyen)) +- Add EssenceNode [#1792](https://github.com/AlchemyCMS/alchemy_cms/pull/1792) ([mamhoff](https://github.com/mamhoff)) +- Use 2.5.7 of code climate coverage reporter GH action [#1790](https://github.com/AlchemyCMS/alchemy_cms/pull/1790) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade sassc to version 2.3.0 [#1787](https://github.com/AlchemyCMS/alchemy_cms/pull/1787) ([depfu](https://github.com/apps/depfu)) +- [ruby] Upgrade rubocop to version 0.82.0 [#1785](https://github.com/AlchemyCMS/alchemy_cms/pull/1785) ([depfu](https://github.com/apps/depfu)) +- Fix regular icons [#1784](https://github.com/AlchemyCMS/alchemy_cms/pull/1784) ([tvdeyen](https://github.com/tvdeyen)) +- Convert NodeTree into ES6 [#1782](https://github.com/AlchemyCMS/alchemy_cms/pull/1782) ([tvdeyen](https://github.com/tvdeyen)) +- Add Webpacker [#1775](https://github.com/AlchemyCMS/alchemy_cms/pull/1775) ([tvdeyen](https://github.com/tvdeyen)) +- Multi language menus [#1774](https://github.com/AlchemyCMS/alchemy_cms/pull/1774) ([rmparr](https://github.com/rmparr)) +- On Boarding Flow [#1770](https://github.com/AlchemyCMS/alchemy_cms/pull/1770) ([tvdeyen](https://github.com/tvdeyen)) +- Fix bug in language from session w/o site [#1769](https://github.com/AlchemyCMS/alchemy_cms/pull/1769) ([tvdeyen](https://github.com/tvdeyen)) +- Fix fontawesome in production [#1765](https://github.com/AlchemyCMS/alchemy_cms/pull/1765) ([mickenorlen](https://github.com/mickenorlen)) +- Remove implicit Site and Language creation [#1763](https://github.com/AlchemyCMS/alchemy_cms/pull/1763) ([mamhoff](https://github.com/mamhoff)) +- Add content editor data attributes based on name/id and css_classes presenter method [#1761](https://github.com/AlchemyCMS/alchemy_cms/pull/1761) ([mickenorlen](https://github.com/mickenorlen)) - Add alchemy.test to development domains [#1760](https://github.com/AlchemyCMS/alchemy_cms/pull/1760) ([tvdeyen](https://github.com/tvdeyen)) - Update Fontawesome [#1759](https://github.com/AlchemyCMS/alchemy_cms/pull/1759) ([tvdeyen](https://github.com/tvdeyen)) - Fix test coverage reporting [#1757](https://github.com/AlchemyCMS/alchemy_cms/pull/1757) ([tvdeyen](https://github.com/tvdeyen)) @@ -24,6 +93,27 @@ - Add ContentEditor decorator [#1645](https://github.com/AlchemyCMS/alchemy_cms/pull/1645) ([tvdeyen](https://github.com/tvdeyen)) - Remove local options from essence editors [#1638](https://github.com/AlchemyCMS/alchemy_cms/pull/1638) ([tvdeyen](https://github.com/tvdeyen)) +## 4.6.1 (2020-06-04) + +- Fix 4.6 upgrader + +## 4.6.0 (2020-06-04) + +- Use apt update instead of apt-get in GH action [#1865](https://github.com/AlchemyCMS/alchemy_cms/pull/1865) ([tvdeyen](https://github.com/tvdeyen)) +- Use depth for page tree serializer root_or_leaf [#1864](https://github.com/AlchemyCMS/alchemy_cms/pull/1864) ([tvdeyen](https://github.com/tvdeyen)) +- Fix sitemap wrapper height [#1861](https://github.com/AlchemyCMS/alchemy_cms/pull/1861) ([tvdeyen](https://github.com/tvdeyen)) +- Do not return the root page with API responses. [#1860](https://github.com/AlchemyCMS/alchemy_cms/pull/1860) ([tvdeyen](https://github.com/tvdeyen)) +- Introduce page.url_path and use it for alchemyPageSelect [#1859](https://github.com/AlchemyCMS/alchemy_cms/pull/1859) ([tvdeyen](https://github.com/tvdeyen)) +- Update Urlname translation [#1857](https://github.com/AlchemyCMS/alchemy_cms/pull/1857) ([tvdeyen](https://github.com/tvdeyen)) +- Show url name in Page tree [#1856](https://github.com/AlchemyCMS/alchemy_cms/pull/1856) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate Page#visible attribute [#1855](https://github.com/AlchemyCMS/alchemy_cms/pull/1855) ([tvdeyen](https://github.com/tvdeyen)) +- 4.6: Re-add `auto_logout_time` configuration option [#1852](https://github.com/AlchemyCMS/alchemy_cms/pull/1852) ([mamhoff](https://github.com/mamhoff)) +- Backport ContentEditor to 4.6, deprecate removed methods on `Alchemy::Content` [#1847](https://github.com/AlchemyCMS/alchemy_cms/pull/1847) ([mamhoff](https://github.com/mamhoff)) +- Deprecate auto_logout_time (4.6) [#1843](https://github.com/AlchemyCMS/alchemy_cms/pull/1843) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate require_ssl (4.6) [#1842](https://github.com/AlchemyCMS/alchemy_cms/pull/1842) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate url_nesting configuration (4.6) [#1841](https://github.com/AlchemyCMS/alchemy_cms/pull/1841) ([tvdeyen](https://github.com/tvdeyen)) +- Allow page visible toggle (4.6) [#1838](https://github.com/AlchemyCMS/alchemy_cms/pull/1838) ([tvdeyen](https://github.com/tvdeyen)) + ## 4.5.0 (2020-03-30) - Sortable menus [#1758](https://github.com/AlchemyCMS/alchemy_cms/pull/1758) ([mamhoff](https://github.com/mamhoff)) diff --git a/Gemfile b/Gemfile index e43124be57..b1f343681e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,33 +1,34 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" gemspec -rails_version = ENV.fetch('RAILS_VERSION', 6.0).to_f -gem 'rails', "~> #{rails_version}.0" +rails_version = ENV.fetch("RAILS_VERSION", 6.0).to_f +gem "rails", "~> #{rails_version}.0" -if ENV['DB'].nil? || ENV['DB'] == 'sqlite' - gem 'sqlite3', rails_version > 5.0 ? '~> 1.4.1' : '~> 1.3.6' +if ENV["DB"].nil? || ENV["DB"] == "sqlite" + gem "sqlite3", "~> 1.4.1" end -gem 'mysql2', '~> 0.5.1' if ENV['DB'] == 'mysql' -gem 'pg', '~> 1.0' if ENV['DB'] == 'postgresql' +gem "mysql2", "~> 0.5.1" if ENV["DB"] == "mysql" +gem "pg", "~> 1.0" if ENV["DB"] == "postgresql" group :development, :test do - if ENV['GITHUB_ACTIONS'] - gem 'sassc', '~> 2.1.0' # https://github.com/sass/sassc-ruby/issues/146 + if ENV["GITHUB_ACTIONS"] + gem "sassc", "~> 2.4.0" # https://github.com/sass/sassc-ruby/issues/146 else - gem 'launchy' - gem 'annotate' - gem 'bumpy' - gem 'yard' - gem 'redcarpet' - gem 'pry-byebug' - gem 'rubocop', '~> 0.80.1', require: false - gem 'listen' - gem 'localeapp', '~> 3.0', require: false - gem 'dotenv', '~> 2.2' - gem 'github_fast_changelog', require: false - gem 'active_record_query_trace', require: false - gem 'rack-mini-profiler', require: false + gem "launchy" + gem "annotate" + gem "bumpy" + gem "yard" + gem "redcarpet" + gem "pry-byebug" + gem "rubocop", "~> 0.85.0", require: false + gem "listen" + gem "localeapp", "~> 3.0", require: false + gem "dotenv", "~> 2.2" + gem "github_fast_changelog", require: false + gem "active_record_query_trace", require: false + gem "rack-mini-profiler", require: false + gem "rufo", require: false end end diff --git a/README.md b/README.md index f559b8597b..a972eb19cf 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ or visit the existing demo at https://alchemy-demo.herokuapp.com ## 🚂 Rails Version -**This version of AlchemyCMS runs with all versions of Rails 5 and Rails 6** +**This version of AlchemyCMS runs with Rails 5.2 and Rails 6.0** +* For a Rails 5.0 or 5.1 compatible version use the [`4.5-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/4.5-stable). * For a Rails 4.2 compatible version use the [`3.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.6-stable). * For a Rails 4.0/4.1 compatible version use the [`3.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.1-stable). * For a Rails 3.2 compatible version use the [`2.8-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/2.8-stable). diff --git a/Rakefile b/Rakefile index f3c0b67156..494e284beb 100644 --- a/Rakefile +++ b/Rakefile @@ -45,6 +45,7 @@ namespace :alchemy do bin/rake db:create && \ bin/rake db:environment:set && \ bin/rake db:migrate:reset && \ + bin/rails g alchemy:install --skip --skip-demo-files && \ cd - BASH result || fail diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index 7d442a1b3c..74dec0ef7e 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -8,10 +8,10 @@ Gem::Specification.new do |gem| gem.version = Alchemy::VERSION gem.platform = Gem::Platform::RUBY gem.authors = ['Thomas von Deyen', 'Robin Boening', 'Marc Schettke', 'Hendrik Mans', 'Carsten Fregin', 'Martin Meyerhoff'] - gem.email = ['alchemy@magiclabs.de'] + gem.email = ['hello@alchemy-cms.com'] gem.homepage = 'https://alchemy-cms.com' - gem.summary = 'A powerful, userfriendly and flexible CMS for Rails 5' - gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails 5 CMS.' + gem.summary = 'A powerful, userfriendly and flexible CMS for Rails' + gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails CMS.' gem.requirements << 'ImageMagick (libmagick), v6.6 or greater.' gem.required_ruby_version = '>= 2.3.0' gem.license = 'BSD New' @@ -32,7 +32,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'kaminari', ['~> 1.1'] gem.add_runtime_dependency 'originator', ['~> 3.1'] gem.add_runtime_dependency 'non-stupid-digest-assets', ['~> 1.0.8'] - gem.add_runtime_dependency 'rails', ['>= 5.0.0', '< 6.1'] + gem.add_runtime_dependency 'rails', ['>= 5.2.0', '< 6.1'] gem.add_runtime_dependency 'ransack', ['>= 1.8', '< 3.0'] gem.add_runtime_dependency 'request_store', ['~> 1.2'] gem.add_runtime_dependency 'responders', ['>= 2.0', '< 4.0'] @@ -41,10 +41,11 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'simple_form', ['>= 4.0', '< 6'] gem.add_runtime_dependency 'sprockets', ['>= 3.0', '< 5'] gem.add_runtime_dependency 'turbolinks', ['>= 2.5'] + gem.add_runtime_dependency 'webpacker', ['>= 4.0', '< 6'] gem.add_development_dependency 'capybara', ['~> 3.0'] gem.add_development_dependency 'capybara-screenshot', ['~> 1.0'] - gem.add_development_dependency 'factory_bot_rails', ['~> 5.0'] + gem.add_development_dependency 'factory_bot_rails', ['~> 6.0'] gem.add_development_dependency 'puma', ['~> 4.0'] gem.add_development_dependency 'rails-controller-testing', ['~> 1.0'] gem.add_development_dependency 'rspec-activemodel-mocks', ['~> 1.0'] diff --git a/app/assets/javascripts/alchemy/admin.js b/app/assets/javascripts/alchemy/admin.js index 7b728dfd24..32c2d118e3 100644 --- a/app/assets/javascripts/alchemy/admin.js +++ b/app/assets/javascripts/alchemy/admin.js @@ -15,10 +15,8 @@ //= require requestAnimationFrame //= require select2 //= require handlebars -//= require sortable/Sortable.min //= require alchemy/templates //= require alchemy/alchemy.base -//= require alchemy/alchemy.utils //= require alchemy/alchemy.autocomplete //= require alchemy/alchemy.browser //= require alchemy/alchemy.buttons @@ -34,14 +32,12 @@ //= require alchemy/alchemy.growler //= require alchemy/alchemy.gui //= require alchemy/alchemy.hotkeys -//= require alchemy/alchemy.i18n //= require alchemy/alchemy.image_cropper //= require alchemy/alchemy.image_overlay //= require alchemy/alchemy.string_extension //= require alchemy/alchemy.link_dialog //= require alchemy/alchemy.list_filter //= require alchemy/alchemy.initializer -//= require alchemy/alchemy.node_tree //= require alchemy/alchemy.page_sorter //= require alchemy/alchemy.uploader //= require alchemy/alchemy.preview_window @@ -49,6 +45,6 @@ //= require alchemy/alchemy.spinner //= require alchemy/alchemy.tinymce //= require alchemy/alchemy.tooltips -//= require alchemy/alchemy.translations //= require alchemy/alchemy.trash_window //= require alchemy/page_select +//= require alchemy/node_select diff --git a/app/assets/javascripts/alchemy/alchemy.base.js.coffee b/app/assets/javascripts/alchemy/alchemy.base.js.coffee index db7ea0bbe8..20b7de18a8 100644 --- a/app/assets/javascripts/alchemy/alchemy.base.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.base.js.coffee @@ -62,9 +62,10 @@ $.extend Alchemy, removePicture: (selector) -> $form_field = $(selector) $element = $form_field.closest(".element-editor") + $content = $form_field.closest(".content_editor") if $form_field[0] $form_field.val "" - $element.find(".thumbnail_background").html('') + $content.find(".thumbnail_background").html('') Alchemy.setElementDirty $element false diff --git a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee index 1d86ff16f1..80ae7b18be 100644 --- a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee @@ -179,8 +179,6 @@ Alchemy.ElementEditors = if data.message == 'Alchemy.focusElementEditor' $element = $("#element_#{data.element_id}") Alchemy.ElementEditors.focusElement($element) - else - console.warn 'Unknown message received!', data onClickBody: (e) -> element = $(e.target).parents('.element-editor')[0] diff --git a/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee b/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee deleted file mode 100644 index 5f08c82c07..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee +++ /dev/null @@ -1,32 +0,0 @@ -#= require alchemy/alchemy.translations - -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -Alchemy.I18n = - - KEY_SEPARATOR: /\./ - - # Translates given string - # - translate: (key, replacement) -> - if !Alchemy.locale? - throw 'Alchemy.locale is not set! Please set Alchemy.locale to a locale string in order to translate something.' - translations = Alchemy.translations[Alchemy.locale] - if translations - if @KEY_SEPARATOR.test(key) - keys = key.split(@KEY_SEPARATOR) - translation = translations[keys[0]][keys[1]] || key - else - translation = translations[key] || key - if replacement - translation.replace(/%\{.+\}/, replacement) - else - translation - else - console.warn "Translations for locale #{Alchemy.locale} not found!" - key - -# Global utility method for translating a given string -# -Alchemy.t = (key, replacement) -> - Alchemy.I18n.translate(key, replacement) diff --git a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee index 46bf1bad57..cbc7e463b3 100644 --- a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee @@ -76,16 +76,16 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog meta = data.meta results: data.pages.map (page) -> - id: "/#{page.urlname}" + id: page.url_path name: page.name - urlname: page.urlname + url_path: page.url_path page_id: page.id more: meta.page * meta.per_page < meta.total_count initSelection: ($element, callback) => urlname = $element.val() $.get Alchemy.routes.api_pages_path, q: - urlname_eq: urlname.replace(/^\//, '') + urlname_eq: urlname.replace(/^\/([a-z]{2}(-[A-Z]{2})?\/)?/, '') page: 1 per_page: 1, (data) => @@ -93,9 +93,9 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog if page @initElementSelect(page.id) callback - id: "/#{page.urlname}" + id: page.url_path name: page.name - urlname: page.name + url_path: page.url_path page_id: page.id formatSelection: (page) -> page.name diff --git a/app/assets/javascripts/alchemy/alchemy.node_tree.js b/app/assets/javascripts/alchemy/alchemy.node_tree.js deleted file mode 100644 index 9213d8bb70..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.node_tree.js +++ /dev/null @@ -1,66 +0,0 @@ -Alchemy.NodeTree = { - onFinishDragging: function (evt) { - var url = Alchemy.routes.move_api_node_path(evt.item.dataset.id) - var data = { - target_parent_id: evt.to.dataset.nodeId, - new_position: evt.newIndex - }; - var ajax = Alchemy.ajax('PATCH', url, data) - - ajax.then(function(response) { - Alchemy.growl('Successfully moved menu item.') - Alchemy.NodeTree.displayNodeFolders() - }).catch(function() { - Alchemy.growl(error.message || error); - }) - }, - - displayNodeFolders: function () { - document.querySelectorAll('li.menu-item').forEach(function (el) { - var leftIconArea = el.querySelector('.nodes_tree-left_images') - var list = el.querySelector('ul') - var node = { folded: el.dataset.folded === 'true', id: el.dataset.id } - - if (list.children.length > 0 || node.folded ) { - leftIconArea.innerHTML = HandlebarsTemplates.node_folder({ node: node }) - } else { - leftIconArea.innerHTML = ' ' - } - }); - }, - - handleNodeFolders: function() { - Alchemy.on('click', '.nodes_tree', '.node_folder', function(evt) { - var nodeId = this.dataset.nodeId - var menu_item = this.closest('li.menu-item') - var url = Alchemy.routes.toggle_folded_api_node_path(nodeId) - var list = menu_item.querySelector('.children') - var ajax = Alchemy.ajax('PATCH', url) - - ajax.then(function() { - list.classList.toggle('folded') - menu_item.dataset.folded = menu_item.dataset.folded == 'true' ? 'false' : 'true' - Alchemy.NodeTree.displayNodeFolders(); - }).catch(function(error){ - Alchemy.growl(error.message || error); - }); - }); - }, - - init: function() { - this.handleNodeFolders() - this.displayNodeFolders() - - document.querySelectorAll('.nodes_tree ul.children').forEach(function (el) { - new Sortable(el, { - group: 'nodes', - animation: 150, - fallbackOnBody: true, - swapThreshold: 0.65, - handle: '.node_name', - invertSwap: true, - onEnd: Alchemy.NodeTree.onFinishDragging - }); - }); - } -} diff --git a/app/assets/javascripts/alchemy/alchemy.page_sorter.js b/app/assets/javascripts/alchemy/alchemy.page_sorter.js index df57ce4be1..9c4039c97d 100644 --- a/app/assets/javascripts/alchemy/alchemy.page_sorter.js +++ b/app/assets/javascripts/alchemy/alchemy.page_sorter.js @@ -1,24 +1,24 @@ -Alchemy.PageSorter = function() { - var $sortables = $('ul#sitemap').find('ul.level_1_children'); +Alchemy.PageSorter = function () { + var $sortables = $("ul#sitemap").find("ul.level_0_children") $sortables.nestedSortable({ - disableNesting: 'no-nest', + disableNesting: "no-nest", forcePlaceholderSize: true, - handle: '.handle', - items: 'li', - listType: 'ul', + handle: ".handle", + items: "li", + listType: "ul", opacity: 0.5, - placeholder: 'placeholder', + placeholder: "placeholder", tabSize: 16, - tolerance: 'pointer', - toleranceElement: '> div' - }); + tolerance: "pointer", + toleranceElement: "> div" + }) - $('#save_page_order').click(function(e) { - e.preventDefault(); - Alchemy.Buttons.disable(this); + $("#save_page_order").click(function (e) { + e.preventDefault() + Alchemy.Buttons.disable(this) $.post(Alchemy.routes.order_admin_pages_path, { - set: JSON.stringify($sortables.nestedSortable('toHierarchy')) - }); - }); -}; + set: JSON.stringify($sortables.nestedSortable("toHierarchy")) + }) + }) +} diff --git a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee b/app/assets/javascripts/alchemy/alchemy.translations.js.coffee deleted file mode 100644 index 57e2f84d49..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +++ /dev/null @@ -1,29 +0,0 @@ -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -# Holds translations for javascripts -# -Alchemy.translations = - en: - allowed_chars: 'of %{count} chars' - cancel: 'Cancel' - cancelled: 'Cancelled' - click_to_edit: 'click to edit' - complete: 'Complete' - element_dirty_notice: 'This element has unsaved changes. Do you really want to fold it?' - help: 'Help' - ok: 'Ok' - page_dirty_notice: 'You have unsaved changes on this page. They will be lost if you continue.' - page_found: 'Page found' - pages_found: 'Pages found' - url_validation_failed: 'The url has no valid format.' - warning: 'Warning!' - 'File is too large': 'File is too large' - 'File is too small': 'File is too small' - 'File type not allowed': 'File type not allowed' - 'Maximum number of files exceeded': 'Maximum number of files exceeded.' - 'Uploaded bytes exceed file size': 'Uploaded bytes exceed file size' - formats: - datetime: "Y-m-d H:i" - date: "Y-m-d" - time: "H:i" - time_24hr: false diff --git a/app/assets/javascripts/alchemy/alchemy.utils.js b/app/assets/javascripts/alchemy/alchemy.utils.js deleted file mode 100644 index 5c35fa08e0..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.utils.js +++ /dev/null @@ -1,45 +0,0 @@ -Alchemy.on = function (eventName, baseSelector, targetSelector, callback) { - var baseNode = document.querySelector(baseSelector) - baseNode.addEventListener(eventName, function (evt) { - var targets = Array.from(baseNode.querySelectorAll(targetSelector)) - var currentNode = evt.target - while (currentNode !== baseNode) { - if (targets.includes(currentNode)) { - callback.call(currentNode, evt) - return - } - currentNode = currentNode.parentElement - } - }); -} - -Alchemy.ajax = function(method, url, data) { - var xhr = new XMLHttpRequest() - var token = document.querySelector('meta[name="csrf-token"]').attributes.content.textContent - var promise = new Promise(function (resolve, reject) { - xhr.onload = function() { - try { - resolve({ - data: JSON.parse(xhr.responseText), - status: xhr.status - }) - } catch (error) { - reject(new Error(JSON.parse(xhr.responseText).error)) - } - }; - xhr.onerror = function() { - reject(new Error(xhr.statusText)) - } - }); - xhr.open(method, url); - xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('X-CSRF-Token', token) - if (data) { - xhr.send(JSON.stringify(data)) - } else { - xhr.send() - } - - return promise -} diff --git a/app/assets/javascripts/alchemy/node_select.js b/app/assets/javascripts/alchemy/node_select.js new file mode 100644 index 0000000000..a358703905 --- /dev/null +++ b/app/assets/javascripts/alchemy/node_select.js @@ -0,0 +1,39 @@ +$.fn.alchemyNodeSelect = function (options) { + var renderNodeTemplate = function (node) { + return HandlebarsTemplates.node({ node: node }) + } + var queryParamsFromTerm = function (term) { + return { + filter: Object.assign( + { name_or_page_name_cont: term }, + options.query_params + ) + } + } + var resultsFromResponse = function (response) { + var meta = response.meta + var data = response.data + var more = meta.page * meta.per_page < meta.total_count + return { results: data, more: more } + } + + return this.select2({ + placeholder: options.placeholder, + allowClear: true, + minimumInputLength: 3, + initSelection: function (_$el, callback) { + if (options.initialSelection) { + callback(options.initialSelection) + } + }, + ajax: { + url: options.url, + datatype: "json", + quietMillis: 300, + data: queryParamsFromTerm, + results: resultsFromResponse + }, + formatSelection: renderNodeTemplate, + formatResult: renderNodeTemplate + }) +} diff --git a/app/assets/javascripts/alchemy/templates/index.js b/app/assets/javascripts/alchemy/templates/index.js index 0405d22de6..acbaf8b3db 100644 --- a/app/assets/javascripts/alchemy/templates/index.js +++ b/app/assets/javascripts/alchemy/templates/index.js @@ -1,3 +1,4 @@ //= require alchemy/templates/spinner //= require alchemy/templates/page //= require alchemy/templates/node_folder +//= require alchemy/templates/node diff --git a/app/assets/javascripts/alchemy/templates/node.hbs b/app/assets/javascripts/alchemy/templates/node.hbs new file mode 100644 index 0000000000..50d487d912 --- /dev/null +++ b/app/assets/javascripts/alchemy/templates/node.hbs @@ -0,0 +1,16 @@ +
+ +
+ + {{#each node.ancestors}} + {{ this.name }} /  + {{/each}} + + + {{ node.name }} + +
+
+ {{ node.url }} +
+
diff --git a/app/assets/javascripts/alchemy/templates/page.hbs b/app/assets/javascripts/alchemy/templates/page.hbs index 1686cacbe1..81748efe20 100644 --- a/app/assets/javascripts/alchemy/templates/page.hbs +++ b/app/assets/javascripts/alchemy/templates/page.hbs @@ -4,6 +4,6 @@ {{ page.name }} - /{{ page.urlname }} + {{ page.url_path }} diff --git a/app/assets/stylesheets/alchemy/_mixins.scss b/app/assets/stylesheets/alchemy/_mixins.scss index f68687d3f4..2251b133b6 100644 --- a/app/assets/stylesheets/alchemy/_mixins.scss +++ b/app/assets/stylesheets/alchemy/_mixins.scss @@ -49,9 +49,8 @@ border-color: $hover-border-color; } - &:active, &:active:focus { - border-color: $hover-border-color; - box-shadow: none; + &:active, &.active { + box-shadow: inset $button-box-shadow; } &:focus { diff --git a/app/assets/stylesheets/alchemy/_variables.scss b/app/assets/stylesheets/alchemy/_variables.scss index 343c204b5e..6bb3e2bd14 100644 --- a/app/assets/stylesheets/alchemy/_variables.scss +++ b/app/assets/stylesheets/alchemy/_variables.scss @@ -76,7 +76,7 @@ $button-text-shadow: none !default; $button-box-shadow: 0px 1px 1px -1px #333 !default; $button-focus-box-shadow: 0px 1px 1px 0px $button-focus-border-color !default; $button-padding: 0.55em 2em !default; -$small-button-padding: 0.4em 1.25em !default; +$small-button-padding: 0.4em 0.8em !default; $button-margin: $form-field-margin !default; $secondary-button-bg-color: transparent !default; @@ -108,7 +108,7 @@ $form-right-width: 65% !default; $sitemap-line-height: 32px !default; $sitemap-page-background-color: rgba($white, 0.75) !default; $sitemap-page-hover-color: rgba($light_yellow, 0.5) !default; -$sitemap-info-background-color: rgba($linked-color, 0.5) !default; +$sitemap-info-background-color: rgba($white, 0.5) !default; $sitemap-highlight-color: rgba(#fffba5, 0.5) !default; $main-menu-width: 150px !default; diff --git a/app/assets/stylesheets/alchemy/admin.scss b/app/assets/stylesheets/alchemy/admin.scss index 16e2418dc8..e657f50327 100644 --- a/app/assets/stylesheets/alchemy/admin.scss +++ b/app/assets/stylesheets/alchemy/admin.scss @@ -31,6 +31,7 @@ @import "alchemy/image_library"; @import "alchemy/labels"; @import "alchemy/nodes"; +@import "alchemy/node-select"; @import "alchemy/notices"; @import "alchemy/page-select"; @import "alchemy/pagination"; diff --git a/app/assets/stylesheets/alchemy/lists.scss b/app/assets/stylesheets/alchemy/lists.scss index 6463353737..890a52be88 100644 --- a/app/assets/stylesheets/alchemy/lists.scss +++ b/app/assets/stylesheets/alchemy/lists.scss @@ -13,14 +13,6 @@ ul.list { padding: 0; list-style-type: none; - &#layoutpages { - margin-top: 16px; - - li { - margin-left: 8px; - } - } - li { list-style-type: none; display: block; diff --git a/app/assets/stylesheets/alchemy/node-select.scss b/app/assets/stylesheets/alchemy/node-select.scss new file mode 100644 index 0000000000..ebccde55db --- /dev/null +++ b/app/assets/stylesheets/alchemy/node-select.scss @@ -0,0 +1,43 @@ +.node-select--node, +.node-select--node-url { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.node-select--node { + display: flex; + align-items: center; + + .icon { + margin: 0 8px 0 4px; + + .select2-highlighted & { + color: $white + } + } +} + +.node-select--node-name { + font-weight: bold; +} + +.node-select--node-url { + margin-left: auto; + padding: $default-padding 2*$default-padding; + color: $dark-gray; + font-size: $small-font-size; + + .select2-highlighted & { + color: $white + } +} + +// The container of the rendered node is slightly larger than other a line of +// text, as it would be for the Alchemy::EssencePage. Reducing the padding here from 0.6em +// to 0.4em centers the content nicely. +.essence_node { + .select2-container.alchemy_selectbox .select2-choice { + padding: 0.4em 0.75em; + } +} diff --git a/app/assets/stylesheets/alchemy/nodes.scss b/app/assets/stylesheets/alchemy/nodes.scss index c35731998c..4c2aa6512f 100644 --- a/app/assets/stylesheets/alchemy/nodes.scss +++ b/app/assets/stylesheets/alchemy/nodes.scss @@ -18,7 +18,7 @@ .node_page, .node_url { - width: 200px; + width: 250px; max-width: 45%; white-space: nowrap; text-overflow: ellipsis; @@ -64,7 +64,7 @@ margin: 0; padding: 0; - .folded > li { + &.folded > li { display: none; } } diff --git a/app/assets/stylesheets/alchemy/sitemap.scss b/app/assets/stylesheets/alchemy/sitemap.scss index 35fb24957c..13c5fc24a0 100644 --- a/app/assets/stylesheets/alchemy/sitemap.scss +++ b/app/assets/stylesheets/alchemy/sitemap.scss @@ -1,3 +1,6 @@ +$sitemap-url-large-width: 250px; +$sitemap-url-xlarge-width: 350px; + #sort_panel { background: $light-gray; padding: 47px 0 8px 0; @@ -20,7 +23,7 @@ #sitemap-wrapper { position: relative; - min-height: 100%; + min-height: calc(100vh - 96px); } .sitemap_pagename_link { @@ -28,23 +31,35 @@ padding: 0 10px; margin: 2px; text-decoration: none; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; &.inactive { color: #656565; } } -.redirect_url { +.sitemap_url { + display: none; float: right; - text-align: right; background-color: $sitemap-info-background-color; - line-height: $sitemap-line-height; + line-height: $sitemap-line-height - 2px; font-size: $small-font-size; - padding: 0 2*$default-padding; - max-width: 45%; + padding: 0 2 * $default-padding; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + border: 1px solid $sitemap-page-background-color; + + @media screen and (min-width: $large-screen-break-point) { + display: block; + width: $sitemap-url-large-width; + } + + @media screen and (min-width: 1440px) { + width: $sitemap-url-xlarge-width; + } } .sitemap_line_spacer { @@ -55,7 +70,7 @@ .sitemap_page { height: $sitemap-line-height; - margin: 3*$default-margin 0; + margin: 3 * $default-margin 0; position: relative; transition: background-color $transition-duration; @@ -89,13 +104,13 @@ width: 32px; line-height: $sitemap-line-height; float: left; - padding: 0 2*$default-padding; + padding: 0 2 * $default-padding; text-align: center; } .sitemap_right_tools { height: $sitemap-line-height; - padding: 0 2*$default-padding; + padding: 0 2 * $default-padding; float: right; .sitemap_tool { @@ -130,7 +145,9 @@ &.sorting { padding-top: 100px; - .page_icon { cursor: move } + .page_icon { + cursor: move; + } } .page_folder { @@ -180,28 +197,51 @@ .page_status { display: inline-block; + + .alchemy-dialog & { + display: block; + } } #sitemap_heading { + display: flex; padding: 0; - - .page_infos { - margin-right: 210px; - text-align: left; - float: right; - line-height: 28px; - background: transparent; - } + line-height: 28px; .page_name { - line-height: 28px; margin-left: 43px; } + + .page_urlname { + display: none; + margin-left: auto; + padding-left: 2 * $default-padding; + padding-right: 2 * $default-padding; + + @media screen and (min-width: $large-screen-break-point) { + display: block; + width: $sitemap-url-large-width; + } + + @media screen and (min-width: 1440px) { + width: $sitemap-url-xlarge-width; + } + } + + .page_status { + padding-left: 2 * $default-padding; + margin-right: 188px; + margin-left: auto; + + @media screen and (min-width: $large-screen-break-point) { + margin-left: initial; + } + } } #page_filter_result { display: none; - margin-left: 2*$default-margin; + margin-left: 2 * $default-margin; } .alchemy-dialog { @@ -213,6 +253,8 @@ margin: 0; padding: 0 24px 8px 8px; - .page_icon { cursor: default } + .page_icon { + cursor: default; + } } } diff --git a/app/controllers/alchemy/admin/attachments_controller.rb b/app/controllers/alchemy/admin/attachments_controller.rb index 54eecdc262..47bc3ca362 100644 --- a/app/controllers/alchemy/admin/attachments_controller.rb +++ b/app/controllers/alchemy/admin/attachments_controller.rb @@ -6,11 +6,11 @@ class AttachmentsController < ResourcesController include UploaderResponses include ArchiveOverlay - helper 'alchemy/admin/tags' + helper "alchemy/admin/tags" def index @query = Attachment.ransack(search_filter_params[:q]) - @query.sorts = 'name asc' if @query.sorts.empty? + @query.sorts = "name asc" if @query.sorts.empty? @attachments = @query.result if search_filter_params[:tagged_with].present? @@ -42,13 +42,13 @@ def create def update @attachment.update(attachment_attributes) - if attachment_attributes['file'].present? + if attachment_attributes["file"].present? handle_uploader_response(status: :accepted) else render_errors_or_redirect( @attachment, admin_attachments_path(search_filter_params), - Alchemy.t("File successfully updated") + Alchemy.t("File successfully updated"), ) end end @@ -57,14 +57,14 @@ def destroy name = @attachment.name @attachment.destroy @url = admin_attachments_url(search_filter_params) - flash[:notice] = Alchemy.t('File deleted successfully', name: name) + flash[:notice] = Alchemy.t("File deleted successfully", name: name) end def download @attachment = Attachment.find(params[:id]) send_file @attachment.file.path, { filename: @attachment.file_name, - type: @attachment.file_mime_type + type: @attachment.file_mime_type, } end @@ -74,8 +74,8 @@ def search_filter_params @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:attachment]).permit( *common_search_filter_includes + [ :file_type, - :content_id - ] + :content_id, + ], ) end diff --git a/app/controllers/alchemy/admin/base_controller.rb b/app/controllers/alchemy/admin/base_controller.rb index 4e3e16c3cb..63a0a4e34e 100644 --- a/app/controllers/alchemy/admin/base_controller.rb +++ b/app/controllers/alchemy/admin/base_controller.rb @@ -6,7 +6,6 @@ class BaseController < Alchemy::BaseController include Userstamp include Locale - before_action { enforce_ssl if ssl_required? && !request.ssl? } before_action :load_locked_pages helper_method :clipboard_empty?, :trash_empty?, :get_clipboard, :is_admin? @@ -27,14 +26,14 @@ class BaseController < Alchemy::BaseController def leave authorize! :leave, :alchemy_admin - render template: '/alchemy/admin/leave', layout: !request.xhr? + render template: "/alchemy/admin/leave", layout: !request.xhr? end private # Disable layout rendering for xhr requests. def set_layout - request.xhr? ? false : 'alchemy/admin' + request.xhr? ? false : "alchemy/admin" end # Handles exceptions @@ -55,7 +54,7 @@ def show_error_notice(error) if request.xhr? render action: "error_notice" else - render '500', status: 500 + render "500", status: 500 end end @@ -105,7 +104,7 @@ def render_errors_or_redirect(object, redirect_url, flash_notice) flash[:notice] = Alchemy.t(flash_notice) do_redirect_to redirect_url else - render action: (params[:action] == 'update' ? 'edit' : 'new') + render action: (params[:action] == "update" ? "edit" : "new") end end @@ -113,7 +112,7 @@ def render_errors_or_redirect(object, redirect_url, flash_notice) # def do_redirect_to(url_or_path) respond_to do |format| - format.js { + format.js { @redirect_url = url_or_path render :redirect } @@ -131,7 +130,7 @@ def raise_exception? # Are we currently in the page edit mode page preview. def is_page_preview? - controller_path == 'alchemy/admin/pages' && action_name == 'show' + controller_path == "alchemy/admin/pages" && action_name == "show" end def load_locked_pages @@ -142,11 +141,11 @@ def load_locked_pages # def current_alchemy_site @current_alchemy_site ||= begin - site_id = params[:site_id] || session[:alchemy_site_id] - site = Site.find_by(id: site_id) || super - session[:alchemy_site_id] = site&.id - site - end + site_id = params[:site_id] || session[:alchemy_site_id] + site = Site.find_by(id: site_id) || super + session[:alchemy_site_id] = site&.id + site + end end end end diff --git a/app/controllers/alchemy/admin/clipboard_controller.rb b/app/controllers/alchemy/admin/clipboard_controller.rb index 3977e72ede..06c6733f4a 100644 --- a/app/controllers/alchemy/admin/clipboard_controller.rb +++ b/app/controllers/alchemy/admin/clipboard_controller.rb @@ -17,10 +17,10 @@ def index def insert @item = model_class.find(remarkable_params[:remarkable_id]) - unless @clipboard.detect { |item| item['id'] == remarkable_params[:remarkable_id] } + unless @clipboard.detect { |item| item["id"] == remarkable_params[:remarkable_id] } @clipboard << { - 'id' => remarkable_params[:remarkable_id], - 'action' => params[:remove] ? 'cut' : 'copy' + "id" => remarkable_params[:remarkable_id], + "action" => params[:remove] ? "cut" : "copy", } end respond_to do |format| @@ -30,7 +30,7 @@ def insert def remove @item = model_class.find(remarkable_params[:remarkable_id]) - @clipboard.delete_if { |item| item['id'] == remarkable_params[:remarkable_id] } + @clipboard.delete_if { |item| item["id"] == remarkable_params[:remarkable_id] } respond_to do |format| format.js end diff --git a/app/controllers/alchemy/admin/contents_controller.rb b/app/controllers/alchemy/admin/contents_controller.rb index d4685c266e..13b460dc69 100644 --- a/app/controllers/alchemy/admin/contents_controller.rb +++ b/app/controllers/alchemy/admin/contents_controller.rb @@ -3,7 +3,7 @@ module Alchemy module Admin class ContentsController < Alchemy::Admin::BaseController - helper 'alchemy/admin/essences' + helper "alchemy/admin/essences" authorize_resource class: Alchemy::Content diff --git a/app/controllers/alchemy/admin/dashboard_controller.rb b/app/controllers/alchemy/admin/dashboard_controller.rb index 0ec91fcedc..cde544c502 100644 --- a/app/controllers/alchemy/admin/dashboard_controller.rb +++ b/app/controllers/alchemy/admin/dashboard_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'net/http' -require 'alchemy/version' +require "net/http" +require "alchemy/version" module Alchemy module Admin @@ -28,9 +28,9 @@ def info def update_check @alchemy_version = Alchemy.version if @alchemy_version < latest_alchemy_version - render plain: 'true' + render plain: "true" else - render plain: 'false' + render plain: "false" end rescue UpdateServiceUnavailable => e render plain: e, status: 503 @@ -41,7 +41,7 @@ def update_check # Returns latest alchemy version. def latest_alchemy_version versions = get_alchemy_versions - return '' if versions.blank? + return "" if versions.blank? # reject any non release version versions.reject! { |v| v =~ /[a-z]/ } @@ -54,14 +54,14 @@ def get_alchemy_versions response = query_rubygems if response.code == "200" alchemy_versions = JSON.parse(response.body) - alchemy_versions.collect { |h| h['number'] }.sort + alchemy_versions.collect { |h| h["number"] }.sort else # rubygems.org not available? # then we try github response = query_github if response.code == "200" alchemy_tags = JSON.parse(response.body) - alchemy_tags.collect { |h| h['name'].tr('v', '') }.sort + alchemy_tags.collect { |h| h["name"].tr("v", "") }.sort else # no luck at all? raise UpdateServiceUnavailable @@ -71,12 +71,12 @@ def get_alchemy_versions # Query the RubyGems API for Alchemy versions. def query_rubygems - make_api_request('https://rubygems.org/api/v1/versions/alchemy_cms.json') + make_api_request("https://rubygems.org/api/v1/versions/alchemy_cms.json") end # Query the GitHub API for Alchemy tags. def query_github - make_api_request('https://api.github.com/repos/AlchemyCMS/alchemy_cms/tags') + make_api_request("https://api.github.com/repos/AlchemyCMS/alchemy_cms/tags") end # Make a HTTP API request for given request url. diff --git a/app/controllers/alchemy/admin/elements_controller.rb b/app/controllers/alchemy/admin/elements_controller.rb index 11520da98a..54ad29933c 100644 --- a/app/controllers/alchemy/admin/elements_controller.rb +++ b/app/controllers/alchemy/admin/elements_controller.rb @@ -17,7 +17,7 @@ def new @parent_element = Element.find_by(id: params[:parent_element_id]) @elements = @page.available_elements_within_current_scope(@parent_element) @element = @page.elements.build - @clipboard = get_clipboard('elements') + @clipboard = get_clipboard("elements") @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page) end @@ -30,7 +30,7 @@ def create else @element = Element.create(create_element_params) end - if @page.definition['insert_elements_at'] == 'top' + if @page.definition["insert_elements_at"] == "top" @insert_at_top = true @element.move_to_top end @@ -40,7 +40,7 @@ def create else @element.page = @page @elements = @page.available_element_definitions - @clipboard = get_clipboard('elements') + @clipboard = get_clipboard("elements") @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page) render :new end @@ -56,7 +56,7 @@ def update @element_validated = @element.update(element_params) else @element_validated = false - @notice = Alchemy.t('Validation failed') + @notice = Alchemy.t("Validation failed") @error_message = "

#{@notice}

#{Alchemy.t(:content_validations_headline)}

".html_safe end end @@ -81,7 +81,7 @@ def order Element.where(id: element_id).update_all( page_id: params[:page_id], parent_element_id: params[:parent_element_id], - position: idx + 1 + position: idx + 1, ) end @parent_element.try!(:touch) @@ -100,20 +100,20 @@ def element_includes [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, :tags, { all_nested_elements: [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] - } + :tags, + ], + }, ] end @@ -123,20 +123,20 @@ def load_element def element_from_clipboard @element_from_clipboard ||= begin - @clipboard = get_clipboard('elements') - @clipboard.detect { |item| item['id'].to_i == params[:paste_from_clipboard].to_i } - end + @clipboard = get_clipboard("elements") + @clipboard.detect { |item| item["id"].to_i == params[:paste_from_clipboard].to_i } + end end def paste_element_from_clipboard - @source_element = Element.find(element_from_clipboard['id']) + @source_element = Element.find(element_from_clipboard["id"]) element = Element.copy(@source_element, { parent_element_id: create_element_params[:parent_element_id], - page_id: @page.id} - ) - if element_from_clipboard['action'] == 'cut' + page_id: @page.id, + }) + if element_from_clipboard["action"] == "cut" @cut_element_id = @source_element.id - @clipboard.delete_if { |item| item['id'] == @source_element.id.to_s } + @clipboard.delete_if { |item| item["id"] == @source_element.id.to_s } @source_element.destroy end element diff --git a/app/controllers/alchemy/admin/essence_pictures_controller.rb b/app/controllers/alchemy/admin/essence_pictures_controller.rb index 4bc85b2c9e..9656635ec5 100644 --- a/app/controllers/alchemy/admin/essence_pictures_controller.rb +++ b/app/controllers/alchemy/admin/essence_pictures_controller.rb @@ -9,9 +9,9 @@ class EssencePicturesController < Alchemy::Admin::BaseController before_action :load_essence_picture, only: [:edit, :crop, :update] before_action :load_content, only: [:edit, :update, :assign] - helper 'alchemy/admin/contents' - helper 'alchemy/admin/essences' - helper 'alchemy/url' + helper "alchemy/admin/contents" + helper "alchemy/admin/essences" + helper "alchemy/url" def edit end diff --git a/app/controllers/alchemy/admin/languages_controller.rb b/app/controllers/alchemy/admin/languages_controller.rb index b3f97d30b9..402ee0e395 100644 --- a/app/controllers/alchemy/admin/languages_controller.rb +++ b/app/controllers/alchemy/admin/languages_controller.rb @@ -14,14 +14,14 @@ def index def new @language = Language.new( site: @current_site, - page_layout: Config.get(:default_language)['page_layout'] + page_layout: Config.get(:default_language)["page_layout"], ) end def create @language = Alchemy::Language.new(resource_params) if @language.save - flash[:notice] = Alchemy.t('Language successfully created') + flash[:notice] = Alchemy.t("Language successfully created") redirect_to alchemy.admin_pages_path(language_id: @language) else render :new @@ -30,7 +30,7 @@ def create def destroy if @language.destroy - flash[:notice] = Alchemy.t('Language successfully removed') + flash[:notice] = Alchemy.t("Language successfully removed") else flash[:warning] = @language.errors.full_messages.to_sentence end @@ -47,7 +47,7 @@ def switch def load_current_site @current_site = Alchemy::Site.current if @current_site.nil? - flash[:warning] = Alchemy.t('Please create a site first.') + flash[:warning] = Alchemy.t("Please create a site first.") redirect_to admin_sites_path end end diff --git a/app/controllers/alchemy/admin/layoutpages_controller.rb b/app/controllers/alchemy/admin/layoutpages_controller.rb index 23a2e42765..2279f19e42 100644 --- a/app/controllers/alchemy/admin/layoutpages_controller.rb +++ b/app/controllers/alchemy/admin/layoutpages_controller.rb @@ -10,7 +10,7 @@ class LayoutpagesController < Alchemy::Admin::BaseController helper Alchemy::Admin::PagesHelper def index - @layout_root = Page.find_or_create_layout_root_for(@current_language.id) + @layout_pages = Page.layoutpages.where(language: @current_language) @languages = Language.on_current_site end diff --git a/app/controllers/alchemy/admin/nodes_controller.rb b/app/controllers/alchemy/admin/nodes_controller.rb index 98ca0bb1f4..e0db2ef6c0 100644 --- a/app/controllers/alchemy/admin/nodes_controller.rb +++ b/app/controllers/alchemy/admin/nodes_controller.rb @@ -11,9 +11,8 @@ def index def new @node = Node.new( - site: Alchemy::Site.current, parent_id: params[:parent_id], - language: @current_language + language: @current_language, ) end @@ -21,7 +20,7 @@ def new def resource_params params.require(:node).permit( - :site_id, + :menu_type, :parent_id, :language_id, :page_id, @@ -29,7 +28,7 @@ def resource_params :url, :title, :nofollow, - :external + :external, ) end end diff --git a/app/controllers/alchemy/admin/pages_controller.rb b/app/controllers/alchemy/admin/pages_controller.rb index 36716c5f8d..d5ce82c902 100644 --- a/app/controllers/alchemy/admin/pages_controller.rb +++ b/app/controllers/alchemy/admin/pages_controller.rb @@ -5,7 +5,7 @@ module Admin class PagesController < Alchemy::Admin::BaseController include OnPageLayout::CallbacksRunner - helper 'alchemy/pages' + helper "alchemy/pages" before_action :load_page, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link, :sort] @@ -48,7 +48,7 @@ def show Page.current_preview = @page # Setting the locale to pages language, so the page content has it's correct translations. ::I18n.locale = @page.language.locale - render(layout: Alchemy::Config.get(:admin_page_preview_layout) || 'application') + render(layout: Alchemy::Config.get(:admin_page_preview_layout) || "application") end def info @@ -56,9 +56,9 @@ def info end def new - @page ||= Page.new(layoutpage: params[:layoutpage] == 'true', parent_id: params[:parent_id]) + @page ||= Page.new(layoutpage: params[:layoutpage] == "true", parent_id: params[:parent_id]) @page_layouts = PageLayout.layouts_for_select(@current_language.id, @page.layoutpage?) - @clipboard = get_clipboard('pages') + @clipboard = get_clipboard("pages") @clipboard_items = Page.all_from_clipboard_for_select(@clipboard, @current_language.id, @page.layoutpage?) end @@ -80,11 +80,12 @@ def create def edit # fetching page via before filter if page_is_locked? - flash[:warning] = Alchemy.t('This page is locked', name: @page.locker_name) + flash[:warning] = Alchemy.t("This page is locked", name: @page.locker_name) redirect_to admin_pages_path elsif page_needs_lock? @page.lock_to!(current_alchemy_user) end + @preview_url = Alchemy::Admin::PREVIEW_URL.url_for(@page) @layoutpage = @page.layoutpage? end @@ -102,7 +103,7 @@ def update @old_page_layout = @page.page_layout if @page.update(page_params) @notice = Alchemy.t("Page saved", name: @page.name) - @while_page_edit = request.referer.include?('edit') + @while_page_edit = request.referer.include?("edit") unless @while_page_edit @tree = serialized_page_tree @@ -118,17 +119,17 @@ def destroy flash[:notice] = Alchemy.t("Page deleted", name: @page.name) # Remove page from clipboard - clipboard = get_clipboard('pages') - clipboard.delete_if { |item| item['id'] == @page.id.to_s } + clipboard = get_clipboard("pages") + clipboard.delete_if { |item| item["id"] == @page.id.to_s } end respond_to do |format| format.js do @redirect_url = if @page.layoutpage? - alchemy.admin_layoutpages_path - else - alchemy.admin_pages_path - end + alchemy.admin_layoutpages_path + else + alchemy.admin_pages_path + end render :redirect end @@ -139,7 +140,6 @@ def link @attachments = Attachment.all.collect { |f| [f.name, download_attachment_path(id: f.id, name: f.urlname)] } - @url_prefix = prefix_locale? ? "#{@current_language.code}/" : "" end def fold @@ -169,7 +169,7 @@ def visit redirect_to show_page_url( urlname: @page.urlname, locale: prefix_locale? ? @page.language_code : nil, - host: @page.site.host == "*" ? request.host : @page.site.host + host: @page.site.host == "*" ? request.host : @page.site.host, ) end @@ -221,13 +221,11 @@ def flush private def copy_of_language_root - page_copy = Page.copy( + Page.copy( language_root_to_copy_from, language_id: params[:languages][:new_lang_id], - language_code: @current_language.code + language_code: @current_language.code, ) - page_copy.move_to_child_of Page.root - page_copy end def language_root_to_copy_from @@ -257,14 +255,14 @@ def language_root_to_copy_from def visit_nodes(nodes, my_left, parent, depth, tree, url, restricted) nodes.each do |item| my_right = my_left + 1 - my_restricted = item['restricted'] || restricted + my_restricted = item["restricted"] || restricted urls = process_url(url, item) - if item['children'] - my_right, tree = visit_nodes(item['children'], my_left + 1, item['id'], depth + 1, tree, urls[:children_path], my_restricted) + if item["children"] + my_right, tree = visit_nodes(item["children"], my_left + 1, item["id"], depth + 1, tree, urls[:children_path], my_restricted) end - tree[item['id']] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted) + tree[item["id"]] = TreeNode.new(my_left, my_right, parent, depth, urls[:my_urlname], my_restricted) my_left = my_right + 1 end @@ -293,24 +291,14 @@ def create_tree(items, rootpage) # This function will add a node's own slug into their ancestor's path # in order to create the full URL of a node # - # NOTE: Invisible pages are not part of the full path of their children - # # @param [String] # The node's ancestors path # @param [Hash] # A children node # def process_url(ancestors_path, item) - default_urlname = (ancestors_path.blank? ? "" : "#{ancestors_path}/") + item['slug'].to_s - - pair = {my_urlname: default_urlname, children_path: default_urlname} - - if item['visible'] == false - # children ignore an ancestor in their path if invisible - pair[:children_path] = ancestors_path - end - - pair + default_urlname = (ancestors_path.blank? ? "" : "#{ancestors_path}/") + item["slug"].to_s + { my_urlname: default_urlname, children_path: default_urlname } end def load_page @@ -318,10 +306,10 @@ def load_page end def pages_from_raw_request - request.raw_post.split('&').map do |i| - parts = i.split('=') + request.raw_post.split("&").map do |i| + parts = i.split("=") { - parts[0].gsub(/[^0-9]/, '') => parts[1] + parts[0].gsub(/[^0-9]/, "") => parts[1], } end end @@ -362,7 +350,7 @@ def page_needs_lock? def paste_from_clipboard if params[:paste_from_clipboard] source = Page.find(params[:paste_from_clipboard]) - parent = Page.find_by(id: params[:page][:parent_id]) || Page.root + parent = Page.find_by(id: params[:page][:parent_id]) Page.copy_and_paste(source, parent, params[:page][:name]) end end @@ -374,7 +362,7 @@ def set_root_page def serialized_page_tree PageTreeSerializer.new(@page, ability: current_ability, user: current_alchemy_user, - full: params[:full] == 'true') + full: params[:full] == "true") end end end diff --git a/app/controllers/alchemy/admin/pictures_controller.rb b/app/controllers/alchemy/admin/pictures_controller.rb index 75354555ed..7869a3ee42 100644 --- a/app/controllers/alchemy/admin/pictures_controller.rb +++ b/app/controllers/alchemy/admin/pictures_controller.rb @@ -6,7 +6,7 @@ class PicturesController < Alchemy::Admin::ResourcesController include UploaderResponses include ArchiveOverlay - helper 'alchemy/admin/tags' + helper "alchemy/admin/tags" before_action :load_resource, only: [:show, :edit, :update, :destroy, :info] @@ -14,12 +14,12 @@ class PicturesController < Alchemy::Admin::ResourcesController authorize_resource class: Alchemy::Picture def index - @size = params[:size].present? ? params[:size] : 'medium' + @size = params[:size].present? ? params[:size] : "medium" @query = Picture.ransack(search_filter_params[:q]) @pictures = Picture.search_by( search_filter_params, @query, - items_per_page + items_per_page, ) if in_overlay? @@ -31,7 +31,7 @@ def show @previous = @picture.previous(params) @next = @picture.next(params) @assignments = @picture.essence_pictures.joins(content: {element: :page}) - render action: 'show' + render action: "show" end def create @@ -46,19 +46,19 @@ def create def edit_multiple @pictures = Picture.where(id: params[:picture_ids]) - @tags = @pictures.collect(&:tag_list).flatten.uniq.join(', ') + @tags = @pictures.collect(&:tag_list).flatten.uniq.join(", ") end def update if @picture.update(picture_params) @message = { body: Alchemy.t(:picture_updated_successfully, name: @picture.name), - type: 'notice' + type: "notice", } else @message = { body: Alchemy.t(:picture_update_failed), - type: 'error' + type: "error", } end render :update @@ -89,7 +89,7 @@ def delete_multiple if not_deletable.any? flash[:warn] = Alchemy.t( "These pictures could not be deleted, because they were in use", - names: not_deletable.to_sentence + names: not_deletable.to_sentence, ) else flash[:notice] = Alchemy.t("Pictures deleted successfully", names: names.to_sentence) @@ -116,8 +116,8 @@ def destroy def items_per_page if in_overlay? case params[:size] - when 'small' then 25 - when 'large' then 4 + when "small" then 25 + when "large" then 4 else 9 end @@ -137,8 +137,8 @@ def items_per_page_options def pictures_per_page_for_size(size) case size - when 'small' then 60 - when 'large' then 12 + when "small" then 60 + when "large" then 12 else 20 end @@ -154,8 +154,8 @@ def search_filter_params :size, :element_id, :swap, - :content_id - ] + :content_id, + ], ) end diff --git a/app/controllers/alchemy/admin/resources_controller.rb b/app/controllers/alchemy/admin/resources_controller.rb index e6079942c8..4bbf962c89 100644 --- a/app/controllers/alchemy/admin/resources_controller.rb +++ b/app/controllers/alchemy/admin/resources_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'csv' -require 'alchemy/resource' -require 'alchemy/resources_helper' +require "csv" +require "alchemy/resource" +require "alchemy/resources_helper" module Alchemy module Admin @@ -53,7 +53,7 @@ def new end def show - render action: 'edit' + render action: "edit" end def edit; end @@ -64,7 +64,7 @@ def create render_errors_or_redirect( resource_instance_variable, resources_path(resource_instance_variable.class, search_filter_params), - flash_notice_for_resource_action + flash_notice_for_resource_action, ) end @@ -73,14 +73,17 @@ def update render_errors_or_redirect( resource_instance_variable, resources_path(resource_instance_variable.class, search_filter_params), - flash_notice_for_resource_action + flash_notice_for_resource_action, ) end def destroy resource_instance_variable.destroy + if resource_instance_variable.errors.any? + flash[:error] = resource_instance_variable.errors.full_messages.join(", ") + end flash_notice_for_resource_action - do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: 'index')) + do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index")) end def resource_handler @@ -106,11 +109,11 @@ def flash_notice_for_resource_action(action = params[:action]) end def is_alchemy_module? - !alchemy_module.nil? && !alchemy_module['engine_name'].nil? + !alchemy_module.nil? && !alchemy_module["engine_name"].nil? end def alchemy_module - @alchemy_module ||= module_definition_for(controller: params[:controller], action: 'index') + @alchemy_module ||= module_definition_for(controller: params[:controller], action: "index") end def load_resource @@ -147,12 +150,12 @@ def common_search_filter_includes [ {q: [ resource_handler.search_field_name, - :s + :s, ]}, :tagged_with, :filter, :page, - :per_page + :per_page, ].freeze end @@ -166,8 +169,8 @@ def items_per_page_options end def default_sort_order - name = resource_handler.attributes.detect { |attr| attr[:name] == 'name' } - name ? 'name asc' : "#{resource_handler.attributes.first[:name]} asc" + name = resource_handler.attributes.detect { |attr| attr[:name] == "name" } + name ? "name asc" : "#{resource_handler.attributes.first[:name]} asc" end end end diff --git a/app/controllers/alchemy/admin/sites_controller.rb b/app/controllers/alchemy/admin/sites_controller.rb index 3fd772b664..be79b0dd29 100644 --- a/app/controllers/alchemy/admin/sites_controller.rb +++ b/app/controllers/alchemy/admin/sites_controller.rb @@ -6,7 +6,7 @@ class SitesController < ResourcesController def create @site = Alchemy::Site.new(resource_params) if @site.save - flash[:notice] = Alchemy.t('Please create a default language for this site.') + flash[:notice] = Alchemy.t("Please create a default language for this site.") redirect_to alchemy.admin_languages_path(site_id: @site) else render :new @@ -15,7 +15,7 @@ def create def destroy if @site.destroy - flash[:notice] = Alchemy.t('Site successfully removed') + flash[:notice] = Alchemy.t("Site successfully removed") else flash[:warning] = @site.errors.full_messages.to_sentence end diff --git a/app/controllers/alchemy/admin/tags_controller.rb b/app/controllers/alchemy/admin/tags_controller.rb index d418b7aca9..857e4f4778 100644 --- a/app/controllers/alchemy/admin/tags_controller.rb +++ b/app/controllers/alchemy/admin/tags_controller.rb @@ -21,7 +21,7 @@ def new def create @tag = Gutentag::Tag.create(tag_params) - render_errors_or_redirect @tag, admin_tags_path, Alchemy.t('New Tag Created') + render_errors_or_redirect @tag, admin_tags_path, Alchemy.t("New Tag Created") end def edit @@ -32,7 +32,7 @@ def update if tag_params[:merge_to] @new_tag = Gutentag::Tag.find(tag_params[:merge_to]) Tag.replace(@tag, @new_tag) - operation_text = Alchemy.t('Replaced Tag') % {old_tag: @tag.name, new_tag: @new_tag.name} + operation_text = Alchemy.t("Replaced Tag") % {old_tag: @tag.name, new_tag: @new_tag.name} @tag.destroy else @tag.update(tag_params) @@ -67,7 +67,7 @@ def tag_params def tags_from_term(term) return [] if term.blank? - Gutentag::Tag.where(['LOWER(name) LIKE ?', "#{term.downcase}%"]) + Gutentag::Tag.where(["LOWER(name) LIKE ?", "#{term.downcase}%"]) end def json_for_autocomplete(items, attribute) diff --git a/app/controllers/alchemy/admin/trash_controller.rb b/app/controllers/alchemy/admin/trash_controller.rb index 8956f70b79..98c3dce514 100644 --- a/app/controllers/alchemy/admin/trash_controller.rb +++ b/app/controllers/alchemy/admin/trash_controller.rb @@ -25,16 +25,16 @@ def element_includes [ { contents: { - essence: :ingredient_association + essence: :ingredient_association, }, all_nested_elements: [ { contents: { - essence: :ingredient_association - } - } - ] - } + essence: :ingredient_association, + }, + }, + ], + }, ] end end diff --git a/app/controllers/alchemy/api/base_controller.rb b/app/controllers/alchemy/api/base_controller.rb index b3e21c42f5..c79f5badcd 100644 --- a/app/controllers/alchemy/api/base_controller.rb +++ b/app/controllers/alchemy/api/base_controller.rb @@ -11,11 +11,11 @@ class Api::BaseController < Alchemy::BaseController private def render_not_authorized - render json: {error: 'Not authorized'}, status: 403 + render json: {error: "Not authorized"}, status: 403 end def render_not_found - render json: {error: 'Record not found'}, status: 404 + render json: {error: "Record not found"}, status: 404 end end end diff --git a/app/controllers/alchemy/api/contents_controller.rb b/app/controllers/alchemy/api/contents_controller.rb index 13162e08ca..c864de5fe2 100644 --- a/app/controllers/alchemy/api/contents_controller.rb +++ b/app/controllers/alchemy/api/contents_controller.rb @@ -18,7 +18,7 @@ def index end @contents = @contents.includes(*content_includes) - render json: @contents, adapter: :json, root: 'contents' + render json: @contents, adapter: :json, root: "contents" end # Returns a json object for content @@ -36,7 +36,7 @@ def show elsif params[:element_id] && params[:name] @content = Content.where( element_id: params[:element_id], - name: params[:name] + name: params[:name], ).includes(*content_includes).first || raise(ActiveRecord::RecordNotFound) end authorize! :show, @content @@ -48,8 +48,8 @@ def show def content_includes [ { - essence: :ingredient_association - } + essence: :ingredient_association, + }, ] end end diff --git a/app/controllers/alchemy/api/elements_controller.rb b/app/controllers/alchemy/api/elements_controller.rb index 45367a8104..1ad9583065 100644 --- a/app/controllers/alchemy/api/elements_controller.rb +++ b/app/controllers/alchemy/api/elements_controller.rb @@ -22,7 +22,7 @@ def index end @elements = @elements.includes(*element_includes) - render json: @elements, adapter: :json, root: 'elements' + render json: @elements, adapter: :json, root: "elements" end # Returns a json object for element @@ -41,18 +41,18 @@ def element_includes nested_elements: [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] + :tags, + ], }, { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags + :tags, ] end end diff --git a/app/controllers/alchemy/api/nodes_controller.rb b/app/controllers/alchemy/api/nodes_controller.rb index 3e61059fb5..766f85ce7a 100644 --- a/app/controllers/alchemy/api/nodes_controller.rb +++ b/app/controllers/alchemy/api/nodes_controller.rb @@ -2,9 +2,21 @@ module Alchemy class Api::NodesController < Api::BaseController - before_action :load_node + before_action :load_node, except: :index before_action :authorize_access, only: [:move, :toggle_folded] + def index + @nodes = Node.all + @nodes = @nodes.includes(:parent) + @nodes = @nodes.ransack(params[:filter]).result + + if params[:page] + @nodes = @nodes.page(params[:page]).per(params[:per_page]) + end + + render json: @nodes, adapter: :json, root: "data", meta: meta_data, include: params[:include] + end + def move target_parent_node = Node.find(params[:target_parent_id]) @node.move_to_child_with_index(target_parent_node, params[:new_position]) @@ -25,5 +37,29 @@ def load_node def authorize_access authorize! :update, @node end + + def meta_data + { + total_count: total_count_value, + per_page: per_page_value, + page: page_value, + } + end + + def total_count_value + params[:page] ? @nodes.total_count : @nodes.size + end + + def per_page_value + if params[:page] + (params[:per_page] || Kaminari.config.default_per_page).to_i + else + @nodes.size + end + end + + def page_value + params[:page] ? params[:page].to_i : 1 + end end end diff --git a/app/controllers/alchemy/api/pages_controller.rb b/app/controllers/alchemy/api/pages_controller.rb index 0946d6a39e..1b24ddfebe 100644 --- a/app/controllers/alchemy/api/pages_controller.rb +++ b/app/controllers/alchemy/api/pages_controller.rb @@ -20,7 +20,7 @@ def index @pages = @pages.page(params[:page]).per(params[:per_page]) end - render json: @pages, adapter: :json, root: 'pages', meta: meta_data + render json: @pages, adapter: :json, root: "pages", meta: meta_data end # Returns all pages as nested json object for tree views @@ -64,7 +64,7 @@ def load_page_by_urlname Language.current.pages.where( urlname: params[:urlname], - language_code: params[:locale] || Language.current.code + language_code: params[:locale] || Language.current.code, ).includes(page_includes).first end @@ -72,7 +72,7 @@ def meta_data { total_count: total_count_value, per_page: per_page_value, - page: page_value + page: page_value, } end @@ -96,25 +96,26 @@ def page_includes [ :tags, { + language: :site, elements: [ { nested_elements: [ { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] + :tags, + ], }, { contents: { - essence: :ingredient_association - } + essence: :ingredient_association, + }, }, - :tags - ] - } + :tags, + ], + }, ] end end diff --git a/app/controllers/alchemy/attachments_controller.rb b/app/controllers/alchemy/attachments_controller.rb index 3327d67849..8841ad8a35 100644 --- a/app/controllers/alchemy/attachments_controller.rb +++ b/app/controllers/alchemy/attachments_controller.rb @@ -7,24 +7,24 @@ class AttachmentsController < BaseController # sends file inline. i.e. for viewing pdfs/movies in browser def show - response.headers['Content-Length'] = @attachment.file.size.to_s + response.headers["Content-Length"] = @attachment.file.size.to_s send_file( @attachment.file.path, { filename: @attachment.file_name, type: @attachment.file_mime_type, - disposition: 'inline' - } + disposition: "inline", + }, ) end # sends file as attachment. aka download def download - response.headers['Content-Length'] = @attachment.file.size.to_s + response.headers["Content-Length"] = @attachment.file.size.to_s send_file( @attachment.file.path, { filename: @attachment.file_name, - type: @attachment.file_mime_type + type: @attachment.file_mime_type, } ) end diff --git a/app/controllers/alchemy/base_controller.rb b/app/controllers/alchemy/base_controller.rb index dad72d6c72..ea201d954d 100644 --- a/app/controllers/alchemy/base_controller.rb +++ b/app/controllers/alchemy/base_controller.rb @@ -8,14 +8,13 @@ class BaseController < ApplicationController include Alchemy::AbilityHelper include Alchemy::ControllerActions include Alchemy::Modules - include Alchemy::SSLProtection protect_from_forgery before_action :mailer_set_url_options before_action :set_locale - helper 'alchemy/admin/form' + helper "alchemy/admin/form" rescue_from CanCan::AccessDenied do |exception| permission_denied(exception) @@ -27,6 +26,7 @@ class BaseController < ApplicationController # def set_locale return unless Language.current + ::I18n.locale = Language.current&.locale end @@ -61,11 +61,11 @@ def permission_denied(exception = nil) end def handle_redirect_for_user - flash[:warning] = Alchemy.t('You are not authorized') + flash[:warning] = Alchemy.t("You are not authorized") if can?(:index, :alchemy_admin_dashboard) redirect_or_render_notice else - redirect_to('/') + redirect_to("/") end end @@ -76,8 +76,8 @@ def redirect_or_render_notice render plain: flash.discard(:warning), status: 403 end format.html do - render partial: 'alchemy/admin/partials/flash', - locals: {message: flash[:warning], flash_type: 'warning'} + render partial: "alchemy/admin/partials/flash", + locals: { message: flash[:warning], flash_type: "warning" } end end else @@ -86,7 +86,7 @@ def redirect_or_render_notice end def handle_redirect_for_guest - flash[:info] = Alchemy.t('Please log in') + flash[:info] = Alchemy.t("Please log in") if request.xhr? render :permission_denied else @@ -99,7 +99,7 @@ def handle_redirect_for_guest def exception_logger(error) Rails.logger.error("\n#{error.class} #{error.message} in #{error.backtrace.first}") Rails.logger.error(error.backtrace[1..50].each { |line| - line.gsub(/#{Rails.root}/, '') + line.gsub(/#{Rails.root}/, "") }.join("\n")) end end diff --git a/app/controllers/alchemy/messages_controller.rb b/app/controllers/alchemy/messages_controller.rb index 1503f71b6e..0148f29d1c 100644 --- a/app/controllers/alchemy/messages_controller.rb +++ b/app/controllers/alchemy/messages_controller.rb @@ -39,18 +39,18 @@ module Alchemy class MessagesController < Alchemy::BaseController before_action :get_page, except: :create - helper 'alchemy/pages' + helper "alchemy/pages" def index #:nodoc: redirect_to show_page_path( urlname: @page.urlname, - locale: prefix_locale? ? @page.language_code : nil + locale: prefix_locale? ? @page.language_code : nil, ) end def new #:nodoc: @message = Message.new - render template: 'alchemy/pages/show' + render template: "alchemy/pages/show" end def create #:nodoc: @@ -67,7 +67,7 @@ def create #:nodoc: MessagesMailer.contact_form_mail(@message, mail_to, mail_from, subject).deliver redirect_to_success_page else - render template: 'alchemy/pages/show' + render template: "alchemy/pages/show" end end @@ -78,29 +78,29 @@ def mailer_config end def mail_to - @element.ingredient(:mail_to) || mailer_config['mail_to'] + @element.ingredient(:mail_to) || mailer_config["mail_to"] end def mail_from - @element.ingredient(:mail_from) || mailer_config['mail_from'] + @element.ingredient(:mail_from) || mailer_config["mail_from"] end def subject - @element.ingredient(:subject) || mailer_config['subject'] + @element.ingredient(:subject) || mailer_config["subject"] end def redirect_to_success_page - flash[:notice] = Alchemy.t(:success, scope: 'contactform.messages') + flash[:notice] = Alchemy.t(:success, scope: "contactform.messages") if success_page urlname = success_page_urlname - elsif mailer_config['forward_to_page'] && mailer_config['mail_success_page'] - urlname = Page.find_by(urlname: mailer_config['mail_success_page']).urlname + elsif mailer_config["forward_to_page"] && mailer_config["mail_success_page"] + urlname = Page.find_by(urlname: mailer_config["mail_success_page"]).urlname else urlname = Language.current_root_page.urlname end redirect_to show_page_path( urlname: urlname, - locale: prefix_locale? ? Language.current.code : nil + locale: prefix_locale? ? Language.current.code : nil, ) end @@ -118,16 +118,16 @@ def success_page_urlname end def get_page - @page = Language.current.pages.find_by(page_layout: mailer_config['page_layout_name']) + @page = Language.current.pages.find_by(page_layout: mailer_config["page_layout_name"]) if @page.blank? - raise "Page for page_layout #{mailer_config['page_layout_name']} not found" + raise "Page for page_layout #{mailer_config["page_layout_name"]} not found" end @root_page = @page.get_language_root end def message_params - params.require(:message).permit(*mailer_config['fields']) + params.require(:message).permit(*mailer_config["fields"]) end end end diff --git a/app/controllers/alchemy/pages_controller.rb b/app/controllers/alchemy/pages_controller.rb index 87c153d2e7..674ff91ed9 100644 --- a/app/controllers/alchemy/pages_controller.rb +++ b/app/controllers/alchemy/pages_controller.rb @@ -3,10 +3,10 @@ module Alchemy class PagesController < Alchemy::BaseController SHOW_PAGE_PARAMS_KEYS = [ - 'action', - 'controller', - 'urlname', - 'locale' + "action", + "controller", + "urlname", + "locale", ] include OnPageLayout::CallbacksRunner @@ -78,7 +78,7 @@ def show def sitemap @pages = Page.sitemap respond_to do |format| - format.xml { render layout: 'alchemy/sitemap' } + format.xml { render layout: "alchemy/sitemap" } end end @@ -95,7 +95,7 @@ def sitemap # def load_index_page @page ||= Language.current_root_page - render template: 'alchemy/welcome', layout: false if signup_required? + render template: "alchemy/welcome", layout: false if signup_required? end # == Loads page by urlname @@ -112,7 +112,7 @@ def load_page @page ||= Language.current.pages.contentpages.find_by( urlname: params[:urlname], - language_code: params[:locale] || Language.current.code + language_code: params[:locale] || Language.current.code, ) end @@ -150,7 +150,7 @@ def render_page if @page.contains_feed? render action: :show, layout: false, handlers: [:builder] else - render xml: {error: 'Not found'}, status: 404 + render xml: { error: "Not found" }, status: 404 end end end @@ -193,9 +193,9 @@ def page_etag # def render_fresh_page? must_not_cache? || stale?(etag: page_etag, - last_modified: @page.published_at, - public: !@page.restricted, - template: 'pages/show') + last_modified: @page.published_at, + public: !@page.restricted, + template: "pages/show") end # don't cache pages if we have flash message to display or the page has caching disabled diff --git a/app/controllers/concerns/alchemy/admin/archive_overlay.rb b/app/controllers/concerns/alchemy/admin/archive_overlay.rb index 782228a730..6346d646d6 100644 --- a/app/controllers/concerns/alchemy/admin/archive_overlay.rb +++ b/app/controllers/concerns/alchemy/admin/archive_overlay.rb @@ -12,8 +12,8 @@ def archive_overlay @content = Content.find_by(id: params[:content_id]) respond_to do |format| - format.html { render partial: 'archive_overlay' } - format.js { render action: 'archive_overlay' } + format.html { render partial: "archive_overlay" } + format.js { render action: "archive_overlay" } end end end diff --git a/app/controllers/concerns/alchemy/admin/current_language.rb b/app/controllers/concerns/alchemy/admin/current_language.rb index 6a6a864e1a..d2bf50a6e7 100644 --- a/app/controllers/concerns/alchemy/admin/current_language.rb +++ b/app/controllers/concerns/alchemy/admin/current_language.rb @@ -14,7 +14,7 @@ module CurrentLanguage def load_current_language @current_language = Alchemy::Language.current if @current_language.nil? - flash[:warning] = Alchemy.t('Please create a language first.') + flash[:warning] = Alchemy.t("Please create a language first.") redirect_to admin_languages_path end end diff --git a/app/controllers/concerns/alchemy/admin/uploader_responses.rb b/app/controllers/concerns/alchemy/admin/uploader_responses.rb index 4662ea384a..6a2f21f2e6 100644 --- a/app/controllers/concerns/alchemy/admin/uploader_responses.rb +++ b/app/controllers/concerns/alchemy/admin/uploader_responses.rb @@ -8,12 +8,12 @@ module UploaderResponses def successful_uploader_response(file:, status: :created) message = Alchemy.t(:upload_success, scope: [:uploader, file.class.model_name.i18n_key], - name: file.name + name: file.name, ) { json: uploader_response(file: file, message: message), - status: status + status: status, } end @@ -21,12 +21,12 @@ def failed_uploader_response(file:) message = Alchemy.t(:upload_failure, scope: [:uploader, file.class.model_name.i18n_key], error: file.errors[:file].join, - name: file.name + name: file.name, ) { json: uploader_response(file: file, message: message), - status: :unprocessable_entity + status: :unprocessable_entity, } end @@ -35,7 +35,7 @@ def failed_uploader_response(file:) def uploader_response(file:, message:) { files: [file.to_jq_upload], - growl_message: message + growl_message: message, } end end diff --git a/app/controllers/concerns/alchemy/legacy_page_redirects.rb b/app/controllers/concerns/alchemy/legacy_page_redirects.rb index e00780d26d..aa18739edf 100644 --- a/app/controllers/concerns/alchemy/legacy_page_redirects.rb +++ b/app/controllers/concerns/alchemy/legacy_page_redirects.rb @@ -36,18 +36,18 @@ def legacy_page_redirect_url alchemy.show_page_path( locale: prefix_locale? ? page.language_code : nil, - urlname: page.urlname + urlname: page.urlname, ) end def legacy_urls # /slug/tree => slug/tree - urlname = (request.fullpath[1..-1] if request.fullpath[0] == '/') || request.fullpath + urlname = (request.fullpath[1..-1] if request.fullpath[0] == "/") || request.fullpath LegacyPageUrl.joins(:page).where( urlname: urlname, Page.table_name => { - language_id: Language.current.id - } + language_id: Language.current.id, + }, ) end diff --git a/app/controllers/concerns/alchemy/page_redirects.rb b/app/controllers/concerns/alchemy/page_redirects.rb index 751f42c3e9..b39d766670 100644 --- a/app/controllers/concerns/alchemy/page_redirects.rb +++ b/app/controllers/concerns/alchemy/page_redirects.rb @@ -51,7 +51,7 @@ def public_child_redirect_url def page_redirect_url(options = {}) options = { locale: prefix_locale? ? @page.language_code : nil, - urlname: @page.urlname + urlname: @page.urlname, }.merge(options) alchemy.show_page_path additional_params.merge(options) diff --git a/app/controllers/concerns/alchemy/site_redirects.rb b/app/controllers/concerns/alchemy/site_redirects.rb index e0696dd7fb..059bd7a5d3 100644 --- a/app/controllers/concerns/alchemy/site_redirects.rb +++ b/app/controllers/concerns/alchemy/site_redirects.rb @@ -17,7 +17,7 @@ def enforce_primary_host_for_site def needs_redirect_to_primary_host? current_alchemy_site&.redirect_to_primary_host? && - current_alchemy_site.host != '*' && + current_alchemy_site.host != "*" && current_alchemy_site.host != request.host end end diff --git a/app/decorators/alchemy/content_editor.rb b/app/decorators/alchemy/content_editor.rb index 2e5055aee5..4d7bcf6317 100644 --- a/app/decorators/alchemy/content_editor.rb +++ b/app/decorators/alchemy/content_editor.rb @@ -10,15 +10,15 @@ def to_partial_path def css_classes [ - 'content_editor', - essence_partial_name + "content_editor", + essence_partial_name, ].compact end def data_attributes { content_id: id, - content_name: name + content_name: name, } end @@ -36,11 +36,11 @@ def data_attributes # # <%= text_field_tag content_editor.form_field_name(:link), content_editor.ingredient %> # - def form_field_name(essence_column = 'ingredient') + def form_field_name(essence_column = "ingredient") "contents[#{id}][#{essence_column}]" end - def form_field_id(essence_column = 'ingredient') + def form_field_id(essence_column = "ingredient") "contents_#{id}_#{essence_column}" end diff --git a/app/decorators/alchemy/element_editor.rb b/app/decorators/alchemy/element_editor.rb index ba2b1ce838..29c9478079 100644 --- a/app/decorators/alchemy/element_editor.rb +++ b/app/decorators/alchemy/element_editor.rb @@ -11,14 +11,14 @@ def to_partial_path # CSS classes for the element editor partial. def css_classes [ - 'element-editor', - content_definitions.present? ? 'with-contents' : 'without-contents', - nestable_elements.any? ? 'nestable' : 'not-nestable', - taggable? ? 'taggable' : 'not-taggable', - folded ? 'folded' : 'expanded', - compact? ? 'compact' : nil, - fixed? ? 'is-fixed' : 'not-fixed' - ].join(' ') + "element-editor", + content_definitions.present? ? "with-contents" : "without-contents", + nestable_elements.any? ? "nestable" : "not-nestable", + taggable? ? "taggable" : "not-taggable", + folded ? "folded" : "expanded", + compact? ? "compact" : nil, + fixed? ? "is-fixed" : "not-fixed", + ].join(" ") end # Tells us, if we should show the element footer and form inputs. diff --git a/app/helpers/alchemy/admin/attachments_helper.rb b/app/helpers/alchemy/admin/attachments_helper.rb index 22bb255b39..8bc876a275 100644 --- a/app/helpers/alchemy/admin/attachments_helper.rb +++ b/app/helpers/alchemy/admin/attachments_helper.rb @@ -6,17 +6,17 @@ module AttachmentsHelper include Alchemy::Admin::BaseHelper def mime_to_human(mime) - Alchemy.t(mime, scope: 'mime_types', default: Alchemy.t(:document)) + Alchemy.t(mime, scope: "mime_types", default: Alchemy.t(:document)) end def attachment_preview_size(attachment) case attachment.icon_css_class - when 'image' then '600x475' - when 'audio' then '600x190' - when 'video' then '600x485' - when 'pdf' then '600x500' + when "image" then "600x475" + when "audio" then "600x190" + when "video" then "600x485" + when "pdf" then "600x500" else - '600x145' + "600x145" end end end diff --git a/app/helpers/alchemy/admin/base_helper.rb b/app/helpers/alchemy/admin/base_helper.rb index ca7afd44c0..617390178f 100644 --- a/app/helpers/alchemy/admin/base_helper.rb +++ b/app/helpers/alchemy/admin/base_helper.rb @@ -23,7 +23,7 @@ module BaseHelper def current_alchemy_user_name name = current_alchemy_user.try(:alchemy_display_name) if name.present? - content_tag :span, "#{Alchemy.t('Logged in as')} #{name}", class: 'current-user-name' + content_tag :span, "#{Alchemy.t("Logged in as")} #{name}", class: "current-user-name" end end @@ -55,7 +55,7 @@ def link_to_dialog(content, url, options = {}, html_options = {}) default_options = {modal: true} options = default_options.merge(options) link_to content, url, - html_options.merge('data-alchemy-dialog' => options.to_json) + html_options.merge("data-alchemy-dialog" => options.to_json) end # Used for translations selector in Alchemy cockpit user settings. @@ -99,13 +99,13 @@ def sites_for_select # def js_filter_field(items, options = {}) options = { - class: 'js_filter_field', - data: {'alchemy-list-filter' => items} + class: "js_filter_field", + data: {"alchemy-list-filter" => items}, }.merge(options) - content_tag(:div, class: 'js_filter_field_box') do + content_tag(:div, class: "js_filter_field_box") do concat text_field_tag(nil, nil, options) concat render_icon(:search) - concat link_to(render_icon(:times, size: 'xs'), '', class: 'js_filter_field_clear', title: Alchemy.t(:click_to_show_all)) + concat link_to(render_icon(:times, size: "xs"), "", class: "js_filter_field_clear", title: Alchemy.t(:click_to_show_all)) end end @@ -136,12 +136,12 @@ def js_filter_field(items, options = {}) def link_to_confirm_dialog(link_string = "", message = "", url = "", html_options = {}) link_to(link_string, url, html_options.merge( - 'data-alchemy-confirm-delete' => { + "data-alchemy-confirm-delete" => { title: Alchemy.t(:please_confirm), message: message, ok_label: Alchemy.t("Yes"), - cancel_label: Alchemy.t("No") - }.to_json + cancel_label: Alchemy.t("No"), + }.to_json, ) ) end @@ -170,10 +170,10 @@ def button_with_confirm(value = "", url = "", options = {}, html_options = {}) message: Alchemy.t(:confirm_to_proceed), ok_label: Alchemy.t("Yes"), title: Alchemy.t(:please_confirm), - cancel_label: Alchemy.t("No") + cancel_label: Alchemy.t("No"), }.merge(options) - form_tag url, {method: html_options.delete(:method), class: 'button-with-confirm'} do - button_tag value, html_options.merge('data-alchemy-confirm' => options.to_json) + form_tag url, {method: html_options.delete(:method), class: "button-with-confirm"} do + button_tag value, html_options.merge("data-alchemy-confirm" => options.to_json) end end @@ -188,18 +188,18 @@ def button_with_confirm(value = "", url = "", options = {}, html_options = {}) # def delete_button(url, options = {}, html_options = {}) options = { - title: Alchemy.t('Delete'), - message: Alchemy.t('Are you sure?'), - icon: :minus + title: Alchemy.t("Delete"), + message: Alchemy.t("Are you sure?"), + icon: :minus, }.merge(options) button_with_confirm( render_icon(options[:icon]), url, { - message: options[:message] + message: options[:message], }, { - method: 'delete', + method: "delete", title: options[:title], - class: "icon_button #{html_options.delete(:class)}".strip + class: "icon_button #{html_options.delete(:class)}".strip, }.merge(html_options) ) end @@ -259,11 +259,11 @@ def toolbar_button(options = {}) active: false, link_options: {}, dialog_options: {}, - loading_indicator: false + loading_indicator: false, }.merge(options.symbolize_keys) button = render( - 'alchemy/admin/partials/toolbar_button', - options: options + "alchemy/admin/partials/toolbar_button", + options: options, ) if options[:skip_permission_check] || can?(*permission_from_options(options)) button @@ -302,13 +302,13 @@ def toolbar_button(options = {}) def toolbar(options = {}) defaults = { buttons: [], - search: true + search: true, } options = defaults.merge(options) content_for(:toolbar) do content = <<-CONTENT.strip_heredoc #{options[:buttons].map { |button_options| toolbar_button(button_options) }.join} - #{render('alchemy/admin/partials/search_form', url: options[:search_url]) if options[:search]} + #{render("alchemy/admin/partials/search_form", url: options[:search_url]) if options[:search]} CONTENT content.html_safe end @@ -360,7 +360,7 @@ def new_asset_path_with_session_information(asset_type) # The value the input displays. If you pass a String its parsed with +Time.parse+ # def alchemy_datepicker(object, method, html_options = {}) - type = html_options.delete(:type) || 'date' + type = html_options.delete(:type) || "date" date = html_options.delete(:value) || object.send(method.to_sym).presence date = Time.zone.parse(date) if date.is_a?(String) value = date ? date.iso8601 : nil @@ -374,9 +374,9 @@ def alchemy_datepicker(object, method, html_options = {}) def render_hint_for(element) return unless element.has_hint? - content_tag :span, class: 'hint-with-icon' do - render_icon('question-circle') + - content_tag(:span, element.hint.html_safe, class: 'hint-bubble') + content_tag :span, class: "hint-with-icon" do + render_icon("question-circle") + + content_tag(:span, element.hint.html_safe, class: "hint-bubble") end end @@ -386,7 +386,7 @@ def alchemy_body_class controller_name, action_name, content_for(:main_menu_style), - content_for(:alchemy_body_class) + content_for(:alchemy_body_class), ].compact end @@ -405,7 +405,7 @@ def clipboard_select_tag_options(items) # Returns the regular expression used for external url validation in link dialog. def link_url_regexp - Alchemy::Config.get(:format_matchers)['link_url'] || /^(mailto:|\/|[a-z]+:\/\/)/ + Alchemy::Config.get(:format_matchers)["link_url"] || /^(mailto:|\/|[a-z]+:\/\/)/ end # Renders a hint with tooltip @@ -418,9 +418,9 @@ def link_url_regexp # @param icon: 'exclamation-triangle' [String] - Icon name # # @return [String] - def hint_with_tooltip(text, icon: 'exclamation-triangle') - content_tag :span, class: 'hint-with-icon' do - render_icon(icon) + content_tag(:span, text, class: 'hint-bubble') + def hint_with_tooltip(text, icon: "exclamation-triangle") + content_tag :span, class: "hint-with-icon" do + render_icon(icon) + content_tag(:span, text, class: "hint-bubble") end end @@ -428,7 +428,7 @@ def hint_with_tooltip(text, icon: 'exclamation-triangle') # that explains the user that the page layout is missing def page_layout_missing_warning hint_with_tooltip( - Alchemy.t(:page_definition_missing) + Alchemy.t(:page_definition_missing), ) end @@ -443,10 +443,10 @@ def permission_from_options(options) end def permission_array_from_url(options) - action_controller = options[:url].gsub(/\A\//, '').split('/') + action_controller = options[:url].gsub(/\A\//, "").split("/") [ action_controller.last.to_sym, - action_controller[0..action_controller.length - 2].join('_').to_sym + action_controller[0..action_controller.length - 2].join("_").to_sym, ] end end diff --git a/app/helpers/alchemy/admin/contents_helper.rb b/app/helpers/alchemy/admin/contents_helper.rb index 609d6980a7..18b4fcf214 100644 --- a/app/helpers/alchemy/admin/contents_helper.rb +++ b/app/helpers/alchemy/admin/contents_helper.rb @@ -13,7 +13,7 @@ module ContentsHelper # def render_content_name(content) if content.blank? - warning('Content is nil') + warning("Content is nil") return end @@ -23,7 +23,7 @@ def render_content_name(content) warning("Content #{content.name} is missing its definition") icon = hint_with_tooltip( - Alchemy.t(:content_definition_missing) + Alchemy.t(:content_definition_missing), ) content_name = "#{icon} #{content_name}".html_safe @@ -39,7 +39,7 @@ def render_content_name(content) # Renders the label and a remove link for a content. def content_label(content) content_tag :label, for: content.form_field_id do - [render_hint_for(content), render_content_name(content)].compact.join(' ').html_safe + [render_hint_for(content), render_content_name(content)].compact.join(" ").html_safe end end end diff --git a/app/helpers/alchemy/admin/elements_helper.rb b/app/helpers/alchemy/admin/elements_helper.rb index 07b01eb8ca..56d0aeec66 100644 --- a/app/helpers/alchemy/admin/elements_helper.rb +++ b/app/helpers/alchemy/admin/elements_helper.rb @@ -16,8 +16,8 @@ def elements_for_select(elements) elements.collect do |e| [ - Element.display_name_for(e['name']), - e['name'] + Element.display_name_for(e["name"]), + e["name"], ] end end diff --git a/app/helpers/alchemy/admin/essences_helper.rb b/app/helpers/alchemy/admin/essences_helper.rb index 9f67587dca..00fefb240a 100644 --- a/app/helpers/alchemy/admin/essences_helper.rb +++ b/app/helpers/alchemy/admin/essences_helper.rb @@ -13,17 +13,17 @@ def essence_picture_thumbnail(content) image_tag( essence.thumbnail_url, alt: picture.name, - class: 'img_paddingtop', - title: Alchemy.t(:image_name) + ": #{picture.name}" + class: "img_paddingtop", + title: Alchemy.t(:image_name) + ": #{picture.name}", ) end # Size value for edit picture dialog def edit_picture_dialog_size(content) if content.settings[:caption_as_textarea] - content.settings[:sizes] ? '380x320' : '380x300' + content.settings[:sizes] ? "380x320" : "380x300" else - content.settings[:sizes] ? '380x290' : '380x255' + content.settings[:sizes] ? "380x290" : "380x255" end end end diff --git a/app/helpers/alchemy/admin/form_helper.rb b/app/helpers/alchemy/admin/form_helper.rb index 2e0dc0a9cd..1a132029a2 100644 --- a/app/helpers/alchemy/admin/form_helper.rb +++ b/app/helpers/alchemy/admin/form_helper.rb @@ -19,7 +19,7 @@ def alchemy_form_for(object, *args, &block) options[:remote] = request.xhr? options[:html] = { id: options.delete(:id), - class: ["alchemy", options.delete(:class)].compact.join(' ') + class: ["alchemy", options.delete(:class)].compact.join(" "), } simple_form_for(object, *(args << options), &block) end diff --git a/app/helpers/alchemy/admin/navigation_helper.rb b/app/helpers/alchemy/admin/navigation_helper.rb index 846e08a628..63654d9cc3 100644 --- a/app/helpers/alchemy/admin/navigation_helper.rb +++ b/app/helpers/alchemy/admin/navigation_helper.rb @@ -12,9 +12,9 @@ module NavigationHelper # def alchemy_main_navigation_entry(alchemy_module) render( - 'alchemy/admin/partials/main_navigation_entry', + "alchemy/admin/partials/main_navigation_entry", alchemy_module: alchemy_module, - navigation: alchemy_module['navigation'] + navigation: alchemy_module["navigation"], ) end @@ -36,8 +36,8 @@ def alchemy_main_navigation_entry(alchemy_module) # def navigate_module(navigation) [ - navigation['action'].to_sym, - navigation['controller'].to_s.gsub(/\A\//, '').gsub(/\//, '_').to_sym + navigation["action"].to_sym, + navigation["controller"].to_s.gsub(/\A\//, "").gsub(/\//, "_").to_sym, ] end @@ -45,9 +45,9 @@ def navigate_module(navigation) # def main_navigation_css_classes(navigation) [ - 'main_navi_entry', - admin_mainnavi_active?(navigation) ? 'active' : nil, - navigation.key?('sub_navigation') ? 'has_sub_navigation' : nil + "main_navi_entry", + admin_mainnavi_active?(navigation) ? "active" : nil, + navigation.key?("sub_navigation") ? "has_sub_navigation" : nil, ].compact end @@ -74,8 +74,8 @@ def entry_active?(entry) # def url_for_module(alchemy_module) route_from_engine_or_main_app( - alchemy_module['engine_name'], - url_options_for_module(alchemy_module) + alchemy_module["engine_name"], + url_options_for_module(alchemy_module), ) end @@ -93,8 +93,8 @@ def url_for_module_sub_navigation(navigation) return if alchemy_module.nil? route_from_engine_or_main_app( - alchemy_module['engine_name'], - url_options_for_navigation_entry(navigation) + alchemy_module["engine_name"], + url_options_for_navigation_entry(navigation), ) end @@ -106,13 +106,13 @@ def sorted_alchemy_modules sorted = [] not_sorted = [] alchemy_modules.map do |m| - if m['position'].blank? + if m["position"].blank? not_sorted << m else sorted << m end end - sorted.sort_by { |m| m['position'] } + not_sorted + sorted.sort_by { |m| m["position"] } + not_sorted end private @@ -138,7 +138,7 @@ def route_from_engine_or_main_app(engine_name, url_options) # A Alchemy module definition # def url_options_for_module(alchemy_module) - url_options_for_navigation_entry(alchemy_module['navigation'] || {}) + url_options_for_navigation_entry(alchemy_module["navigation"] || {}) end # Returns a url options hash for given navigation entry. @@ -148,26 +148,26 @@ def url_options_for_module(alchemy_module) # def url_options_for_navigation_entry(entry) { - controller: entry['controller'], - action: entry['action'], + controller: entry["controller"], + action: entry["action"], only_path: true, - params: entry['params'] + params: entry["params"], }.delete_if { |_k, v| v.nil? } end # Retrieves the current Alchemy module from controller and index action. # def current_alchemy_module - module_definition_for(controller: params[:controller], action: 'index') + module_definition_for(controller: params[:controller], action: "index") end # Returns true if the current controller and action is in a modules navigation definition. # def admin_mainnavi_active?(navigation) # Has the given navigation entry a active sub navigation? - has_active_entry?(navigation['sub_navigation'] || []) || + has_active_entry?(navigation["sub_navigation"] || []) || # Has the given navigation entry a active nested navigation? - has_active_entry?(navigation['nested'] || []) || + has_active_entry?(navigation["nested"] || []) || # Is the navigation entry active? entry_active?(navigation || {}) end @@ -175,7 +175,7 @@ def admin_mainnavi_active?(navigation) # Returns true if the given entry's controller is current controller # def is_entry_controller_active?(entry) - entry['controller'].gsub(/\A\//, '') == params[:controller] + entry["controller"].gsub(/\A\//, "") == params[:controller] end # Returns true if the given entry's action is current controllers action @@ -183,8 +183,8 @@ def is_entry_controller_active?(entry) # Also checks if given entry has a +nested_actions+ key, if so it checks if one of them is current controller's action # def is_entry_action_active?(entry) - entry['action'] == params[:action] || - entry.fetch('nested_actions', []).include?(params[:action]) + entry["action"] == params[:action] || + entry.fetch("nested_actions", []).include?(params[:action]) end # Returns true if an entry of given entries is active. diff --git a/app/helpers/alchemy/admin/pages_helper.rb b/app/helpers/alchemy/admin/pages_helper.rb index f46664bff3..af2037bf3e 100644 --- a/app/helpers/alchemy/admin/pages_helper.rb +++ b/app/helpers/alchemy/admin/pages_helper.rb @@ -9,13 +9,13 @@ module PagesHelper # def preview_sizes_for_select options_for_select([ - 'auto', - [Alchemy.t('240', scope: 'preview_sizes'), 240], - [Alchemy.t('320', scope: 'preview_sizes'), 320], - [Alchemy.t('480', scope: 'preview_sizes'), 480], - [Alchemy.t('768', scope: 'preview_sizes'), 768], - [Alchemy.t('1024', scope: 'preview_sizes'), 1024], - [Alchemy.t('1280', scope: 'preview_sizes'), 1280] + "auto", + [Alchemy.t("240", scope: "preview_sizes"), 240], + [Alchemy.t("320", scope: "preview_sizes"), 320], + [Alchemy.t("480", scope: "preview_sizes"), 480], + [Alchemy.t("768", scope: "preview_sizes"), 768], + [Alchemy.t("1024", scope: "preview_sizes"), 1024], + [Alchemy.t("1280", scope: "preview_sizes"), 1280], ]) end @@ -27,8 +27,8 @@ def page_layout_label(page) if page.persisted? && page.definition.blank? [ page_layout_missing_warning, - Alchemy.t(:page_type) - ].join(' ').html_safe + Alchemy.t(:page_type), + ].join(" ").html_safe else Alchemy.t(:page_type) end @@ -39,10 +39,10 @@ def page_status_checkbox(page, attribute) if page.attribute_fixed?(attribute) checkbox = check_box(:page, attribute, disabled: true) - hint = content_tag(:span, class: 'hint-bubble') do + hint = content_tag(:span, class: "hint-bubble") do Alchemy.t(:attribute_fixed, attribute: attribute) end - content = content_tag(:span, class: 'with-hint') do + content = content_tag(:span, class: "with-hint") do "#{checkbox}\n#{label}\n#{hint}".html_safe end else @@ -50,7 +50,7 @@ def page_status_checkbox(page, attribute) content = "#{checkbox}\n#{label}".html_safe end - content_tag(:label, class: 'checkbox') { content } + content_tag(:label, class: "checkbox") { content } end end end diff --git a/app/helpers/alchemy/admin/pictures_helper.rb b/app/helpers/alchemy/admin/pictures_helper.rb index 0cf9ab0ec4..d2c5f47cc4 100644 --- a/app/helpers/alchemy/admin/pictures_helper.rb +++ b/app/helpers/alchemy/admin/pictures_helper.rb @@ -4,12 +4,10 @@ module Alchemy module Admin module PicturesHelper def preview_size(size) - case size - when 'small' then '80x60' - when 'large' then '240x180' - else - '160x120' - end + Alchemy::Picture::THUMBNAIL_SIZES.fetch( + size, + Alchemy::Picture::THUMBNAIL_SIZES[:medium] + ) end end end diff --git a/app/helpers/alchemy/admin/tags_helper.rb b/app/helpers/alchemy/admin/tags_helper.rb index c9e7a9709a..05212f15f0 100644 --- a/app/helpers/alchemy/admin/tags_helper.rb +++ b/app/helpers/alchemy/admin/tags_helper.rb @@ -12,18 +12,18 @@ module TagsHelper # A HTML string containing
  • tags # def render_tag_list(class_name) - raise ArgumentError, 'Please provide a String as class_name' if class_name.nil? + raise ArgumentError, "Please provide a String as class_name" if class_name.nil? sorted_tags_from(class_name: class_name).map do |tag| - content_tag('li', name: tag.name, class: filtered_by_tag?(tag) ? 'active' : nil) do + content_tag("li", name: tag.name, class: filtered_by_tag?(tag) ? "active" : nil) do link_to( "#{tag.name} (#{tag.taggings_count})", url_for( search_filter_params.except(:page, :tagged_with).merge( - tagged_with: tags_for_filter(current: tag).presence - ) + tagged_with: tags_for_filter(current: tag).presence, + ), ), - remote: request.xhr? + remote: request.xhr?, ) end end.join.html_safe @@ -44,13 +44,13 @@ def tags_for_filter(current:) tags_from_params - Array(current.name) else tags_from_params.push(current.name) - end.uniq.join(',') + end.uniq.join(",") end # Returns tags from params # @returns [Array] def tags_from_params - search_filter_params[:tagged_with].to_s.split(',') + search_filter_params[:tagged_with].to_s.split(",") end def sorted_tags_from(class_name:) diff --git a/app/helpers/alchemy/base_helper.rb b/app/helpers/alchemy/base_helper.rb index 55c60f98c3..76b9826326 100644 --- a/app/helpers/alchemy/base_helper.rb +++ b/app/helpers/alchemy/base_helper.rb @@ -27,16 +27,16 @@ def warning(message, text = nil) # # @return [String] def render_icon(icon_class, options = {}) - options = {style: 'solid'}.merge(options) + options = {style: "solid"}.merge(options) classes = [ "icon fa-fw", "fa-#{icon_class}", "fa#{options[:style].first}", options[:size] ? "fa-#{options[:size]}" : nil, options[:transform] ? "fa-#{options[:transform]}" : nil, - options[:class] + options[:class], ].compact - content_tag('i', nil, class: classes) + content_tag("i", nil, class: classes) end # Returns a div with an icon and the passed content @@ -64,7 +64,7 @@ def render_message(type = :info, msg = nil, &blk) # @param [Symbol] style The style of this flash. Valid values are +:notice+ (default), +:warn+ and +:error+ # def render_flash_notice(notice, style = :notice) - render('alchemy/admin/partials/flash', flash_type: style, message: notice) + render("alchemy/admin/partials/flash", flash_type: style, message: notice) end # Checks if the given argument is a String or a Page object. @@ -93,9 +93,9 @@ def page_or_find(page) # @return [String] The FontAwesome icon name def message_icon_class(message_type) case message_type.to_s - when 'warning', 'warn', 'alert' then 'exclamation' - when 'notice' then 'check' - when 'error' then 'bug' + when "warning", "warn", "alert" then "exclamation" + when "notice" then "check" + when "error" then "bug" else message_type end diff --git a/app/helpers/alchemy/elements_block_helper.rb b/app/helpers/alchemy/elements_block_helper.rb index 91aafeed07..6d5e0459c8 100644 --- a/app/helpers/alchemy/elements_block_helper.rb +++ b/app/helpers/alchemy/elements_block_helper.rb @@ -33,7 +33,7 @@ def render(name, options = {}, html_options = {}) helpers.render(content, { content: content, options: options, - html_options: html_options + html_options: html_options, }) end @@ -107,7 +107,7 @@ def element_view_for(element, options = {}) tag: :div, id: element_dom_id(element), class: element.name, - tags_formatter: ->(tags) { tags.join(" ") } + tags_formatter: ->(tags) { tags.join(" ") }, }.merge(options) # capture inner template block diff --git a/app/helpers/alchemy/elements_helper.rb b/app/helpers/alchemy/elements_helper.rb index 981ee81503..0d3e31e247 100644 --- a/app/helpers/alchemy/elements_helper.rb +++ b/app/helpers/alchemy/elements_helper.rb @@ -91,7 +91,7 @@ module ElementsHelper def render_elements(options = {}) options = { from_page: @page, - render_format: 'html' + render_format: "html", }.update(options) finder = options[:finder] || Alchemy::ElementsFinder.new(options) @@ -150,8 +150,8 @@ def render_elements(options = {}) # def render_element(element, options = {}, counter = 1) if element.nil? - warning('Element is nil') - render "alchemy/elements/view_not_found", {name: 'nil'} + warning("Element is nil") + render "alchemy/elements/view_not_found", {name: "nil"} return end @@ -160,7 +160,7 @@ def render_element(element, options = {}, counter = 1) render element, { element: element, counter: counter, - options: options + options: options, }.merge(options.delete(:locals) || {}) rescue ActionView::MissingTemplate => e warning(%( @@ -179,19 +179,14 @@ def element_dom_id(element) # Renders the HTML tag attributes required for preview mode. def element_preview_code(element) - if respond_to?(:tag_options) - tag_options(element_preview_code_attributes(element)) - else - # Rails 5.1 uses TagBuilder - tag_builder.tag_options(element_preview_code_attributes(element)) - end + tag_builder.tag_options(element_preview_code_attributes(element)) end # Returns a hash containing the HTML tag attributes required for preview mode. def element_preview_code_attributes(element) return {} unless element.present? && @preview_mode && element.page == @page - { 'data-alchemy-element' => element.id } + { "data-alchemy-element" => element.id } end # Returns the element's tags information as a string. Parameters and options @@ -203,12 +198,7 @@ def element_preview_code_attributes(element) # HTML tag attributes containing the element's tag information. # def element_tags(element, options = {}) - if respond_to?(:tag_options) - tag_options(element_tags_attributes(element, options)) - else - # Rails 5.1 uses TagBuilder - tag_builder.tag_options(element_tags_attributes(element, options)) - end + tag_builder.tag_options(element_tags_attributes(element, options)) end # Returns the element's tags information as an attribute hash. @@ -224,12 +214,12 @@ def element_tags(element, options = {}) # def element_tags_attributes(element, options = {}) options = { - formatter: lambda { |tags| tags.join(' ') } + formatter: lambda { |tags| tags.join(" ") }, }.merge(options) return {} if !element.taggable? || element.tag_list.blank? - { 'data-element-tags' => options[:formatter].call(element.tag_list) } + { "data-element-tags" => options[:formatter].call(element.tag_list) } end end end diff --git a/app/helpers/alchemy/pages_helper.rb b/app/helpers/alchemy/pages_helper.rb index b9af4d9c5c..c98ee22d4c 100644 --- a/app/helpers/alchemy/pages_helper.rb +++ b/app/helpers/alchemy/pages_helper.rb @@ -25,19 +25,19 @@ def picture_essence_caption(content) # def language_links(options = {}) options = { - linkname: 'name', + linkname: "name", show_title: true, - spacer: '', - reverse: false + spacer: "", + reverse: false, }.merge(options) - languages = Language.on_current_site.published.with_root_page.order("name #{options[:reverse] ? 'DESC' : 'ASC'}") + languages = Language.on_current_site.published.with_root_page.order("name #{options[:reverse] ? "DESC" : "ASC"}") return nil if languages.count < 2 render( partial: "alchemy/language_links/language", collection: languages, spacer_template: "alchemy/language_links/spacer", - locals: {languages: languages, options: options} + locals: { languages: languages, options: options }, ) end @@ -51,7 +51,7 @@ def render_page_layout render @page, page: @page rescue ActionView::MissingTemplate warning("PageLayout: '#{@page.page_layout}' not found. Rendering standard page_layout.") - render 'alchemy/page_layouts/standard', page: @page + render "alchemy/page_layouts/standard", page: @page end # Renders a partial for current site @@ -78,26 +78,22 @@ def render_site_layout # Menu partials are placed in the `app/views/alchemy/menus` folder # Use the `rails g alchemy:menus` generator to create the partials # - # @param [String] - Name of the menu + # @param [String] - Type of the menu # @param [Hash] - A set of options available in your menu partials - def render_menu(name, options = {}) + def render_menu(menu_type, options = {}) root_node = Alchemy::Node.roots.find_by( - name: name, - language: Alchemy::Language.current + menu_type: menu_type, + language: Alchemy::Language.current, ) if root_node.nil? - warning("Menu with name #{name} not found!") + warning("Menu with type #{menu_type} not found!") return end - options = { - node_partial_name: "#{root_node.view_folder_name}/node" - }.merge(options) - - render(root_node.to_partial_path, menu: root_node, node: root_node, options: options) + render("alchemy/menus/#{menu_type}/wrapper", menu: root_node, options: options) rescue ActionView::MissingTemplate => e warning <<~WARN - Menu partial not found for #{name}. + Menu partial not found for #{menu_type}. #{e} WARN end @@ -124,11 +120,11 @@ def render_breadcrumb(options = {}) page: @page, restricted_only: false, reverse: false, - link_active_page: false + link_active_page: false, }.merge(options) - pages = Page. - ancestors_for(options[:page]). + pages = options[:page]. + self_and_ancestors.contentpages. accessible_by(current_ability, :see) if options.delete(:restricted_only) @@ -136,7 +132,7 @@ def render_breadcrumb(options = {}) end if options.delete(:reverse) - pages = pages.reorder('lft DESC') + pages = pages.reorder("lft DESC") end if options[:without].present? @@ -144,7 +140,7 @@ def render_breadcrumb(options = {}) pages = pages.where.not(id: without.try(:collect, &:id) || without.id) end - render 'alchemy/breadcrumb/wrapper', pages: pages, options: options + render "alchemy/breadcrumb/wrapper", pages: pages, options: options end # Returns current page title @@ -160,7 +156,7 @@ def page_title(options = {}) options = { prefix: "", suffix: "", - separator: "" + separator: "", }.update(options) title_parts = [options[:prefix]] if response.status == 200 @@ -181,7 +177,7 @@ def meta_keywords end def meta_robots - "#{@page.robot_index? ? '' : 'no'}index, #{@page.robot_follow? ? '' : 'no'}follow" + "#{@page.robot_index? ? "" : "no"}index, #{@page.robot_follow? ? "" : "no"}follow" end end end diff --git a/app/helpers/alchemy/url_helper.rb b/app/helpers/alchemy/url_helper.rb index 0ce6846d80..a628ac6ecd 100644 --- a/app/helpers/alchemy/url_helper.rb +++ b/app/helpers/alchemy/url_helper.rb @@ -18,7 +18,7 @@ def show_alchemy_page_url(page, optional_params = {}) # Returns the correct params-hash for passing to show_page_path def show_page_path_params(page, optional_params = {}) - raise ArgumentError, 'Page is nil' if page.nil? + raise ArgumentError, "Page is nil" if page.nil? url_params = {urlname: page.urlname}.update(optional_params) prefix_locale? ? url_params.update(locale: page.language_code) : url_params diff --git a/app/mailers/alchemy/base_mailer.rb b/app/mailers/alchemy/base_mailer.rb index 7a72340240..ff69598fdc 100644 --- a/app/mailers/alchemy/base_mailer.rb +++ b/app/mailers/alchemy/base_mailer.rb @@ -2,7 +2,7 @@ module Alchemy begin - base_class = Object.const_get('::ApplicationMailer') + base_class = Object.const_get("::ApplicationMailer") rescue NameError base_class = ActionMailer::Base end diff --git a/app/mailers/alchemy/messages_mailer.rb b/app/mailers/alchemy/messages_mailer.rb index 61640737e3..b5d2e77080 100644 --- a/app/mailers/alchemy/messages_mailer.rb +++ b/app/mailers/alchemy/messages_mailer.rb @@ -8,7 +8,7 @@ def contact_form_mail(message, mail_to, mail_from, subject) from: mail_from, to: mail_to, reply_to: message.try(:email), - subject: subject + subject: subject, ) end end diff --git a/app/models/alchemy/attachment.rb b/app/models/alchemy/attachment.rb index a3b9a451eb..080247d20b 100644 --- a/app/models/alchemy/attachment.rb +++ b/app/models/alchemy/attachment.rb @@ -22,7 +22,7 @@ class Attachment < BaseRecord include Alchemy::Filetypes include Alchemy::NameConversions include Alchemy::Taggable - include Alchemy::ContentTouching + include Alchemy::TouchElements dragonfly_accessor :file, app: :alchemy_attachments do after_assign { |f| write_attribute(:file_mime_type, f.mime_type) } @@ -30,7 +30,7 @@ class Attachment < BaseRecord stampable stamper_class_name: Alchemy.user_class_name - has_many :essence_files, class_name: 'Alchemy::EssenceFile', foreign_key: 'attachment_id' + has_many :essence_files, class_name: "Alchemy::EssenceFile", foreign_key: "attachment_id" has_many :contents, through: :essence_files has_many :elements, through: :contents has_many :pages, through: :elements @@ -42,24 +42,25 @@ def searchable_alchemy_resource_attributes end def allowed_filetypes - Config.get(:uploader).fetch('allowed_filetypes', {}).fetch('alchemy/attachments', []) + Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/attachments", []) end def file_types_for_select file_types = Alchemy::Attachment.pluck(:file_mime_type).uniq.map do |type| - [Alchemy.t(type, scope: 'mime_types'), type] + [Alchemy.t(type, scope: "mime_types"), type] end file_types.sort_by(&:first) end end validates_presence_of :file - validates_size_of :file, maximum: Config.get(:uploader)['file_size_limit'].megabytes - validates_property :ext, of: :file, + validates_size_of :file, maximum: Config.get(:uploader)["file_size_limit"].megabytes + validates_property :ext, + of: :file, in: allowed_filetypes, case_sensitive: false, message: Alchemy.t("not a valid file"), - unless: -> { self.class.allowed_filetypes.include?('*') } + unless: -> { self.class.allowed_filetypes.include?("*") } before_save :set_name, if: :file_name_changed? @@ -71,13 +72,13 @@ def to_jq_upload { "name" => read_attribute(:file_name), "size" => read_attribute(:file_size), - 'error' => errors[:file].join + "error" => errors[:file].join, } end # An url save filename without format suffix def urlname - CGI.escape(file_name.gsub(/\.#{extension}$/, '').tr('.', ' ')) + CGI.escape(file_name.gsub(/\.#{extension}$/, "").tr(".", " ")) end # Checks if the attachment is restricted, because it is attached on restricted pages only @@ -89,6 +90,7 @@ def restricted? def extension file_name.split(".").last end + alias_method :suffix, :extension # Returns a css class name for kind of file diff --git a/app/models/alchemy/base_record.rb b/app/models/alchemy/base_record.rb index 3a023c6460..300e372563 100644 --- a/app/models/alchemy/base_record.rb +++ b/app/models/alchemy/base_record.rb @@ -1,14 +1,10 @@ # frozen_string_literal: true module Alchemy def self.table_name_prefix - 'alchemy_' + "alchemy_" end class BaseRecord < ActiveRecord::Base self.abstract_class = true - - def active_record_5_1? - ActiveRecord.gem_version >= Gem::Version.new('5.1.0') - end end end diff --git a/app/models/alchemy/content.rb b/app/models/alchemy/content.rb index 25621726c7..c6b34e5f28 100644 --- a/app/models/alchemy/content.rb +++ b/app/models/alchemy/content.rb @@ -28,29 +28,25 @@ class Content < BaseRecord belongs_to :element, touch: true, inverse_of: :contents has_one :page, through: :element - stampable stamper_class_name: Alchemy.user_class_name - - acts_as_list scope: [:element_id] - # Essence scopes - scope :essence_booleans, -> { where(essence_type: "Alchemy::EssenceBoolean") } - scope :essence_dates, -> { where(essence_type: "Alchemy::EssenceDate") } - scope :essence_files, -> { where(essence_type: "Alchemy::EssenceFile") } - scope :essence_htmls, -> { where(essence_type: "Alchemy::EssenceHtml") } - scope :essence_links, -> { where(essence_type: "Alchemy::EssenceLink") } - scope :essence_pictures, -> { where(essence_type: "Alchemy::EssencePicture") } + scope :essence_booleans, -> { where(essence_type: "Alchemy::EssenceBoolean") } + scope :essence_dates, -> { where(essence_type: "Alchemy::EssenceDate") } + scope :essence_files, -> { where(essence_type: "Alchemy::EssenceFile") } + scope :essence_htmls, -> { where(essence_type: "Alchemy::EssenceHtml") } + scope :essence_links, -> { where(essence_type: "Alchemy::EssenceLink") } + scope :essence_pictures, -> { where(essence_type: "Alchemy::EssencePicture") } scope :essence_richtexts, -> { where(essence_type: "Alchemy::EssenceRichtext") } - scope :essence_selects, -> { where(essence_type: "Alchemy::EssenceSelect") } - scope :essence_texts, -> { where(essence_type: "Alchemy::EssenceText") } - scope :named, ->(name) { where(name: name) } - scope :available, -> { published.not_trashed } - scope :published, -> { joins(:element).merge(Element.published) } - scope :not_trashed, -> { joins(:element).merge(Element.not_trashed) } - scope :not_restricted, -> { joins(:element).merge(Element.not_restricted) } - - delegate :restricted?, to: :page, allow_nil: true - delegate :trashed?, to: :element, allow_nil: true - delegate :public?, to: :element, allow_nil: true + scope :essence_selects, -> { where(essence_type: "Alchemy::EssenceSelect") } + scope :essence_texts, -> { where(essence_type: "Alchemy::EssenceText") } + scope :named, ->(name) { where(name: name) } + scope :available, -> { published.not_trashed } + scope :published, -> { joins(:element).merge(Element.published) } + scope :not_trashed, -> { joins(:element).merge(Element.not_trashed) } + scope :not_restricted, -> { joins(:element).merge(Element.not_restricted) } + + delegate :restricted?, to: :page, allow_nil: true + delegate :trashed?, to: :element, allow_nil: true + delegate :public?, to: :element, allow_nil: true class << self # Returns the translated label for a content name. @@ -72,7 +68,7 @@ def translated_label_for(content_name, element_name = nil) Alchemy.t( content_name, scope: "content_names.#{element_name}", - default: Alchemy.t("content_names.#{content_name}", default: content_name.humanize) + default: Alchemy.t("content_names.#{content_name}", default: content_name.humanize), ) end end @@ -132,7 +128,7 @@ def serialize { name: name, value: serialized_ingredient, - link: essence.try(:link) + link: essence.try(:link), }.delete_if { |_k, v| v.blank? } end @@ -174,12 +170,12 @@ def essence_validation_failed? end def has_validations? - definition['validate'].present? + definition["validate"].present? end # Returns a string used as dom id on html elements. def dom_id - return '' if essence.nil? + return "" if essence.nil? "#{essence_partial_name}_#{id}" end @@ -195,7 +191,7 @@ def linked? # Returns true if this content should be taken for element preview. def preview_content? - !!definition['as_element_title'] + !!definition["as_element_title"] end # Proxy method that returns the preview text from essence. @@ -205,7 +201,7 @@ def preview_text(maxlength = 30) end def essence_partial_name - return '' if essence.nil? + return "" if essence.nil? essence.partial_name end diff --git a/app/models/alchemy/content/factory.rb b/app/models/alchemy/content/factory.rb index c01ba3505b..44c197125a 100644 --- a/app/models/alchemy/content/factory.rb +++ b/app/models/alchemy/content/factory.rb @@ -56,11 +56,11 @@ def copy(source, differences = {}) new_content = Content.new( source.attributes. except(*SKIPPED_ATTRIBUTES_ON_COPY). - merge(differences.with_indifferent_access) + merge(differences.with_indifferent_access), ) new_essence = source.essence.class.create!( source.essence.attributes. - except(*SKIPPED_ATTRIBUTES_ON_COPY) + except(*SKIPPED_ATTRIBUTES_ON_COPY), ) new_content.tap do |content| content.essence = new_essence @@ -71,7 +71,7 @@ def copy(source, differences = {}) # Returns all content definitions from elements.yml # def definitions - definitions = Element.definitions.flat_map { |e| e['contents'] } + definitions = Element.definitions.flat_map { |e| e["contents"] } definitions.compact! definitions end @@ -119,7 +119,7 @@ def definition # def build_essence(type = essence_type) self.essence = essence_class(type).new({ - ingredient: default_value + ingredient: default_value, }) end @@ -139,7 +139,7 @@ def create_essence!(type = nil) # If an optional type is passed, this type of essence gets constantized. # def essence_class(type = nil) - Content.normalize_essence_type(type || definition['type']).constantize + Content.normalize_essence_type(type || definition["type"]).constantize end end end diff --git a/app/models/alchemy/element.rb b/app/models/alchemy/element.rb index 2385a3269c..4cef90a3b2 100644 --- a/app/models/alchemy/element.rb +++ b/app/models/alchemy/element.rb @@ -22,6 +22,8 @@ module Alchemy class Element < BaseRecord + NAME_REGEXP = /\A[a-z0-9_-]+\z/ + include Alchemy::Logger include Alchemy::Taggable include Alchemy::Hints @@ -34,7 +36,7 @@ class Element < BaseRecord "hint", "taggable", "compact", - "message" + "message", ].freeze SKIPPED_ATTRIBUTES_ON_COPY = [ @@ -45,7 +47,7 @@ class Element < BaseRecord "folded", "position", "updated_at", - "updater_id" + "updater_id", ].freeze # All Elements that share the same page id and parent element id and are fixed or not are considered a list. @@ -60,17 +62,17 @@ class Element < BaseRecord stampable stamper_class_name: Alchemy.user_class_name - has_many :contents, -> { order(:position) }, dependent: :destroy, inverse_of: :element + has_many :contents, dependent: :destroy, inverse_of: :element has_many :all_nested_elements, -> { order(:position).not_trashed }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", foreign_key: :parent_element_id, dependent: :destroy has_many :nested_elements, -> { order(:position).available }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", foreign_key: :parent_element_id, dependent: :destroy, inverse_of: :parent_element @@ -79,17 +81,17 @@ class Element < BaseRecord # A nested element belongs to a parent element. belongs_to :parent_element, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", optional: true, touch: true, inverse_of: :nested_elements has_and_belongs_to_many :touchable_pages, -> { distinct }, - class_name: 'Alchemy::Page', + class_name: "Alchemy::Page", join_table: ElementToPage.table_name validates_presence_of :name, on: :create - validates_format_of :name, on: :create, with: /\A[a-z0-9_-]+\z/ + validates_format_of :name, on: :create, with: NAME_REGEXP attr_accessor :autogenerate_contents attr_accessor :autogenerate_nested_elements @@ -98,19 +100,19 @@ class Element < BaseRecord after_update :touch_touchable_pages - scope :trashed, -> { where(position: nil).order('updated_at DESC') } - scope :not_trashed, -> { where.not(position: nil) } - scope :published, -> { where(public: true) } - scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) } - scope :available, -> { published.not_trashed } - scope :named, ->(names) { where(name: names) } - scope :excluded, ->(names) { where.not(name: names) } - scope :fixed, -> { where(fixed: true) } - scope :unfixed, -> { where(fixed: false) } - scope :from_current_site, -> { where(Language.table_name => {site_id: Site.current || Site.default}).joins(page: 'language') } - scope :folded, -> { where(folded: true) } - scope :expanded, -> { where(folded: false) } - scope :not_nested, -> { where(parent_element_id: nil) } + scope :trashed, -> { where(position: nil).order("updated_at DESC") } + scope :not_trashed, -> { where.not(position: nil) } + scope :published, -> { where(public: true) } + scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) } + scope :available, -> { published.not_trashed } + scope :named, ->(names) { where(name: names) } + scope :excluded, ->(names) { where.not(name: names) } + scope :fixed, -> { where(fixed: true) } + scope :unfixed, -> { where(fixed: false) } + scope :from_current_site, -> { where(Language.table_name => { site_id: Site.current || Site.default }).joins(page: "language") } + scope :folded, -> { where(folded: true) } + scope :expanded, -> { where(folded: false) } + scope :not_nested, -> { where(parent_element_id: nil) } delegate :restricted?, to: :page, allow_nil: true @@ -132,7 +134,7 @@ class << self def new(attributes = {}) return super if attributes[:name].blank? - element_attributes = attributes.to_h.merge(name: attributes[:name].split('#').first) + element_attributes = attributes.to_h.merge(name: attributes[:name].split("#").first) element_definition = Element.definition_by_name(element_attributes[:name]) if element_definition.nil? raise(ElementDefinitionError, attributes) @@ -154,13 +156,13 @@ def new(attributes = {}) # def copy(source_element, differences = {}) attributes = source_element.attributes.with_indifferent_access - .except(*SKIPPED_ATTRIBUTES_ON_COPY) - .merge(differences) - .merge({ - autogenerate_contents: false, - autogenerate_nested_elements: false, - tag_list: source_element.tag_list - }) + .except(*SKIPPED_ATTRIBUTES_ON_COPY) + .merge(differences) + .merge({ + autogenerate_contents: false, + autogenerate_nested_elements: false, + tag_list: source_element.tag_list, + }) new_element = create!(attributes) @@ -178,7 +180,7 @@ def copy(source_element, differences = {}) def all_from_clipboard(clipboard) return [] if clipboard.nil? - where(id: clipboard.collect { |e| e['id'] }) + where(id: clipboard.collect { |e| e["id"] }) end # All elements in clipboard that could be placed on page @@ -197,7 +199,7 @@ def all_from_clipboard_for_page(clipboard, page) # Pass an element name to get next of this kind. # def next(name = nil) - elements = page.elements.published.where('position > ?', position) + elements = page.elements.published.where("position > ?", position) select_element(elements, name, :asc) end @@ -206,7 +208,7 @@ def next(name = nil) # Pass an element name to get previous of this kind. # def prev(name = nil) - elements = page.elements.published.where('position < ?', position) + elements = page.elements.published.where("position < ?", position) select_element(elements, name, :desc) end @@ -232,7 +234,7 @@ def trashed? # Returns true if the definition of this element has a taggable true value. def taggable? - definition['taggable'] == true + definition["taggable"] == true end # The opposite of folded? @@ -242,7 +244,7 @@ def expanded? # Defined as compact element? def compact? - definition['compact'] == true + definition["compact"] == true end # The element's view partial is dependent from its name @@ -279,7 +281,7 @@ def cache_key # A collection of element names that can be nested inside this element. def nestable_elements - definition.fetch('nestable_elements', []) + definition.fetch("nestable_elements", []) end # Copy all nested elements from current element to given target element. @@ -287,7 +289,7 @@ def copy_nested_elements_to(target_element) nested_elements.map do |nested_element| Element.copy(nested_element, { parent_element_id: target_element.id, - page_id: target_element.page_id + page_id: target_element.page_id, }) end end @@ -295,7 +297,7 @@ def copy_nested_elements_to(target_element) private def generate_nested_elements - definition.fetch('autogenerate', []).each do |nestable_element| + definition.fetch("autogenerate", []).each do |nestable_element| if nestable_elements.include?(nestable_element) Element.create(page: page, parent_element_id: id, name: nestable_element) else diff --git a/app/models/alchemy/element/definitions.rb b/app/models/alchemy/element/definitions.rb index ed42ab42a2..6bea4c26a0 100644 --- a/app/models/alchemy/element/definitions.rb +++ b/app/models/alchemy/element/definitions.rb @@ -19,7 +19,7 @@ def definitions # Returns one element definition by given name. # def definition_by_name(name) - definitions.detect { |d| d['name'] == name } + definitions.detect { |d| d["name"] == name } end private @@ -37,14 +37,14 @@ def read_definitions_file # Returns the +elements.yml+ file path # def definitions_file_path - Rails.root.join 'config/alchemy/elements.yml' + Rails.root.join "config/alchemy/elements.yml" end end # The definition of this element. # def definition - if definition = self.class.definitions.detect { |d| d['name'] == name } + if definition = self.class.definitions.detect { |d| d["name"] == name } definition else log_warning "Could not find element definition for #{name}. Please check your elements.yml file!" diff --git a/app/models/alchemy/element/element_contents.rb b/app/models/alchemy/element/element_contents.rb index 254b941740..6175097b5a 100644 --- a/app/models/alchemy/element/element_contents.rb +++ b/app/models/alchemy/element/element_contents.rb @@ -73,7 +73,7 @@ def copy_contents_to(element) # rss_title: true # def content_for_rss_title - content_for_rss_meta('title') + content_for_rss_meta("title") end # Returns the content that is marked as rss description. @@ -87,14 +87,14 @@ def content_for_rss_title # rss_description: true # def content_for_rss_description - content_for_rss_meta('description') + content_for_rss_meta("description") end # Returns the array with the hashes for all element contents in the elements.yml file def content_definitions return nil if definition.blank? - definition['contents'] + definition["contents"] end # Returns the definition for given content_name @@ -103,7 +103,7 @@ def content_definition_for(content_name) log_warning "Element #{name} is missing the content definition for #{content_name}" nil else - content_definitions.detect { |d| d['name'] == content_name.to_s } + content_definitions.detect { |d| d["name"] == content_name.to_s } end end @@ -139,12 +139,12 @@ def content_for_rss_meta(type) definition = content_definitions.detect { |c| c["rss_#{type}"] } return if definition.blank? - contents.detect { |content| content.name == definition['name'] } + contents.detect { |content| content.name == definition["name"] } end # creates the contents for this element as described in the elements.yml def create_contents - definition.fetch('contents', []).each do |attributes| + definition.fetch("contents", []).each do |attributes| Content.create(attributes.merge(element: self)) end end diff --git a/app/models/alchemy/element/element_essences.rb b/app/models/alchemy/element/element_essences.rb index 706cde4fb2..e94237a8dc 100644 --- a/app/models/alchemy/element/element_essences.rb +++ b/app/models/alchemy/element/element_essences.rb @@ -97,12 +97,12 @@ def essence_error_messages errors.each do |error| messages << Alchemy.t( "#{name}.#{content_name}.#{error}", - scope: 'content_validations', + scope: "content_validations", default: [ "fields.#{content_name}.#{error}".to_sym, - "errors.#{error}".to_sym + "errors.#{error}".to_sym, ], - field: Content.translated_label_for(content_name, name) + field: Content.translated_label_for(content_name, name), ) end end diff --git a/app/models/alchemy/element/presenters.rb b/app/models/alchemy/element/presenters.rb index 2f08573ef1..2feb754185 100644 --- a/app/models/alchemy/element/presenters.rb +++ b/app/models/alchemy/element/presenters.rb @@ -23,7 +23,7 @@ module ClassMethods # If no translation is found a humanized name is used. # def display_name_for(name) - Alchemy.t(name, scope: 'element_names', default: name.to_s.humanize) + Alchemy.t(name, scope: "element_names", default: name.to_s.humanize) end end @@ -32,7 +32,7 @@ def display_name_for(name) # @see Alchemy::Element::Presenters#display_name_for # def display_name - self.class.display_name_for(definition['name'] || name) + self.class.display_name_for(definition["name"] || name) end # Returns a preview text for element. diff --git a/app/models/alchemy/element_to_page.rb b/app/models/alchemy/element_to_page.rb index f48f6d79ef..4fe35ffb56 100644 --- a/app/models/alchemy/element_to_page.rb +++ b/app/models/alchemy/element_to_page.rb @@ -3,7 +3,7 @@ module Alchemy class ElementToPage def self.table_name - [Alchemy::Element.table_name, Alchemy::Page.table_name].join('_') + [Alchemy::Element.table_name, Alchemy::Page.table_name].join("_") end end end diff --git a/app/models/alchemy/essence_boolean.rb b/app/models/alchemy/essence_boolean.rb index b943e4f67a..075ef37f6a 100644 --- a/app/models/alchemy/essence_boolean.rb +++ b/app/models/alchemy/essence_boolean.rb @@ -8,14 +8,12 @@ # value :boolean # created_at :datetime not null # updated_at :datetime not null -# creator_id :integer -# updater_id :integer # # Stores boolean values. # Provides a checkbox in the editor views. module Alchemy class EssenceBoolean < BaseRecord - acts_as_essence ingredient_column: 'value' + acts_as_essence ingredient_column: "value" end end diff --git a/app/models/alchemy/essence_date.rb b/app/models/alchemy/essence_date.rb index 8b4bfa8534..d3b7db2b4e 100644 --- a/app/models/alchemy/essence_date.rb +++ b/app/models/alchemy/essence_date.rb @@ -6,15 +6,13 @@ # # id :integer not null, primary key # date :datetime -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # module Alchemy class EssenceDate < BaseRecord - acts_as_essence ingredient_column: 'date' + acts_as_essence ingredient_column: "date" # Returns self.date for the Element#preview_text method. def preview_text(_maxlength = nil) diff --git a/app/models/alchemy/essence_file.rb b/app/models/alchemy/essence_file.rb index c732875553..3485d53a3a 100644 --- a/app/models/alchemy/essence_file.rb +++ b/app/models/alchemy/essence_file.rb @@ -8,8 +8,6 @@ # attachment_id :integer # title :string # css_class :string -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # link_text :string @@ -18,7 +16,7 @@ module Alchemy class EssenceFile < BaseRecord belongs_to :attachment, optional: true - acts_as_essence ingredient_column: 'attachment' + acts_as_essence ingredient_column: "attachment" def attachment_url return if attachment.nil? @@ -26,7 +24,7 @@ def attachment_url routes.download_attachment_path( id: attachment.id, name: attachment.urlname, - format: attachment.suffix + format: attachment.suffix, ) end diff --git a/app/models/alchemy/essence_html.rb b/app/models/alchemy/essence_html.rb index bb8b5bc7bc..09208db337 100644 --- a/app/models/alchemy/essence_html.rb +++ b/app/models/alchemy/essence_html.rb @@ -6,15 +6,13 @@ # # id :integer not null, primary key # source :text -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # module Alchemy class EssenceHtml < BaseRecord - acts_as_essence ingredient_column: 'source' + acts_as_essence ingredient_column: "source" # Returns the first x (default = 30) (HTML escaped) characters from self.source for the Element#preview_text method. def preview_text(maxlength = 30) diff --git a/app/models/alchemy/essence_link.rb b/app/models/alchemy/essence_link.rb index 6b0f0a5491..77735d8d22 100644 --- a/app/models/alchemy/essence_link.rb +++ b/app/models/alchemy/essence_link.rb @@ -11,12 +11,10 @@ # link_class_name :string # created_at :datetime not null # updated_at :datetime not null -# creator_id :integer -# updater_id :integer # module Alchemy class EssenceLink < BaseRecord - acts_as_essence ingredient_column: 'link' + acts_as_essence ingredient_column: "link" end end diff --git a/app/models/alchemy/essence_node.rb b/app/models/alchemy/essence_node.rb new file mode 100644 index 0000000000..162dba46cc --- /dev/null +++ b/app/models/alchemy/essence_node.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Alchemy + class EssenceNode < BaseRecord + acts_as_essence( + ingredient_column: :node, + preview_text_column: :node_name, + belongs_to: { + class_name: "Alchemy::Node", + foreign_key: :node_id, + inverse_of: :essence_nodes, + optional: true, + }, + ) + + delegate :name, to: :node, prefix: true, allow_nil: true + end +end diff --git a/app/models/alchemy/essence_page.rb b/app/models/alchemy/essence_page.rb index ad369cd975..749583bcdf 100644 --- a/app/models/alchemy/essence_page.rb +++ b/app/models/alchemy/essence_page.rb @@ -2,28 +2,15 @@ module Alchemy class EssencePage < BaseRecord - PAGE_ID = /\A\d+\z/ - acts_as_essence( ingredient_column: :page, preview_text_method: :name, belongs_to: { - class_name: 'Alchemy::Page', + class_name: "Alchemy::Page", foreign_key: :page_id, inverse_of: :essence_pages, - optional: true - } + optional: true, + }, ) - - def ingredient=(page) - case page - when PAGE_ID - self.page = Alchemy::Page.new(id: page) - when Alchemy::Page - self.page = page - else - super - end - end end end diff --git a/app/models/alchemy/essence_picture.rb b/app/models/alchemy/essence_picture.rb index 473f1dbd96..dc777a296a 100644 --- a/app/models/alchemy/essence_picture.rb +++ b/app/models/alchemy/essence_picture.rb @@ -14,8 +14,6 @@ # link_title :string # css_class :string # link_target :string -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # crop_from :string @@ -26,10 +24,10 @@ module Alchemy class EssencePicture < BaseRecord acts_as_essence ingredient_column: :picture, belongs_to: { - class_name: 'Alchemy::Picture', + class_name: "Alchemy::Picture", foreign_key: :picture_id, inverse_of: :essence_pictures, - optional: true + optional: true, } serialize :render_gravity, JSON @@ -83,7 +81,7 @@ def picture_url_options format: picture.default_render_format, crop_from: crop_from.presence, crop_size: crop_size.presence, - size: content.settings[:size] + size: content.settings[:size], }.with_indifferent_access end @@ -105,7 +103,7 @@ def thumbnail_url crop_from: crop_from.presence, crop_size: crop_size.presence, flatten: true, - format: picture.image_file_format + format: picture.image_file_format, } picture.url(options) @@ -201,7 +199,7 @@ def allow_image_cropping? content && content.settings[:crop] && picture && picture.can_be_cropped_to( content.settings[:size], - content.settings[:upsample] + content.settings[:upsample], ) end @@ -231,7 +229,7 @@ def validate_render_gravity end def normalize_crop_value(crop_value) - self[crop_value].split('x').map { |n| normalize_number(n) }.join('x') + self[crop_value].split("x").map { |n| normalize_number(n) }.join("x") end def normalize_number(number) diff --git a/app/models/alchemy/essence_picture_view.rb b/app/models/alchemy/essence_picture_view.rb index d8af6bbec0..ddd97e7c42 100644 --- a/app/models/alchemy/essence_picture_view.rb +++ b/app/models/alchemy/essence_picture_view.rb @@ -41,12 +41,12 @@ def render output = link_to(output, url_for(essence.link), { title: essence.link_title.presence, target: essence.link_target == "blank" ? "_blank" : nil, - data: {link_target: essence.link_target.presence} + data: { link_target: essence.link_target.presence }, }) end if caption - content_tag(:figure, output, {class: essence.css_class.presence}.merge(html_options)) + content_tag(:figure, output, { class: essence.css_class.presence }.merge(html_options)) else output end @@ -66,8 +66,8 @@ def img_tag alt: essence.alt_tag.presence, title: essence.title.presence, class: caption ? nil : essence.css_class.presence, - srcset: srcset.join(', ').presence, - sizes: options[:sizes].join(', ').presence + srcset: srcset.join(", ").presence, + sizes: options[:sizes].join(", ").presence, }.merge(caption ? {} : html_options) ) end diff --git a/app/models/alchemy/essence_richtext.rb b/app/models/alchemy/essence_richtext.rb index 8c545d76c8..606b1af90b 100644 --- a/app/models/alchemy/essence_richtext.rb +++ b/app/models/alchemy/essence_richtext.rb @@ -8,15 +8,13 @@ # body :text # stripped_body :text # public :boolean -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # module Alchemy class EssenceRichtext < BaseRecord - acts_as_essence preview_text_column: 'stripped_body' + acts_as_essence preview_text_column: "stripped_body" before_save :strip_content diff --git a/app/models/alchemy/essence_select.rb b/app/models/alchemy/essence_select.rb index b9e2865fbe..167e94abda 100644 --- a/app/models/alchemy/essence_select.rb +++ b/app/models/alchemy/essence_select.rb @@ -8,13 +8,11 @@ # value :string # created_at :datetime not null # updated_at :datetime not null -# creator_id :integer -# updater_id :integer # # Provides a select box that stores string values. module Alchemy class EssenceSelect < BaseRecord - acts_as_essence ingredient_column: 'value' + acts_as_essence ingredient_column: "value" end end diff --git a/app/models/alchemy/essence_text.rb b/app/models/alchemy/essence_text.rb index 7f10c0ef1c..dbde3047dd 100644 --- a/app/models/alchemy/essence_text.rb +++ b/app/models/alchemy/essence_text.rb @@ -11,8 +11,6 @@ # link_class_name :string # public :boolean default(FALSE) # link_target :string -# creator_id :integer -# updater_id :integer # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/alchemy/language.rb b/app/models/alchemy/language.rb index 3770f1c2c0..9626acdc45 100644 --- a/app/models/alchemy/language.rb +++ b/app/models/alchemy/language.rb @@ -20,15 +20,18 @@ # locale :string # -require_dependency 'alchemy/site' +require_dependency "alchemy/site" module Alchemy class Language < BaseRecord belongs_to :site - has_many :pages + has_many :pages, inverse_of: :language + has_many :nodes, inverse_of: :language before_validation :set_locale, if: -> { locale.blank? } + has_one :root_page, -> { where(parent: nil, layoutpage: false) }, class_name: "Alchemy::Page" + validates :name, presence: true validates :page_layout, presence: true validates :frontpage_name, presence: true @@ -59,8 +62,8 @@ class Language < BaseRecord throw(:abort) end - scope :published, -> { where(public: true) } - scope :with_root_page, -> { joins(:pages).where(Page.table_name => {language_root: true}) } + scope :published, -> { where(public: true) } + scope :with_root_page, -> { joins(:pages).where(Page.table_name => { language_root: true }) } class << self def on_site(site) @@ -104,16 +107,6 @@ def label(attrib) include Alchemy::Language::Code - # Root page - def root_page - @root_page ||= pages.language_roots.first - end - - # Layout root page - def layout_root_page - @layout_root_page ||= Page.layout_root_for(id) - end - # All available locales matching this language # # Matching either the code (+language_code+ + +country_code+) or the +language_code+ @@ -122,10 +115,14 @@ def layout_root_page # def matching_locales @_matching_locales ||= ::I18n.available_locales.select do |locale| - locale.to_s.split('-')[0] == language_code + locale.to_s.split("-")[0] == language_code end end + def available_menu_names + Alchemy::Node.available_menu_names - nodes.reject(&:parent_id).map(&:menu_type) + end + private def set_locale @@ -167,11 +164,7 @@ def remove_old_default end def should_set_pages_language? - if active_record_5_1? - saved_change_to_language_code? || saved_change_to_country_code? - else - language_code_changed? || country_code_changed? - end + saved_change_to_language_code? || saved_change_to_country_code? end def set_pages_language @@ -179,11 +172,7 @@ def set_pages_language end def should_unpublish_pages? - if active_record_5_1? - saved_changes[:public] == [true, false] - else - changes[:public] == [true, false] - end + saved_changes[:public] == [true, false] end def unpublish_pages diff --git a/app/models/alchemy/language/code.rb b/app/models/alchemy/language/code.rb index 3b90d98b75..78d257315e 100644 --- a/app/models/alchemy/language/code.rb +++ b/app/models/alchemy/language/code.rb @@ -4,7 +4,7 @@ module Alchemy::Language::Code extend ActiveSupport::Concern def code - [language_code, country_code].select(&:present?).join('-') + [language_code, country_code].select(&:present?).join("-") end def code=(code) @@ -13,11 +13,11 @@ def code=(code) module ClassMethods def find_by_code(code) - codes = code.split('-') - codes << '' if codes.length == 1 + codes = code.split("-") + codes << "" if codes.length == 1 on_current_site.find_by( language_code: codes[0], - country_code: codes[1] + country_code: codes[1], ) end end diff --git a/app/models/alchemy/legacy_page_url.rb b/app/models/alchemy/legacy_page_url.rb index 5e24960d0b..f733105e28 100644 --- a/app/models/alchemy/legacy_page_url.rb +++ b/app/models/alchemy/legacy_page_url.rb @@ -13,7 +13,7 @@ class Alchemy::LegacyPageUrl < ActiveRecord::Base belongs_to :page, - class_name: 'Alchemy::Page', + class_name: "Alchemy::Page", required: true validates :urlname, diff --git a/app/models/alchemy/message.rb b/app/models/alchemy/message.rb index e1c5fea234..64326e07c5 100644 --- a/app/models/alchemy/message.rb +++ b/app/models/alchemy/message.rb @@ -22,17 +22,17 @@ def self.config attr_accessor :contact_form_id, :ip - config['fields'].each do |field| + config["fields"].each do |field| attr_accessor field.to_sym end - config['validate_fields'].each do |field| + config["validate_fields"].each do |field| validates_presence_of field case field.to_sym when /email/ validates_format_of field, - with: Alchemy::Config.get('format_matchers')['email'], + with: Alchemy::Config.get("format_matchers")["email"], if: -> { send(field).present? } when :email_confirmation validates_confirmation_of :email diff --git a/app/models/alchemy/node.rb b/app/models/alchemy/node.rb index 7649a5d64a..187c310cf7 100644 --- a/app/models/alchemy/node.rb +++ b/app/models/alchemy/node.rb @@ -4,13 +4,22 @@ module Alchemy class Node < BaseRecord VALID_URL_REGEX = /\A(\/|\D[a-z\+\d\.\-]+:)/ - acts_as_nested_set scope: 'language_id', touch: true + before_destroy :check_if_related_essence_nodes_present + + acts_as_nested_set scope: "language_id", touch: true stampable stamper_class_name: Alchemy.user_class_name - belongs_to :site, class_name: 'Alchemy::Site' - belongs_to :language, class_name: 'Alchemy::Language' - belongs_to :page, class_name: 'Alchemy::Page', optional: true, inverse_of: :nodes + belongs_to :language, class_name: "Alchemy::Language" + belongs_to :page, class_name: "Alchemy::Page", optional: true, inverse_of: :nodes + + has_one :site, through: :language + + has_many :essence_nodes, class_name: "Alchemy::EssenceNode", foreign_key: :node_id, inverse_of: :ingredient_association + before_validation :translate_root_menu_name, if: -> { root? } + before_validation :set_menu_type_from_root, unless: -> { root? } + + validates :menu_type, presence: true validates :name, presence: true, if: -> { page.nil? } validates :url, format: { with: VALID_URL_REGEX }, unless: -> { url.nil? } @@ -25,7 +34,7 @@ def name class << self # Returns all root nodes for current language def language_root_nodes - raise 'No language found' if Language.current.nil? + raise "No language found" if Language.current.nil? roots.where(language_id: Language.current.id) end @@ -49,7 +58,7 @@ def read_definitions_file # Returns the +menus.yml+ file path # def definitions_file_path - Rails.root.join 'config/alchemy/menus.yml' + Rails.root.join "config/alchemy/menus.yml" end end @@ -58,15 +67,29 @@ def definitions_file_path # Either the value is stored in the database, aka. an external url. # Or, if attached, the values comes from a page. def url - page && "/#{page.urlname}" || read_attribute(:url).presence + page&.url_path || read_attribute(:url).presence end def to_partial_path - "#{view_folder_name}/wrapper" + "alchemy/menus/#{menu_type}/node" + end + + private + + def check_if_related_essence_nodes_present + dependent_essence_nodes = self_and_descendants.flat_map(&:essence_nodes) + if dependent_essence_nodes.any? + errors.add(:base, :essence_nodes_present, page_names: dependent_essence_nodes.map(&:page).map(&:name).to_sentence) + throw(:abort) + end + end + + def translate_root_menu_name + self.name ||= Alchemy.t(menu_type, scope: :menu_names) end - def view_folder_name - "alchemy/menus/#{name.parameterize.underscore}" + def set_menu_type_from_root + self.menu_type = root.menu_type end end end diff --git a/app/models/alchemy/page.rb b/app/models/alchemy/page.rb index f4d6b051d9..669ad7b92c 100644 --- a/app/models/alchemy/page.rb +++ b/app/models/alchemy/page.rb @@ -17,7 +17,6 @@ # rgt :integer # parent_id :integer # depth :integer -# visible :boolean default(FALSE) # locked_by :integer # restricted :boolean default(FALSE) # robot_index :boolean default(TRUE) @@ -44,11 +43,10 @@ class Page < BaseRecord DEFAULT_ATTRIBUTES_FOR_COPY = { autogenerate_elements: false, - visible: false, public_on: nil, public_until: nil, locked_at: nil, - locked_by: nil + locked_by: nil, } SKIPPED_ATTRIBUTES_ON_COPY = %w( @@ -78,16 +76,15 @@ class Page < BaseRecord :tag_list, :title, :urlname, - :visible, :layoutpage, - :menu_id + :menu_id, ] - acts_as_nested_set(dependent: :destroy) + acts_as_nested_set(dependent: :destroy, scope: [:layoutpage, :language_id]) stampable stamper_class_name: Alchemy.user_class_name - belongs_to :language, optional: true + belongs_to :language belongs_to :creator, primary_key: Alchemy.user_class_primary_key, @@ -110,42 +107,33 @@ class Page < BaseRecord has_one :site, through: :language has_many :site_languages, through: :site, source: :languages has_many :folded_pages - has_many :legacy_urls, class_name: 'Alchemy::LegacyPageUrl' - has_many :nodes, class_name: 'Alchemy::Node', inverse_of: :page + has_many :legacy_urls, class_name: "Alchemy::LegacyPageUrl" + has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page - validates_presence_of :language, on: :create, unless: :root - validates_presence_of :page_layout, unless: :systempage? - validates_format_of :page_layout, with: /\A[a-z0-9_-]+\z/, unless: -> { systempage? || page_layout.blank? } - validates_presence_of :parent_id, if: proc { Page.count > 1 } + before_validation :set_language, + if: -> { language.nil? } + + validates_presence_of :page_layout + validates_format_of :page_layout, with: /\A[a-z0-9_-]+\z/, unless: -> { page_layout.blank? } + validates_presence_of :parent, unless: -> { layoutpage? || language_root? } before_save :set_language_code, - if: -> { language.present? }, - unless: :systempage? + if: -> { language.present? } before_save :set_restrictions_to_child_pages, - if: :restricted_changed?, - unless: :systempage? + if: :restricted_changed? before_save :inherit_restricted_status, - if: -> { parent && parent.restricted? }, - unless: :systempage? + if: -> { parent && parent.restricted? } before_save :set_published_at, - if: -> { public_on.present? && published_at.nil? }, - unless: :systempage? + if: -> { public_on.present? && published_at.nil? } before_save :set_fixed_attributes, if: -> { fixed_attributes.any? } - before_create :set_language_from_parent_or_default, - if: -> { language_id.blank? }, - unless: :systempage? - after_update :create_legacy_url, - if: :should_create_legacy_url? - - after_update :attach_to_menu!, - if: :should_attach_to_menu? + if: :saved_change_to_urlname? after_update -> { nodes.update_all(updated_at: Time.current) } @@ -158,22 +146,9 @@ class Page < BaseRecord # site_name accessor delegate :name, to: :site, prefix: true, allow_nil: true - attr_accessor :menu_id - # Class methods # class << self - # The root page of the page tree - # - # Internal use only. You wouldn't use this page ever. - # - # Automatically created when accessed the first time. - # - def root - super || create!(name: 'Root') - end - alias_method :rootpage, :root - # Used to store the current page previewed in the edit page template. # def current_preview=(page) @@ -217,30 +192,12 @@ def copy(source, differences = {}) end end - def layout_root_for(language_id) - where({parent_id: Page.root.id, layoutpage: true, language_id: language_id}).limit(1).first - end - - def find_or_create_layout_root_for(language_id) - layoutroot = layout_root_for(language_id) - return layoutroot if layoutroot - - language = Language.find(language_id) - Page.create!( - name: "Layoutroot for #{language.name}", - layoutpage: true, - language: language, - autogenerate_elements: false, - parent_id: Page.root.id - ) - end - def copy_and_paste(source, new_parent, new_name) page = copy(source, { parent_id: new_parent.id, language: new_parent.language, name: new_name, - title: new_name + title: new_name, }) if source.children.any? source.copy_children_to(page) @@ -251,7 +208,7 @@ def copy_and_paste(source, new_parent, new_name) def all_from_clipboard(clipboard) return [] if clipboard.blank? - where(id: clipboard.collect { |p| p['id'] }) + where(id: clipboard.collect { |p| p["id"] }) end def all_from_clipboard_for_select(clipboard, language_id, layoutpage = false) @@ -259,28 +216,20 @@ def all_from_clipboard_for_select(clipboard, language_id, layoutpage = false) clipboard_pages = all_from_clipboard(clipboard) allowed_page_layouts = Alchemy::PageLayout.selectable_layouts(language_id, layoutpage) - allowed_page_layout_names = allowed_page_layouts.collect { |p| p['name'] } + allowed_page_layout_names = allowed_page_layouts.collect { |p| p["name"] } clipboard_pages.select { |cp| allowed_page_layout_names.include?(cp.page_layout) } end def link_target_options - options = [[Alchemy.t(:default, scope: 'link_target_options'), '']] + options = [[Alchemy.t(:default, scope: "link_target_options"), ""]] link_target_options = Config.get(:link_target_options) link_target_options.each do |option| - options << [Alchemy.t(option, scope: 'link_target_options', - default: option.to_s.humanize), option] + options << [Alchemy.t(option, scope: "link_target_options", + default: option.to_s.humanize), option] end options end - # Returns an array of all pages in the same branch from current. - # I.e. used to find the active page in navigation. - def ancestors_for(current) - return [] if current.nil? - - current.self_and_ancestors.contentpages - end - private # Aggregates the attributes from given source for copy of page. @@ -295,7 +244,7 @@ def attributes_from_source_for_copy(source, differences = {}) differences.stringify_keys! attributes = source.attributes.merge(differences) attributes.merge!(DEFAULT_ATTRIBUTES_FOR_COPY) - attributes['name'] = new_name_for_copy(differences['name'], source.name) + attributes["name"] = new_name_for_copy(differences["name"], source.name) attributes.except(*SKIPPED_ATTRIBUTES_ON_COPY) end @@ -312,7 +261,7 @@ def attributes_from_source_for_copy(source, differences = {}) def new_name_for_copy(custom_name, source_name) return custom_name if custom_name.present? - "#{source_name} (#{Alchemy.t('Copy')})" + "#{source_name} (#{Alchemy.t("Copy")})" end end @@ -345,6 +294,13 @@ def find_elements(options = {}) finder.elements(page: self) end + # = The url_path for this page + # + # @see Alchemy::Page::UrlPath#call + def url_path + Alchemy::Page::UrlPath.new(self).call + end + # The page's view partial is dependent from its page layout # # == Define page layouts @@ -371,9 +327,10 @@ def to_partial_path # only public pages (true), skip public pages (false) # def previous(options = {}) - pages = self_and_siblings.where('lft < ?', lft) + pages = self_and_siblings.where("lft < ?", lft) select_page(pages, options.merge(order: :desc)) end + alias_method :previous_page, :previous # Returns the next page on the same level or nil. @@ -384,9 +341,10 @@ def previous(options = {}) # only public pages (true), skip public pages (false) # def next(options = {}) - pages = self_and_siblings.where('lft > ?', lft) + pages = self_and_siblings.where("lft > ?", lft) select_page(pages, options.merge(order: :asc)) end + alias_method :next_page, :next # Locks the page to given user @@ -435,7 +393,7 @@ def copy_children_to(new_parent) new_child = Page.copy(child, { language_id: new_parent.language_id, - language_code: new_parent.language_code + language_code: new_parent.language_code, }) new_child.move_to_child_of(new_parent) child.copy_children_to(new_child) unless child.children.blank? @@ -454,7 +412,7 @@ def publish! update_columns( published_at: current_time, public_on: already_public_for?(current_time) ? public_on : current_time, - public_until: still_public_for?(current_time) ? public_until : nil + public_until: still_public_for?(current_time) ? public_until : nil, ) end @@ -467,9 +425,9 @@ def publish! # A tree node with new lft, rgt, depth, url, parent_id and restricted indexes to be updated # def update_node!(node) - hash = {lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted} + hash = { lft: node.left, rgt: node.right, parent_id: node.parent, depth: node.depth, restricted: node.restricted } - if Config.get(:url_nesting) && urlname != node.url + if urlname != node.url LegacyPageUrl.create(page_id: id, urlname: urlname) hash[:urlname] = node.url end @@ -520,7 +478,7 @@ def public_until # does not respond to +#name+ it returns +'unknown'+ # def creator_name - creator.try(:name) || Alchemy.t('unknown') + creator.try(:name) || Alchemy.t("unknown") end # Returns the name of the last updater of this page. @@ -529,7 +487,7 @@ def creator_name # does not respond to +#name+ it returns +'unknown'+ # def updater_name - updater.try(:name) || Alchemy.t('unknown') + updater.try(:name) || Alchemy.t("unknown") end # Returns the name of the user currently editing this page. @@ -538,7 +496,7 @@ def updater_name # does not respond to +#name+ it returns +'unknown'+ # def locker_name - locker.try(:name) || Alchemy.t('unknown') + locker.try(:name) || Alchemy.t("unknown") end # Menus (aka. root nodes) this page is attached to @@ -562,8 +520,8 @@ def select_page(pages, options = {}) .limit(1).first end - def set_language_from_parent_or_default - self.language = parent.language || Language.default + def set_language + self.language = parent&.language || Language.current set_language_code end @@ -571,41 +529,13 @@ def set_language_code self.language_code = language.code end - def should_create_legacy_url? - if active_record_5_1? - saved_change_to_urlname? - else - urlname_changed? - end - end - # Stores the old urlname in a LegacyPageUrl def create_legacy_url - if active_record_5_1? - former_urlname = urlname_before_last_save - else - former_urlname = urlname_was - end - legacy_urls.find_or_create_by(urlname: former_urlname) + legacy_urls.find_or_create_by(urlname: urlname_before_last_save) end def set_published_at self.published_at = Time.current end - - def attach_to_menu! - current_site_id = Alchemy::Site.current.id - node = Alchemy::Node.find_by!(id: menu_id, site_id: current_site_id) - node.children.create!( - site_id: current_site_id, - language_id: language_id, - page_id: id, - name: name - ) - end - - def should_attach_to_menu? - menu_id.present? && nodes.none? - end end end diff --git a/app/models/alchemy/page/fixed_attributes.rb b/app/models/alchemy/page/fixed_attributes.rb index 9a91116d92..679a058eb0 100644 --- a/app/models/alchemy/page/fixed_attributes.rb +++ b/app/models/alchemy/page/fixed_attributes.rb @@ -15,7 +15,6 @@ module Alchemy # fixed_attributes: # - public_on: nil # - public_until: nil - # - visible: false # class Page::FixedAttributes attr_reader :page @@ -31,7 +30,7 @@ def initialize(page) # @return Hash # def attributes - @_attributes ||= page.definition.fetch('fixed_attributes', {}).symbolize_keys + @_attributes ||= page.definition.fetch("fixed_attributes", {}).symbolize_keys end alias_method :all, :attributes diff --git a/app/models/alchemy/page/page_elements.rb b/app/models/alchemy/page/page_elements.rb index 7caa06ce01..ea9fbe33c0 100644 --- a/app/models/alchemy/page/page_elements.rb +++ b/app/models/alchemy/page/page_elements.rb @@ -9,37 +9,37 @@ module Page::PageElements has_many :all_elements, -> { order(:position) }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :elements, -> { order(:position).not_nested.unfixed.available }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :trashed_elements, -> { Element.trashed.order(:position) }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :fixed_elements, -> { order(:position).fixed.available }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", inverse_of: :page has_many :dependent_destroyable_elements, -> { not_nested }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", dependent: :destroy has_many :contents, through: :elements has_and_belongs_to_many :to_be_swept_elements, -> { distinct }, - class_name: 'Alchemy::Element', + class_name: "Alchemy::Element", join_table: ElementToPage.table_name after_create :generate_elements, - unless: -> { systempage? || autogenerate_elements == false } + unless: -> { autogenerate_elements == false } after_update :trash_not_allowed_elements!, - if: :has_page_layout_changed? + if: :saved_change_to_page_layout? after_update :generate_elements, - if: :has_page_layout_changed? + if: :saved_change_to_page_layout? end module ClassMethods @@ -53,7 +53,7 @@ def copy_elements(source, target) source_elements = source.all_elements.not_nested.not_trashed source_elements.order(:position).map do |source_element| Element.copy(source_element, { - page_id: target.id + page_id: target.id, }).tap(&:move_to_bottom) end end @@ -81,11 +81,11 @@ def copy_elements(source, target) # def available_element_definitions(only_element_named = nil) @_element_definitions ||= if only_element_named - definition = Element.definition_by_name(only_element_named) - element_definitions_by_name(definition['nestable_elements']) - else - element_definitions - end + definition = Element.definition_by_name(only_element_named) + element_definitions_by_name(definition["nestable_elements"]) + else + element_definitions + end return [] if @_element_definitions.blank? @@ -100,20 +100,20 @@ def available_element_definitions(only_element_named = nil) # All names of elements that can actually be placed on current page. # def available_element_names - @_available_element_names ||= available_element_definitions.map { |e| e['name'] } + @_available_element_names ||= available_element_definitions.map { |e| e["name"] } end # Available element definitions excluding nested unique elements. # def available_elements_within_current_scope(parent) @_available_elements = if parent - parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name) - available_element_definitions(parent.name).reject do |e| - parents_unique_nested_elements.include? e['name'] + parents_unique_nested_elements = parent.nested_elements.where(unique: true).pluck(:name) + available_element_definitions(parent.name).reject do |e| + parents_unique_nested_elements.include? e["name"] + end + else + available_element_definitions end - else - available_element_definitions - end end # All element definitions defined for page's page layout @@ -129,10 +129,10 @@ def element_definitions # def descendent_element_definitions definitions = element_definitions_by_name(element_definition_names) - definitions.select { |d| d.key?('nestable_elements') }.each do |d| - definitions += element_definitions_by_name(d['nestable_elements']) + definitions.select { |d| d.key?("nestable_elements") }.each do |d| + definitions += element_definitions_by_name(d["nestable_elements"]) end - definitions.uniq { |d| d['name'] } + definitions.uniq { |d| d["name"] } end # All names of elements that are defined in the page definition. @@ -145,7 +145,7 @@ def descendent_element_definitions # elements: [headline, contactform] # def element_definition_names - definition['elements'] || [] + definition["elements"] || [] end # Element definitions with given name(s) @@ -161,7 +161,7 @@ def element_definitions_by_name(names) if names.to_s == "all" Element.definitions else - Element.definitions.select { |e| names.include? e['name'] } + Element.definitions.select { |e| names.include? e["name"] } end end @@ -174,14 +174,14 @@ def element_definitions_by_name(names) # feed_elements: [element_name, element_2_name] # def feed_elements - elements.named(definition['feed_elements']) + elements.named(definition["feed_elements"]) end # Returns an array of all EssenceRichtext contents ids from not folded elements # def richtext_contents_ids Alchemy::Content.joins(:element) - .where(Element.table_name => {page_id: id, folded: false}) + .where(Element.table_name => { page_id: id, folded: false }) .select(&:has_tinymce?) .collect(&:id) end @@ -195,7 +195,7 @@ def richtext_contents_ids def generate_elements existing_elements = all_elements.not_nested.not_trashed existing_element_names = existing_elements.pluck(:name).uniq - definition.fetch('autogenerate', []).each do |element_name| + definition.fetch("autogenerate", []).each do |element_name| next if existing_element_names.include?(element_name) Element.create(page: self, name: element_name) @@ -206,24 +206,16 @@ def generate_elements def trash_not_allowed_elements! not_allowed_elements = elements.where([ "#{Element.table_name}.name NOT IN (?)", - element_definition_names + element_definition_names, ]) not_allowed_elements.to_a.map(&:trash!) end - def has_page_layout_changed? - if active_record_5_1? - saved_change_to_page_layout? - else - page_layout_changed? - end - end - # Deletes unique and already present definitions from @_element_definitions. # def delete_unique_element_definitions! @_element_definitions.delete_if do |element| - element['unique'] && @_existing_element_names.include?(element['name']) + element["unique"] && @_existing_element_names.include?(element["name"]) end end @@ -231,8 +223,8 @@ def delete_unique_element_definitions! # def delete_outnumbered_element_definitions! @_element_definitions.delete_if do |element| - outnumbered = @_existing_element_names.select { |name| name == element['name'] } - element['amount'] && outnumbered.count >= element['amount'].to_i + outnumbered = @_existing_element_names.select { |name| name == element["name"] } + element["amount"] && outnumbered.count >= element["amount"].to_i end end end diff --git a/app/models/alchemy/page/page_naming.rb b/app/models/alchemy/page/page_naming.rb index dd55a5668f..6c6f8f61b5 100644 --- a/app/models/alchemy/page/page_naming.rb +++ b/app/models/alchemy/page/page_naming.rb @@ -9,24 +9,22 @@ module Page::PageNaming included do before_validation :set_urlname, if: :renamed?, - unless: -> { systempage? || name.blank? } + unless: -> { name.blank? } validates :name, presence: true validates :urlname, - uniqueness: {scope: [:language_id, :layoutpage], if: -> { urlname.present? }}, - exclusion: {in: RESERVED_URLNAMES}, - length: {minimum: 3, if: -> { urlname.present? }} + uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } }, + exclusion: { in: RESERVED_URLNAMES }, + length: { minimum: 3, if: -> { urlname.present? } } before_save :set_title, - unless: -> { systempage? }, if: -> { title.blank? } after_update :update_descendants_urlnames, - if: :should_update_descendants_urlnames? + if: :saved_change_to_urlname? - after_move :update_urlname!, - if: -> { Config.get(:url_nesting) } + after_move :update_urlname! end # Returns true if name or urlname has changed. @@ -37,7 +35,7 @@ def renamed? # Makes a slug of all ancestors urlnames including mine and delimit them be slash. # So the whole path is stored as urlname in the database. def update_urlname! - new_urlname = nested_url_name(slug) + new_urlname = nested_url_name if urlname != new_urlname legacy_urls.create(urlname: urlname) update_column(:urlname, new_urlname) @@ -46,34 +44,11 @@ def update_urlname! # Returns always the last part of a urlname path def slug - urlname.to_s.split('/').last - end - - # Returns an array of visible/non-language_root ancestors. - def visible_ancestors - return [] unless parent - - if new_record? - parent.visible_ancestors.tap do |base| - base.push(parent) if parent.visible? - end - else - ancestors.visible.contentpages.where(language_root: nil).to_a - end + urlname.to_s.split("/").last end private - def should_update_descendants_urlnames? - return false if !Config.get(:url_nesting) - - if active_record_5_1? - saved_change_to_urlname? || saved_change_to_visible? - else - urlname_changed? || visible_changed? - end - end - def update_descendants_urlnames reload descendants.each(&:update_urlname!) @@ -81,14 +56,9 @@ def update_descendants_urlnames # Sets the urlname to a url friendly slug. # Either from name, or if present, from urlname. - # If url_nesting is enabled the urlname contains the whole path. + # The urlname contains the whole path including parent urlnames. def set_urlname - if Config.get(:url_nesting) - value = slug - else - value = urlname - end - self[:urlname] = nested_url_name(value) + self[:urlname] = nested_url_name end def set_title @@ -100,26 +70,17 @@ def set_title # Names shorter than 3 will be filled up with dashes, # so it does not collidate with the language code. # - def convert_url_name(value) - url_name = convert_to_urlname(value.blank? ? name : value) - if url_name.length < 3 - ('-' * (3 - url_name.length)) + url_name - else - url_name - end + def converted_url_name + url_name = convert_to_urlname(slug.blank? ? name : slug) + url_name.rjust(3, "-") end - def nested_url_name(value) - (ancestor_slugs << convert_url_name(value)).join('/') - end - - # Slugs of all visible/non-language_root ancestors. - # Returns [], if there is no parent, the parent is - # the root page itself, or url_nesting is off. - def ancestor_slugs - return [] if !Config.get(:url_nesting) || parent.nil? || parent.root? - - visible_ancestors.map(&:slug).compact + def nested_url_name + if parent&.language_root? + converted_url_name + else + [parent&.urlname, converted_url_name].compact.join("/") + end end end end diff --git a/app/models/alchemy/page/page_natures.rb b/app/models/alchemy/page/page_natures.rb index 63be5afe2b..5347f35596 100644 --- a/app/models/alchemy/page/page_natures.rb +++ b/app/models/alchemy/page/page_natures.rb @@ -14,19 +14,13 @@ def expiration_time end def taggable? - definition['taggable'] == true + definition["taggable"] == true end def rootpage? !new_record? && parent_id.blank? end - def systempage? - return true if Page.count.zero? - - rootpage? || (parent_id == Page.root.id && !language_root?) - end - def folded?(user_id) return unless Alchemy.user_class < ActiveRecord::Base @@ -67,9 +61,8 @@ def locked? def status { public: public?, - visible: visible?, locked: locked?, - restricted: restricted? + restricted: restricted?, } end @@ -95,7 +88,7 @@ def definition # Page layout names are defined inside the config/alchemy/page_layouts.yml file. # Translate the name in your config/locales language yml file. def layout_display_name - Alchemy.t(page_layout, scope: 'page_layout_names') + Alchemy.t(page_layout, scope: "page_layout_names") end # Returns the name for the layout partial @@ -155,7 +148,7 @@ def cache_page? return false unless caching_enabled? page_layout = PageLayout.get(self.page_layout) - page_layout['cache'] != false && page_layout['searchresults'] != true + page_layout["cache"] != false && page_layout["searchresults"] != true end private diff --git a/app/models/alchemy/page/page_scopes.rb b/app/models/alchemy/page/page_scopes.rb index acfbafd2a1..8f6255750b 100644 --- a/app/models/alchemy/page/page_scopes.rb +++ b/app/models/alchemy/page/page_scopes.rb @@ -21,20 +21,17 @@ module Page::PageScopes # All pages locked by given user # - scope :locked_by, ->(user) { - if user.class.respond_to? :primary_key - locked.where(locked_by: user.send(user.class.primary_key)) - end - } + scope :locked_by, + ->(user) { + if user.class.respond_to? :primary_key + locked.where(locked_by: user.send(user.class.primary_key)) + end + } # All not locked pages # scope :not_locked, -> { where(locked_at: nil, locked_by: nil) } - # All visible pages - # - scope :visible, -> { where(visible: true) } - # All not restricted pages # scope :not_restricted, -> { where(restricted: false) } @@ -45,29 +42,27 @@ module Page::PageScopes # All pages that are a published language root # - scope :public_language_roots, -> { - published.language_roots.where( - language_code: Language.published.pluck(:language_code) - ) - } + scope :public_language_roots, + -> { + published.language_roots.where( + language_code: Language.published.pluck(:language_code), + ) + } # Last 5 pages that where recently edited by given user # - scope :all_last_edited_from, ->(user) { - where(updater_id: user.id).order('updated_at DESC').limit(5) - } + scope :all_last_edited_from, + ->(user) { + where(updater_id: user.id).order("updated_at DESC").limit(5) + } # Returns all pages that have the given +language_id+ # - scope :with_language, ->(language_id) { - where(language_id: language_id) - } + scope :with_language, ->(language_id) { where(language_id: language_id) } # Returns all content pages. # - scope :contentpages, -> { - where(layoutpage: [false, nil]).where.not(parent_id: nil) - } + scope :contentpages, -> { where(layoutpage: [false, nil]) } # Returns all public contentpages that are not locked. # @@ -79,9 +74,7 @@ module Page::PageScopes # # Used for flushing all pages caches at once. # - scope :flushable_layoutpages, -> { - not_locked.layoutpages.where.not(parent_id: Page.unscoped.root.id) - } + scope :flushable_layoutpages, -> { not_locked.layoutpages } # All searchable pages # @@ -89,9 +82,10 @@ module Page::PageScopes # All pages from +Alchemy::Site.current+ # - scope :from_current_site, -> { - where(Language.table_name => {site_id: Site.current || Site.default}).joins(:language) - } + scope :from_current_site, + -> { + where(Language.table_name => { site_id: Site.current || Site.default }).joins(:language) + } # All pages for xml sitemap # diff --git a/app/models/alchemy/page/url_path.rb b/app/models/alchemy/page/url_path.rb new file mode 100644 index 0000000000..b5fca4aa08 --- /dev/null +++ b/app/models/alchemy/page/url_path.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Alchemy + class Page + # = The url_path for this page + # + # Use this to build relative links to this page + # + # It takes several circumstances into account: + # + # 1. It returns just a slash for language root pages of the default langauge + # 2. It returns a url path with a leading slash for regular pages + # 3. It returns a url path with a leading slash and language code prefix for pages not having the default language + # 4. It returns a url path with a leading slash and the language code for language root pages of a non-default language + # + # == Examples + # + # Using Rails' link_to helper + # + # link_to page.url + # + class UrlPath + ROOT_PATH = "/" + + def initialize(page) + @page = page + @language = @page.language + @site = @language.site + end + + def call + if @page.language_root? + language_root_path + elsif @site.languages.select(&:public?).length > 1 + page_path_with_language_prefix + else + page_path_with_leading_slash + end + end + + private + + def language_root_path + @language.default? ? ROOT_PATH : language_path + end + + def page_path_with_language_prefix + @language.default? ? page_path : language_path + page_path + end + + def page_path_with_leading_slash + @page.language_root? ? ROOT_PATH : page_path + end + + def language_path + "/#{@page.language_code}" + end + + def page_path + "/#{@page.urlname}" + end + end + end +end diff --git a/app/models/alchemy/picture.rb b/app/models/alchemy/picture.rb index 9deb1fd11e..961f14805f 100644 --- a/app/models/alchemy/picture.rb +++ b/app/models/alchemy/picture.rb @@ -22,17 +22,23 @@ module Alchemy class Picture < BaseRecord + THUMBNAIL_SIZES = { + small: "80x60", + medium: "160x120", + large: "240x180", + }.with_indifferent_access.freeze + CONVERTIBLE_FILE_FORMATS = %w(gif jpg jpeg png).freeze include Alchemy::NameConversions include Alchemy::Taggable - include Alchemy::ContentTouching + include Alchemy::TouchElements include Alchemy::Picture::Transformations include Alchemy::Picture::Url has_many :essence_pictures, - class_name: 'Alchemy::EssencePicture', - foreign_key: 'picture_id', + class_name: "Alchemy::EssencePicture", + foreign_key: "picture_id", inverse_of: :ingredient_association has_many :contents, through: :essence_pictures @@ -50,24 +56,37 @@ class Picture < BaseRecord raise PictureInUseError, Alchemy.t(:cannot_delete_picture_notice) % { name: name } end + # Image preprocessing class + def self.preprocessor_class + @_preprocessor_class ||= Preprocessor + end + + # Set a image preprocessing class + # + # # config/initializers/alchemy.rb + # Alchemy::Picture.preprocessor_class = My::ImagePreprocessor + # + def self.preprocessor_class=(klass) + @_preprocessor_class = klass + end + # Enables Dragonfly image processing dragonfly_accessor :image_file, app: :alchemy_pictures do # Preprocess after uploading the picture - after_assign do |p| - resize = Config.get(:preprocess_image_resize) - p.thumb!(resize) if resize.present? + after_assign do |image| + self.class.preprocessor_class.new(image).call end end # We need to define this method here to have it available in the validations below. class << self def allowed_filetypes - Config.get(:uploader).fetch('allowed_filetypes', {}).fetch('alchemy/pictures', []) + Config.get(:uploader).fetch("allowed_filetypes", {}).fetch("alchemy/pictures", []) end end validates_presence_of :image_file - validates_size_of :image_file, maximum: Config.get(:uploader)['file_size_limit'].megabytes + validates_size_of :image_file, maximum: Config.get(:uploader)["file_size_limit"].megabytes validates_property :format, of: :image_file, in: allowed_filetypes, @@ -76,21 +95,10 @@ def allowed_filetypes stampable stamper_class_name: Alchemy.user_class_name - scope :named, ->(name) { - where("#{table_name}.name LIKE ?", "%#{name}%") - } - - scope :recent, -> { - where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) - } - - scope :deletable, -> { - where("#{table_name}.id NOT IN (SELECT picture_id FROM #{EssencePicture.table_name})") - } - - scope :without_tag, -> { - left_outer_joins(:taggings).where(gutentag_taggings: {id: nil}) - } + scope :named, ->(name) { where("#{table_name}.name LIKE ?", "%#{name}%") } + scope :recent, -> { where("#{table_name}.created_at > ?", Time.current - 24.hours).order(:created_at) } + scope :deletable, -> { where("#{table_name}.id NOT IN (SELECT picture_id FROM #{EssencePicture.table_name})") } + scope :without_tag, -> { left_outer_joins(:taggings).where(gutentag_taggings: { id: nil }) } # Class methods @@ -124,11 +132,11 @@ def search_by(params, query, per_page = nil) pictures.order(:name) end - def filtered_by(filter = '') + def filtered_by(filter = "") case filter - when 'recent' then recent - when 'last_upload' then last_upload - when 'without_tag' then without_tag + when "recent" then recent + when "last_upload" then last_upload + when "without_tag" then without_tag else all end @@ -167,7 +175,7 @@ def to_jq_upload { name: image_file_name, size: image_file_size, - error: errors[:image_file].join + error: errors[:image_file].join, } end @@ -177,7 +185,7 @@ def urlname if name.blank? "image_#{id}" else - ::CGI.escape(name.gsub(/\.(gif|png|jpe?g|tiff?)/i, '').tr('.', ' ')) + ::CGI.escape(name.gsub(/\.(gif|png|jpe?g|tiff?)/i, "").tr(".", " ")) end end @@ -215,7 +223,7 @@ def default_render_format # def convertible? Config.get(:image_output_format) && - Config.get(:image_output_format) != 'original' && + Config.get(:image_output_format) != "original" && has_convertible_format? end diff --git a/app/models/alchemy/picture/preprocessor.rb b/app/models/alchemy/picture/preprocessor.rb new file mode 100644 index 0000000000..9798ae125b --- /dev/null +++ b/app/models/alchemy/picture/preprocessor.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Alchemy + class Picture < BaseRecord + class Preprocessor + def initialize(image_file) + @image_file = image_file + end + + # Preprocess images after upload + # + # Define preprocessing options in the Alchemy::Config + # + # preprocess_image_resize [String] - Downsizing example: '1000x1000>' + # + def call + max_image_size = Alchemy::Config.get(:preprocess_image_resize) + image_file.thumb!(max_image_size) if max_image_size.present? + end + + private + + attr_reader :image_file + end + end +end diff --git a/app/models/alchemy/picture/transformations.rb b/app/models/alchemy/picture/transformations.rb index 9382afd970..3ef3819722 100644 --- a/app/models/alchemy/picture/transformations.rb +++ b/app/models/alchemy/picture/transformations.rb @@ -279,7 +279,7 @@ def sizes_from_string(string = "0x0") height = 0 if height.nil? { width: width, - height: height + height: height, } end @@ -309,7 +309,7 @@ def square_format? def image_size { width: image_file_width, - height: image_file_height + height: image_file_height, } end @@ -348,7 +348,7 @@ def point_from_string(string = "0x0") y = 0 if y.nil? { x: x, - y: y + y: y, } end @@ -359,7 +359,7 @@ def point_from_string(string = "0x0") def get_top_left_crop_corner(dimensions) { x: (image_file_width - dimensions[:width]) / 2, - y: (image_file_height - dimensions[:height]) / 2 + y: (image_file_height - dimensions[:height]) / 2, } end @@ -383,7 +383,7 @@ def get_base_dimensions def size_when_fitting(target, dimensions = get_base_dimensions) zoom = [ dimensions[:width].to_f / target[:width], - dimensions[:height].to_f / target[:height] + dimensions[:height].to_f / target[:height], ].max if zoom == 0.0 @@ -406,7 +406,7 @@ def point_and_mask_to_points(point, mask) x1: point[:x], y1: point[:y], x2: point[:x] + mask[:width], - y2: point[:y] + mask[:height] + y2: point[:y] + mask[:height], } end @@ -449,7 +449,7 @@ def xy_crop_resize(dimensions, top_left, crop_dimensions, upsample) def reduce_to_image(dimensions) { width: [dimensions[:width], image_file_width].min, - height: [dimensions[:height], image_file_height].min + height: [dimensions[:height], image_file_height].min, } end end diff --git a/app/models/alchemy/picture/url.rb b/app/models/alchemy/picture/url.rb index 6b2f8a7f13..160e3fe2af 100644 --- a/app/models/alchemy/picture/url.rb +++ b/app/models/alchemy/picture/url.rb @@ -77,8 +77,8 @@ def encoded_image(image, options = {}) end options = { - flatten: target_format != 'gif' && image_file_format == 'gif' - }.merge(options) + flatten: target_format != "gif" && image_file_format == "gif", + }.with_indifferent_access.merge(options) encoding_options = [] @@ -88,13 +88,13 @@ def encoded_image(image, options = {}) end if options[:flatten] - encoding_options << '-flatten' + encoding_options << "-flatten" end convertion_needed = target_format != image_file_format || encoding_options.present? if has_convertible_format? && convertion_needed - image = image.encode(target_format, encoding_options.join(' ')) + image = image.encode(target_format, encoding_options.join(" ")) end image diff --git a/app/models/alchemy/site/layout.rb b/app/models/alchemy/site/layout.rb index a863e834f5..12088dfb8b 100644 --- a/app/models/alchemy/site/layout.rb +++ b/app/models/alchemy/site/layout.rb @@ -3,7 +3,7 @@ module Alchemy module Site::Layout extend ActiveSupport::Concern - SITE_DEFINITIONS_FILE = Rails.root.join('config/alchemy/site_layouts.yml') + SITE_DEFINITIONS_FILE = Rails.root.join("config/alchemy/site_layouts.yml") module ClassMethods # Returns the site layouts definition defined in +site_layouts.yml+ file @@ -28,7 +28,7 @@ def read_site_definitions # Returns site's layout definition # def definition - self.class.definitions.detect { |l| l['name'] == partial_name } + self.class.definitions.detect { |l| l["name"] == partial_name } end # Returns the name for the layout partial diff --git a/app/models/concerns/alchemy/content_touching.rb b/app/models/concerns/alchemy/content_touching.rb deleted file mode 100644 index f7fe71d1be..0000000000 --- a/app/models/concerns/alchemy/content_touching.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Alchemy - module ContentTouching - def self.included(base) - base.after_update(:touch_contents) - end - - private - - # If the model has a +contents+ association, - # it updates all their timestamps. - # - # CAUTION: Only use on bottom to top releations, - # e.g. +Alchemy::Picture+ or +Alchemy::Attachment+ - # not on top to bottom ones like +Alchemy::Element+. - # - def touch_contents - return unless respond_to?(:contents) - - contents.update_all(updated_at: Time.current) - end - end -end diff --git a/app/models/concerns/alchemy/touch_elements.rb b/app/models/concerns/alchemy/touch_elements.rb new file mode 100644 index 0000000000..53d07df594 --- /dev/null +++ b/app/models/concerns/alchemy/touch_elements.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Alchemy + # If the model has a +elements+ association, + # it updates all their timestamps after save. + # + # Should only be used on bottom to top relations, + # e.g. +Alchemy::Picture+ or +Alchemy::Attachment+ + # not on top to bottom ones like +Alchemy::Page+. + # + module TouchElements + def self.included(base) + base.after_save(:touch_elements) + end + + private + + def touch_elements + return unless respond_to?(:elements) + + elements.map(&:touch) + end + end +end diff --git a/app/serializers/alchemy/content_serializer.rb b/app/serializers/alchemy/content_serializer.rb index e9b39494ec..1adc3b6e98 100644 --- a/app/serializers/alchemy/content_serializer.rb +++ b/app/serializers/alchemy/content_serializer.rb @@ -6,9 +6,6 @@ class ContentSerializer < ActiveModel::Serializer :name, :ingredient, :element_id, - :position, - :created_at, - :updated_at, :settings has_one :essence, polymorphic: true diff --git a/app/serializers/alchemy/essence_boolean_serializer.rb b/app/serializers/alchemy/essence_boolean_serializer.rb index d1cc8d2571..6b790875df 100644 --- a/app/serializers/alchemy/essence_boolean_serializer.rb +++ b/app/serializers/alchemy/essence_boolean_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceBooleanSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :value, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_date_serializer.rb b/app/serializers/alchemy/essence_date_serializer.rb index 889fa6e322..b738f6a4e7 100644 --- a/app/serializers/alchemy/essence_date_serializer.rb +++ b/app/serializers/alchemy/essence_date_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceDateSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :date, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_file_serializer.rb b/app/serializers/alchemy/essence_file_serializer.rb index 9afc98fc3f..7ccb46ea7a 100644 --- a/app/serializers/alchemy/essence_file_serializer.rb +++ b/app/serializers/alchemy/essence_file_serializer.rb @@ -2,9 +2,11 @@ module Alchemy class EssenceFileSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :title, - :css_class + :css_class, + ) has_one :attachment end diff --git a/app/serializers/alchemy/essence_html_serializer.rb b/app/serializers/alchemy/essence_html_serializer.rb index afaafafc88..8f76946360 100644 --- a/app/serializers/alchemy/essence_html_serializer.rb +++ b/app/serializers/alchemy/essence_html_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceHtmlSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :source, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_link_serializer.rb b/app/serializers/alchemy/essence_link_serializer.rb index b665693da1..29dcec97dd 100644 --- a/app/serializers/alchemy/essence_link_serializer.rb +++ b/app/serializers/alchemy/essence_link_serializer.rb @@ -2,12 +2,12 @@ module Alchemy class EssenceLinkSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :link, :link_title, :link_target, :link_class_name, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_picture_serializer.rb b/app/serializers/alchemy/essence_picture_serializer.rb index 3082b78b39..5ae7a34c22 100644 --- a/app/serializers/alchemy/essence_picture_serializer.rb +++ b/app/serializers/alchemy/essence_picture_serializer.rb @@ -2,15 +2,15 @@ module Alchemy class EssencePictureSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :picture_id, :caption, :title, :alt_tag, :css_class, :link, - :created_at, - :updated_at + ) has_one :picture @@ -21,7 +21,7 @@ def link url: object.link, css_class: object.link_class_name, title: object.link_title, - target: object.link_target + target: object.link_target, } end end diff --git a/app/serializers/alchemy/essence_richtext_serializer.rb b/app/serializers/alchemy/essence_richtext_serializer.rb index b519ce7c65..bd8291748f 100644 --- a/app/serializers/alchemy/essence_richtext_serializer.rb +++ b/app/serializers/alchemy/essence_richtext_serializer.rb @@ -2,10 +2,10 @@ module Alchemy class EssenceRichtextSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :body, :stripped_body, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_select_serializer.rb b/app/serializers/alchemy/essence_select_serializer.rb index 86483bfa6b..51d9a1a731 100644 --- a/app/serializers/alchemy/essence_select_serializer.rb +++ b/app/serializers/alchemy/essence_select_serializer.rb @@ -2,9 +2,9 @@ module Alchemy class EssenceSelectSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :value, - :created_at, - :updated_at + ) end end diff --git a/app/serializers/alchemy/essence_text_serializer.rb b/app/serializers/alchemy/essence_text_serializer.rb index 3f1d9c6ec6..dd2326518c 100644 --- a/app/serializers/alchemy/essence_text_serializer.rb +++ b/app/serializers/alchemy/essence_text_serializer.rb @@ -2,11 +2,11 @@ module Alchemy class EssenceTextSerializer < ActiveModel::Serializer - attributes :id, + attributes( + :id, :body, :link, - :created_at, - :updated_at + ) def link return if object.link.blank? @@ -15,7 +15,7 @@ def link url: object.link, title: object.link_title, css_class: object.link_class_name, - target: object.link_target + target: object.link_target, } end end diff --git a/app/serializers/alchemy/legacy_element_serializer.rb b/app/serializers/alchemy/legacy_element_serializer.rb deleted file mode 100644 index e3e4e119e2..0000000000 --- a/app/serializers/alchemy/legacy_element_serializer.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Alchemy - class LegacyElementSerializer < ActiveModel::Serializer - attributes :id, - :name, - :position, - :page_id, - :tag_list, - :created_at, - :updated_at - - has_many :contents - end -end diff --git a/app/serializers/alchemy/node_serializer.rb b/app/serializers/alchemy/node_serializer.rb index a260fba3b9..95fc2f8bd1 100644 --- a/app/serializers/alchemy/node_serializer.rb +++ b/app/serializers/alchemy/node_serializer.rb @@ -8,5 +8,7 @@ class NodeSerializer < ActiveModel::Serializer :rgt, :url, :parent_id + + has_many :ancestors, record_type: :node, serializer: self end end diff --git a/app/serializers/alchemy/page_serializer.rb b/app/serializers/alchemy/page_serializer.rb index 776cda862e..1fb53f3b41 100644 --- a/app/serializers/alchemy/page_serializer.rb +++ b/app/serializers/alchemy/page_serializer.rb @@ -13,7 +13,8 @@ class PageSerializer < ActiveModel::Serializer :tag_list, :created_at, :updated_at, - :status + :status, + :url_path has_many :elements end diff --git a/app/serializers/alchemy/page_tree_serializer.rb b/app/serializers/alchemy/page_tree_serializer.rb index f6a36b6d29..a59b6d810c 100644 --- a/app/serializers/alchemy/page_tree_serializer.rb +++ b/app/serializers/alchemy/page_tree_serializer.rb @@ -3,7 +3,7 @@ module Alchemy class PageTreeSerializer < BaseSerializer def attributes - {'pages' => nil} + {"pages" => nil} end def pages @@ -53,15 +53,15 @@ def page_hash(page, has_children, level, folded) id: page.id, name: page.name, public: page.public?, - visible: page.visible?, restricted: page.restricted?, page_layout: page.page_layout, slug: page.slug, urlname: page.urlname, + url_path: page.url_path, level: level, - root: level == 1, - root_or_leaf: level == 1 || !has_children, - children: [] + root: page.depth == 1, + root_or_leaf: page.depth == 1 || !has_children, + children: [], } if opts[:elements] @@ -73,9 +73,9 @@ def page_hash(page, has_children, level, folded) definition_missing: page.definition.blank?, folded: folded, locked: page.locked?, - locked_notice: page.locked? ? Alchemy.t('This page is locked', name: page.locker_name) : nil, + locked_notice: page.locked? ? Alchemy.t("This page is locked", name: page.locker_name) : nil, permissions: page_permissions(page, opts[:ability]), - status_titles: page_status_titles(page) + status_titles: page_status_titles(page), }) else p_hash @@ -83,10 +83,10 @@ def page_hash(page, has_children, level, folded) end def page_elements(page) - if opts[:elements] == 'true' + if opts[:elements] == "true" page.elements else - page.elements.named(opts[:elements].split(',') || []) + page.elements.named(opts[:elements].split(",") || []) end end @@ -97,15 +97,14 @@ def page_permissions(page, ability) copy: ability.can?(:copy, page), destroy: ability.can?(:destroy, page), create: ability.can?(:create, Alchemy::Page), - edit_content: ability.can?(:edit_content, page) + edit_content: ability.can?(:edit_content, page), } end def page_status_titles(page) { public: page.status_title(:public), - visible: page.status_title(:visible), - restricted: page.status_title(:restricted) + restricted: page.status_title(:restricted), } end end diff --git a/app/views/alchemy/admin/layoutpages/index.html.erb b/app/views/alchemy/admin/layoutpages/index.html.erb index 43e7e1e0aa..d7dbe6dcea 100644 --- a/app/views/alchemy/admin/layoutpages/index.html.erb +++ b/app/views/alchemy/admin/layoutpages/index.html.erb @@ -4,7 +4,7 @@ <%= render 'alchemy/admin/partials/language_tree_select' %> <%= toolbar_button( icon: :plus, - url: alchemy.new_admin_page_path(parent_id: @layout_root.id, layoutpage: true), + url: alchemy.new_admin_page_path(language: @current_language, layoutpage: true), hotkey: 'alt+n', dialog_options: { title: Alchemy.t('Add global page'), @@ -30,6 +30,10 @@ <% end %> +

    + <%= Alchemy::Page.human_attribute_name(:name) %> +

    +
      - <%= render partial: "layoutpage", collection: @layout_root.children %> + <%= render partial: "layoutpage", collection: @layout_pages %>
    diff --git a/app/views/alchemy/admin/nodes/_form.html.erb b/app/views/alchemy/admin/nodes/_form.html.erb index dca2a49c03..e8c1602b28 100644 --- a/app/views/alchemy/admin/nodes/_form.html.erb +++ b/app/views/alchemy/admin/nodes/_form.html.erb @@ -1,23 +1,26 @@ <%= alchemy_form_for([:admin, node]) do |f| %> - <% if node.root? %> - <%= f.input :name, - collection: Alchemy::Node.available_menu_names.map { |n| [I18n.t(n, scope: [:alchemy, :menu_names]), n] }, + <% if node.new_record? && node.root? %> + <%= f.input :menu_type, + collection: Alchemy::Language.current.available_menu_names.map { |n| [I18n.t(n, scope: [:alchemy, :menu_names]), n] }, include_blank: false, input_html: { class: 'alchemy_selectbox' } %> <% else %> - <%= f.input :name, input_html: { - autofocus: true, - value: node.page && node.read_attribute(:name).blank? ? nil : node.name, - placeholder: node.page ? node.page.name : nil - } %> - <%= f.input :page_id, label: Alchemy::Page.model_name.human, input_html: { class: 'alchemy_selectbox' } %> - <%= f.input :url, input_html: { disabled: node.page }, hint: Alchemy.t(:node_url_hint) %> - <%= f.input :title %> - <%= f.input :nofollow %> - <%= f.input :external %> - <%= f.hidden_field :parent_id %> + <% if node.root? %> + <%= f.input :name %> + <% else %> + <%= f.input :name, input_html: { + autofocus: true, + value: node.page && node.read_attribute(:name).blank? ? nil : node.name, + placeholder: node.page ? node.page.name : nil + } %> + <%= f.input :page_id, label: Alchemy::Page.model_name.human, input_html: { class: 'alchemy_selectbox' } %> + <%= f.input :url, input_html: { disabled: node.page }, hint: Alchemy.t(:node_url_hint) %> + <%= f.input :title %> + <%= f.input :nofollow %> + <%= f.input :external %> + <%= f.hidden_field :parent_id %> + <% end %> <% end %> - <%= f.hidden_field :site_id %> <%= f.hidden_field :language_id %> <%= f.submit button_label %> <% end %> @@ -30,7 +33,7 @@ initialSelection: { id: <%= node.page_id %>, text: "<%= node.page.name %>", - url: "/<%= node.page.urlname %>" + url_path: "<%= node.page.url_path %>" } <% end %> }).on('change', function(e) { @@ -39,7 +42,7 @@ $('#node_url').val('').prop('disabled', false) } else { $('#node_name').attr('placeholder', e.added.name) - $('#node_url').val('/' + e.added.urlname).prop('disabled', true) + $('#node_url').val(e.added.url_path).prop('disabled', true) } }) diff --git a/app/views/alchemy/admin/nodes/_node.html.erb b/app/views/alchemy/admin/nodes/_node.html.erb index 1615497ed0..143473bc0b 100644 --- a/app/views/alchemy/admin/nodes/_node.html.erb +++ b/app/views/alchemy/admin/nodes/_node.html.erb @@ -47,11 +47,7 @@ <% end %>
    - <% if node.root? %> - <%= I18n.t(node.name, scope: [:alchemy, :menu_names]) %> - <% else %> - <%= node.name || ' '.html_safe %> - <% end %> + <%= node.name || ' '.html_safe %> <% if node.page %> diff --git a/app/views/alchemy/admin/nodes/index.html.erb b/app/views/alchemy/admin/nodes/index.html.erb index 38e6a701c1..41259a463b 100644 --- a/app/views/alchemy/admin/nodes/index.html.erb +++ b/app/views/alchemy/admin/nodes/index.html.erb @@ -43,6 +43,5 @@
    diff --git a/app/views/alchemy/admin/pages/_create_language_form.html.erb b/app/views/alchemy/admin/pages/_create_language_form.html.erb index 5e2ccc088c..482099da00 100644 --- a/app/views/alchemy/admin/pages/_create_language_form.html.erb +++ b/app/views/alchemy/admin/pages/_create_language_form.html.erb @@ -1,4 +1,3 @@ -<% if root = Alchemy::Page.rootpage %>
    <%= render_message do %>

    <%= Alchemy.t(:language_does_not_exist) %>

    @@ -37,7 +36,6 @@ <%= form.hidden_field :language_id, value: @language.id %> <%= form.hidden_field :language_code, value: @language.code %> <%= form.hidden_field :language_root, value: true %> - <%= form.hidden_field :parent_id, value: root.id %> <%= form.hidden_field :public, value: Alchemy::Language.all.size == 1 %> <%= form.submit Alchemy.t("create_tree_as_new_language", language: @language.name), autofocus: true %> <% end %> @@ -50,9 +48,3 @@ <%- end -%>
    -<% else %> -<%= render_message :error do %> -

    Root page not found.

    -

    Please run bin/rake db:seed task.

    -<% end %> -<% end %> diff --git a/app/views/alchemy/admin/pages/_form.html.erb b/app/views/alchemy/admin/pages/_form.html.erb index a0b0a66e5e..2112d973ed 100644 --- a/app/views/alchemy/admin/pages/_form.html.erb +++ b/app/views/alchemy/admin/pages/_form.html.erb @@ -10,7 +10,6 @@
    <%= render 'alchemy/admin/pages/publication_fields' %> <%= page_status_checkbox(@page, :restricted) %> - <%= render 'alchemy/admin/pages/menu_fields', f: f %> <% if configuration(:sitemap)['show_flag'] %> <%= page_status_checkbox(@page, :sitemap) %> <% end %> @@ -18,7 +17,7 @@
    <%= f.input :name, autofocus: true %> - <%= f.input :urlname, as: 'string', input_html: {value: @page.slug} %> + <%= f.input :urlname, as: 'string', input_html: {value: @page.slug}, label: Alchemy::Page.human_attribute_name(:slug) %> <%= f.input :title, input_html: {'data-alchemy-char-counter' => 60} %> diff --git a/app/views/alchemy/admin/pages/_menu_fields.html.erb b/app/views/alchemy/admin/pages/_menu_fields.html.erb deleted file mode 100644 index dadb0f2641..0000000000 --- a/app/views/alchemy/admin/pages/_menu_fields.html.erb +++ /dev/null @@ -1,33 +0,0 @@ -<% if @page.menus.any? %> - - <% @page.menus.each do |menu| %> - - <%= menu.name %> - - <% end %> -<% elsif Alchemy::Node.roots.any? %> - <%= page_status_checkbox(@page, :visible) %> - <%= f.input :menu_id, collection: Alchemy::Node.roots.map { |n| [n.name, n.id] }, - prompt: Alchemy.t('Please choose a menu'), - input_html: { class: 'alchemy_selectbox' }, - wrapper_html: { style: @page.visible? ? 'display: block' : 'display: none' }, - label: false %> - -<% else %> - <%= page_status_checkbox(@page, :visible) %> -<% end %> diff --git a/app/views/alchemy/admin/pages/_new_page_form.html.erb b/app/views/alchemy/admin/pages/_new_page_form.html.erb index b44f5ed276..f717ae233e 100644 --- a/app/views/alchemy/admin/pages/_new_page_form.html.erb +++ b/app/views/alchemy/admin/pages/_new_page_form.html.erb @@ -1,5 +1,6 @@ <%= alchemy_form_for([:admin, @page]) do |f| %> <%= f.hidden_field(:parent_id) %> + <%= f.hidden_field(:language_id) %> <%= f.hidden_field(:layoutpage) %> <%= f.input :page_layout, collection: @page_layouts, diff --git a/app/views/alchemy/admin/pages/_page.html.erb b/app/views/alchemy/admin/pages/_page.html.erb index 9ddda23f77..c43cbbb4d2 100644 --- a/app/views/alchemy/admin/pages/_page.html.erb +++ b/app/views/alchemy/admin/pages/_page.html.erb @@ -1,4 +1,4 @@ -
  • +
  • <% unless @sorting %> @@ -150,15 +150,14 @@ {{status_titles.public}} - - - {{status_titles.visible}} - {{status_titles.restricted}}
    +
    + {{ url_path }} +
    {{#if permissions.edit_content}} <%= link_to_unless( diff --git a/app/views/alchemy/admin/pages/_page_infos.html.erb b/app/views/alchemy/admin/pages/_page_infos.html.erb index 4482354206..81a5318b61 100644 --- a/app/views/alchemy/admin/pages/_page_infos.html.erb +++ b/app/views/alchemy/admin/pages/_page_infos.html.erb @@ -2,10 +2,6 @@ <%= render_icon(:compass, transform: 'shrink-2', class: @page.public? ? nil : 'disabled') %> <%= page.status_title(:public) %> - - <%= render_icon(:eye, transform: 'shrink-2', class: @page.visible? ? nil : 'disabled') %> - <%= page.status_title(:visible) %> - <%= render_icon(:lock, transform: 'shrink-2', class: @page.restricted? ? nil : 'disabled') %> <%= page.status_title(:restricted) %> diff --git a/app/views/alchemy/admin/pages/_sitemap.html.erb b/app/views/alchemy/admin/pages/_sitemap.html.erb index 7f03406879..8df5cdf2d9 100644 --- a/app/views/alchemy/admin/pages/_sitemap.html.erb +++ b/app/views/alchemy/admin/pages/_sitemap.html.erb @@ -1,4 +1,10 @@
    +

    + <%= Alchemy::Page.human_attribute_name(:name) %> + <%= Alchemy::Page.human_attribute_name(:urlname) %> + <%= Alchemy.t(:page_status) %> +

    +

    diff --git a/app/views/alchemy/admin/pages/edit.html.erb b/app/views/alchemy/admin/pages/edit.html.erb index d887e5c550..d9801d114a 100644 --- a/app/views/alchemy/admin/pages/edit.html.erb +++ b/app/views/alchemy/admin/pages/edit.html.erb @@ -190,7 +190,7 @@ } }); - Alchemy.PreviewWindow.init('<%= admin_page_path(@page) %>'); + Alchemy.PreviewWindow.init('<%= @preview_url %>'); $('#preview_size').bind('open.selectBoxIt', function (e) { $('#top_menu').css('z-index', 5000); diff --git a/app/views/alchemy/admin/pages/info.html.erb b/app/views/alchemy/admin/pages/info.html.erb index e3a8f5ee6c..b665bb1545 100644 --- a/app/views/alchemy/admin/pages/info.html.erb +++ b/app/views/alchemy/admin/pages/info.html.erb @@ -14,7 +14,7 @@

    <%= @page.layout_display_name %>

    - +

    <%= "/#{@page.urlname}" %>

    @@ -24,10 +24,6 @@ <%= render_icon(:compass, transform: 'shrink-2', class: @page.public? ? nil : 'disabled') %> <%= @page.status_title(:public) %> - - <%= render_icon(:eye, transform: 'shrink-2', class: @page.visible? ? nil : 'disabled') %> - <%= @page.status_title(:visible) %> - <%= render_icon(:lock, transform: 'shrink-2', class: @page.restricted? ? nil : 'disabled') %> <%= @page.status_title(:restricted) %> diff --git a/app/views/alchemy/admin/pages/unlock.js.erb b/app/views/alchemy/admin/pages/unlock.js.erb index 52f4f5fbcf..9c7761b3e0 100644 --- a/app/views/alchemy/admin/pages/unlock.js.erb +++ b/app/views/alchemy/admin/pages/unlock.js.erb @@ -1,6 +1,13 @@ -(function($) { - $('#locked_page_<%= @page.id -%>').remove(); - $('#page_<%= @page.id -%> .sitemap_left_images .with-hint').remove(); - $('#page_<%= @page.id -%> .sitemap_left_images').append('<%= j render_icon(:file, style: 'regular', size: 'lg') %>'); - Alchemy.growl('<%= flash[:notice] -%>'); -})(jQuery); +(function() { + var locked_page_tab = document.querySelector('#locked_page_<%= @page.id -%>') + var locked_page_icon = document.querySelector( + '#page_<%= @page.id -%> > .sitemap_page > .sitemap_left_images .with-hint' + ) + if (locked_page_tab) { + locked_page_tab.remove() + } + if (locked_page_icon) { + locked_page_icon.innerHTML = '<%= j render_icon(:file, style: 'regular', size: 'lg') %>' + } + Alchemy.growl('<%= flash[:notice] -%>') +})() diff --git a/app/views/alchemy/admin/pictures/index.html.erb b/app/views/alchemy/admin/pictures/index.html.erb index 572a171c3a..090d117a05 100644 --- a/app/views/alchemy/admin/pictures/index.html.erb +++ b/app/views/alchemy/admin/pictures/index.html.erb @@ -14,7 +14,12 @@
    <%= link_to( render_icon('search-minus'), - alchemy.admin_pictures_path(size: "small", q: search_filter_params[:q]), + alchemy.admin_pictures_path( + size: "small", + q: search_filter_params[:q], + filter: search_filter_params[:filter], + tagged_with: search_filter_params[:tagged_with] + ), title: Alchemy.t(:small_thumbnails), class: "icon_button" ) %> @@ -22,7 +27,12 @@
    <%= link_to( render_icon('search'), - alchemy.admin_pictures_path(size: "medium", q: search_filter_params[:q]), + alchemy.admin_pictures_path( + size: "medium", + q: search_filter_params[:q], + filter: search_filter_params[:filter], + tagged_with: search_filter_params[:tagged_with] + ), title: Alchemy.t(:medium_thumbnails), class: "icon_button" ) %> @@ -30,7 +40,12 @@
    <%= link_to( render_icon('search-plus'), - alchemy.admin_pictures_path(size: "large", q: search_filter_params[:q]), + alchemy.admin_pictures_path( + size: "large", + q: search_filter_params[:q], + filter: search_filter_params[:filter], + tagged_with: search_filter_params[:tagged_with] + ), title: Alchemy.t(:big_thumbnails), class: "icon_button" ) %> diff --git a/app/views/alchemy/essences/_essence_node_editor.html.erb b/app/views/alchemy/essences/_essence_node_editor.html.erb new file mode 100644 index 0000000000..13d5112583 --- /dev/null +++ b/app/views/alchemy/essences/_essence_node_editor.html.erb @@ -0,0 +1,27 @@ +<%= content_tag :div, + id: essence_node_editor.dom_id, + class: essence_node_editor.css_classes, + data: essence_node_editor.data_attributes do %> + <%= content_label(essence_node_editor) %> + <%= text_field_tag( + essence_node_editor.form_field_name("node_id"), + essence_node_editor.essence.node_id, + id: essence_node_editor.form_field_id, + class: 'alchemy_selectbox full_width' + ) %> +<% end %> + + diff --git a/app/views/alchemy/essences/_essence_node_view.html.erb b/app/views/alchemy/essences/_essence_node_view.html.erb new file mode 100644 index 0000000000..f1b1ca8ccf --- /dev/null +++ b/app/views/alchemy/essences/_essence_node_view.html.erb @@ -0,0 +1 @@ +<%= render content.ingredient if content.ingredient %> diff --git a/app/views/alchemy/essences/_essence_page_editor.html.erb b/app/views/alchemy/essences/_essence_page_editor.html.erb index 25fcde586b..4dbf0727c6 100644 --- a/app/views/alchemy/essences/_essence_page_editor.html.erb +++ b/app/views/alchemy/essences/_essence_page_editor.html.erb @@ -4,7 +4,7 @@ data: essence_page_editor.data_attributes do %> <%= content_label(essence_page_editor) %> <%= text_field_tag( - essence_page_editor.form_field_name, + essence_page_editor.form_field_name("page_id"), essence_page_editor.essence.page_id, id: essence_page_editor.form_field_id, class: 'alchemy_selectbox full_width' diff --git a/app/views/alchemy/pages/show.rss.builder b/app/views/alchemy/pages/show.rss.builder index 19377b16b8..608239ed2e 100644 --- a/app/views/alchemy/pages/show.rss.builder +++ b/app/views/alchemy/pages/show.rss.builder @@ -10,8 +10,8 @@ xml.rss version: "2.0" do xml.item do xml.title element.content_for_rss_title.try(:ingredient) xml.description element.content_for_rss_description.try(:ingredient) - if element.has_ingredient?('date') - xml.pubDate element.ingredient('date').to_s(:rfc822) + if element.has_ingredient?("date") + xml.pubDate element.ingredient("date").to_s(:rfc822) end xml.link show_alchemy_page_url(@page, anchor: element_dom_id(element)) xml.guid show_alchemy_page_url(@page, anchor: element_dom_id(element)) diff --git a/app/views/layouts/alchemy/admin.html.erb b/app/views/layouts/alchemy/admin.html.erb index d8ac6b27f1..79bf6394e0 100644 --- a/app/views/layouts/alchemy/admin.html.erb +++ b/app/views/layouts/alchemy/admin.html.erb @@ -36,6 +36,7 @@ <%= render 'alchemy/admin/partials/routes' %> <%= javascript_include_tag('alchemy/admin/all', 'data-turbolinks-track' => true) %> + <%= javascript_pack_tag('alchemy/admin') %> <%= yield :javascript_includes %> <%= content_tag :body, id: 'alchemy', class: alchemy_body_class do %> diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000000..ec5616cd71 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,12 @@ +module.exports = { + presets: [ + [ + "@babel/preset-env", + { + targets: { + node: "current" + } + } + ] + ] +} diff --git a/bin/rails b/bin/rails index 5d8a41f30e..7ad4e14e01 100755 --- a/bin/rails +++ b/bin/rails @@ -2,8 +2,8 @@ # frozen_string_literal: true # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. -ENGINE_ROOT = File.expand_path('..', __dir__) -ENGINE_PATH = File.expand_path('../lib/alchemy/engine', __dir__) +ENGINE_ROOT = File.expand_path("..", __dir__) +ENGINE_PATH = File.expand_path("../lib/alchemy/engine", __dir__) -require 'rails/all' -require 'rails/engine/commands' +require "rails/all" +require "rails/engine/commands" diff --git a/config/alchemy/config.yml b/config/alchemy/config.yml index 9e5c07c899..5c063790bf 100644 --- a/config/alchemy/config.yml +++ b/config/alchemy/config.yml @@ -1,12 +1,6 @@ # == This is the global Alchemy configuration file # -# === Require SSL for login form and all admin modules -# -# NOTE: You have to create a SSL certificate on your server to make this work -# -require_ssl: false - # === Auto Log Out Time # # The amount of time of inactivity in minutes after which the user is kicked out of his current session. @@ -17,7 +11,7 @@ auto_logout_time: 30 # === Redirect Options # -# redirect_to_public_child [Boolean] # Alchemy redirects to the first public child page found, if a page is not visible. +# redirect_to_public_child [Boolean] # Alchemy redirects to the first public child page found, if a page is not public. # redirect_to_public_child: true @@ -44,23 +38,33 @@ sitemap: show_root: true show_flag: false -# === URL nesting +# === Default items per page in admin views +# +# In Alchemy's Admin, change how many items you would get shown per page by Kaminari +items_per_page: 15 + +# === Preview window URL configuration # -# Since Alchemy 2.6.0, page urls are nested, respectively to their tree position. +# By default Alchemy uses its internal page preview renderer, +# but you can configure it to be any URL instead. # -# Disable +url_nesting+ to get slug only urls. +# Basic Auth is supported. # -# NOTE: After changing the url_nesting, you should run one of these convert rake tasks: +# preview: +# host: https://www.my-static-site.com +# auth: +# username: <%= ENV["BASIC_AUTH_USERNAME"] %> +# password: <%= ENV["BASIC_AUTH_PASSWORD"] %> # -# rake alchemy:convert:urlnames:to_nested -# rake alchemy:convert:urlnames:to_slug +# Preview config per site is supported as well. # -url_nesting: true - -# === Default items per page in admin views +# preview: +# My site name: +# host: https://www.my-static-site.com +# auth: +# username: <%= ENV["BASIC_AUTH_USERNAME"] %> +# password: <%= ENV["BASIC_AUTH_PASSWORD"] %> # -# In Alchemy's Admin, change how many items you would get shown per page by Kaminari -items_per_page: 15 # === Picture rendering settings # diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 150ad0fdc4..4a75c7e252 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true # Add Alchemy assets for precompiling -Rails.application.config.assets.precompile << 'alchemy_manifest.js' +Rails.application.config.assets.precompile << "alchemy_manifest.js" diff --git a/config/initializers/dragonfly.rb b/config/initializers/dragonfly.rb index 8ed78220d8..786b23a6b5 100644 --- a/config/initializers/dragonfly.rb +++ b/config/initializers/dragonfly.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'dragonfly_svg' +require "dragonfly_svg" # Logger Dragonfly.logger = Rails.logger diff --git a/config/initializers/mini_profiler.rb b/config/initializers/mini_profiler.rb index af008dc7c7..562b940e05 100644 --- a/config/initializers/mini_profiler.rb +++ b/config/initializers/mini_profiler.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true begin - require 'rack-mini-profiler' - Rack::MiniProfiler.config.position = 'right' + require "rack-mini-profiler" + Rack::MiniProfiler.config.position = "right" Rack::MiniProfiler.config.start_hidden = true rescue LoadError end diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 9e632b7e09..1da4f1892c 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -77,7 +77,7 @@ config.boolean_style = :inline # Default class for buttons - config.button_class = 'button' + config.button_class = "button" # Method used to tidy up errors. Specify any Rails Array method. # :first lists the first message for each field. @@ -88,7 +88,7 @@ config.error_notification_tag = :div # CSS class to add for error notification helper. - config.error_notification_class = 'field_with_errors' + config.error_notification_class = "field_with_errors" # ID to add for error notification helper. # config.error_notification_id = nil @@ -103,7 +103,7 @@ config.collection_wrapper_tag = :div # You can define the class to use on all collection wrappers. Defaulting to none. - config.collection_wrapper_class = 'control_group' + config.collection_wrapper_class = "control_group" # You can wrap each item in a collection of radio/check boxes with a tag, # defaulting to :span. @@ -116,7 +116,7 @@ config.label_text = proc { |label, required| "#{label}#{required}" } # You can define the class to use on all labels. Default is nil. - config.label_class = 'control-label' + config.label_class = "control-label" # You can define the default class to be used on forms. Can be overriden # with `html: { :class }`. Defaulting to none. @@ -170,12 +170,12 @@ # config.input_class = nil # Define the default class of the input wrapper of the boolean input. - config.boolean_label_class = 'checkbox' + config.boolean_label_class = "checkbox" # Defines if the default input wrapper class should be included in radio # collection wrappers. # config.include_default_input_wrapper_class = true # Defines which i18n scope will be used in Simple Form. - config.i18n_scope = 'alchemy.forms' + config.i18n_scope = "alchemy.forms" end diff --git a/config/locales/alchemy.en.yml b/config/locales/alchemy.en.yml index 51ea9c2659..cacd677308 100644 --- a/config/locales/alchemy.en.yml +++ b/config/locales/alchemy.en.yml @@ -488,9 +488,6 @@ en: page_published: "Published page" page_restricted: "restricted" page_states: - visible: - "true": "Page is visible in navigation." - "false": "Page is not visible in navigation." public: "true": "Page is published." "false": "Page is unpublished." @@ -587,8 +584,6 @@ en: button_label: Upload image(s) upload_success: "Picture %{name} uploaded successfully" upload_failure: "Error while uploading %{name}: %{error}" - url_name: "URL-Name" - visible: "visible" want_to_create_new_language: "Do you want to create a new empty language tree?" want_to_make_copy_of_existing_language: "Do you want to copy an existing language tree?" "We need at least one default.": "A default language must exist." @@ -684,9 +679,9 @@ en: page_layout: blank: "^Please choose a page layout." urlname: - too_short: "^The pages urlname is too short (minimum of 3 characters)." - taken: "^URL-Name already taken." - exclusion: "^URL-Name reserved." + too_short: "^URL-Path is too short (minimum of 3 characters)." + taken: "^URL-Path already taken." + exclusion: "^URL-Path reserved." alchemy/picture: attributes: image_file: @@ -720,6 +715,10 @@ en: activerecord: errors: models: + alchemy/node: + attributes: + base: + essence_nodes_present: "This menu item is in use inside an Alchemy element on the following pages: %{page_names}." alchemy/site: attributes: languages: @@ -805,8 +804,9 @@ en: locale: Localization code: ISO Code alchemy/legacy_page_url: - urlname: "URL path" + urlname: "URL-Path" alchemy/node: + menu_type: Menu Type name: "Name" title: "Title" nofollow: "Search engine must not follow" @@ -830,8 +830,8 @@ en: tag_list: Tags title: "Title" updated_at: "Updated at" - urlname: "Urlname" - visible: "visible in navigation" + urlname: "URL-Path" + slug: "Slug" alchemy/picture: image_file_name: "Filename" image_file_height: "Height" diff --git a/config/routes.rb b/config/routes.rb index 1cf0de3483..30aad4e77a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -require 'alchemy/routing_constraints' +require "alchemy/routing_constraints" Alchemy::Engine.routes.draw do - root to: 'pages#index' + root to: "pages#index" - get '/sitemap.xml' => 'pages#sitemap', format: 'xml' + get "/sitemap.xml" => "pages#sitemap", format: "xml" scope Alchemy.admin_path, {constraints: Alchemy.admin_constraints} do - get '/' => redirect("#{Alchemy.admin_path}/dashboard"), as: :admin - get '/dashboard' => 'admin/dashboard#index', as: :admin_dashboard - get '/dashboard/info' => 'admin/dashboard#info', as: :dashboard_info - get '/help' => 'admin/dashboard#help', as: :help - get '/dashboard/update_check' => 'admin/dashboard#update_check', as: :update_check - get '/leave' => 'admin/base#leave', as: :leave_admin + get "/" => redirect("#{Alchemy.admin_path}/dashboard"), as: :admin + get "/dashboard" => "admin/dashboard#index", as: :admin_dashboard + get "/dashboard/info" => "admin/dashboard#info", as: :dashboard_info + get "/help" => "admin/dashboard#help", as: :help + get "/dashboard/update_check" => "admin/dashboard#update_check", as: :update_check + get "/leave" => "admin/base#leave", as: :leave_admin end namespace :admin, {path: Alchemy.admin_path, constraints: Alchemy.admin_constraints} do @@ -95,7 +95,7 @@ end end - resource :clipboard, only: :index, controller: 'clipboard' do + resource :clipboard, only: :index, controller: "clipboard" do collection do get :index delete :clear @@ -104,7 +104,7 @@ end end - resource :trash, only: :index, controller: 'trash' do + resource :trash, only: :index, controller: "trash" do collection do get :index delete :clear @@ -119,38 +119,38 @@ resources :sites - get '/styleguide' => 'styleguide#index' + get "/styleguide" => "styleguide#index" end - get '/attachment/:id/download(/:name)' => 'attachments#download', + get "/attachment/:id/download(/:name)" => "attachments#download", as: :download_attachment - get '/attachment/:id/show' => 'attachments#show', + get "/attachment/:id/show" => "attachments#show", as: :show_attachment resources :messages, only: [:index, :new, :create] resources :elements, only: :show resources :contents, only: :show - namespace :api, defaults: {format: 'json'} do + namespace :api, defaults: {format: "json"} do resources :contents, only: [:index, :show] resources :elements, only: [:index, :show] do - get '/contents' => 'contents#index', as: 'contents' - get '/contents/:name' => 'contents#show', as: 'content' + get "/contents" => "contents#index", as: "contents" + get "/contents/:name" => "contents#show", as: "content" end resources :pages, only: [:index] do - get 'elements' => 'elements#index', as: 'elements' - get 'elements/:named' => 'elements#index', as: 'named_elements' + get "elements" => "elements#index", as: "elements" + get "elements/:named" => "elements#index", as: "named_elements" collection do get :nested end end - get '/pages/*urlname(.:format)' => 'pages#show', as: 'page' - get '/admin/pages/:id(.:format)' => 'pages#show', as: 'preview_page' + get "/pages/*urlname(.:format)" => "pages#show", as: "page" + get "/admin/pages/:id(.:format)" => "pages#show", as: "preview_page" - resources :nodes, only: [] do + resources :nodes, only: [:index] do member do patch :move patch :toggle_folded @@ -158,13 +158,13 @@ end end - get '/:locale' => 'pages#index', + get "/:locale" => "pages#index", constraints: {locale: Alchemy::RoutingConstraints::LOCALE_REGEXP}, as: :show_language_root # The page show action has to be last route constraints(locale: Alchemy::RoutingConstraints::LOCALE_REGEXP) do - get '(/:locale)/*urlname(.:format)' => 'pages#show', + get "(/:locale)/*urlname(.:format)" => "pages#show", constraints: Alchemy::RoutingConstraints.new, as: :show_page end diff --git a/config/spring.rb b/config/spring.rb index 9ee5253fd9..a28a83bf50 100755 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -Spring.application_root = './spec/dummy' -Spring.watch 'lib/**/*' +Spring.application_root = "./spec/dummy" +Spring.watch "lib/**/*" diff --git a/db/migrate/20200226213334_alchemy_four_point_four.rb b/db/migrate/20200226213334_alchemy_four_point_four.rb index 3de012be7a..2dcb61665d 100644 --- a/db/migrate/20200226213334_alchemy_four_point_four.rb +++ b/db/migrate/20200226213334_alchemy_four_point_four.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AlchemyFourPointFour < ActiveRecord::Migration[5.0] +class AlchemyFourPointFour < ActiveRecord::Migration[5.2] def up unless table_exists?("alchemy_attachments") create_table "alchemy_attachments", force: :cascade do |t| @@ -8,10 +8,9 @@ def up t.string "file_name" t.string "file_mime_type" t.integer "file_size" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.references "creator" + t.references "updater" + t.timestamps null: false t.string "file_uid" t.index ["file_uid"], name: "index_alchemy_attachments_on_file_uid" end @@ -20,16 +19,8 @@ def up unless table_exists?("alchemy_contents") create_table "alchemy_contents", force: :cascade do |t| t.string "name" - t.string "essence_type", null: false - t.integer "essence_id", null: false - t.integer "element_id", null: false - t.integer "position" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" - t.index ["element_id", "position"], name: "index_contents_on_element_id_and_position" - t.index ["essence_id", "essence_type"], name: "index_alchemy_contents_on_essence_id_and_essence_type", unique: true + t.references "essence", null: false, polymorphic: true, index: { unique: true } + t.references "element", null: false end end @@ -37,15 +28,14 @@ def up create_table "alchemy_elements", force: :cascade do |t| t.string "name" t.integer "position" - t.integer "page_id", null: false + t.references "page", null: false, index: false t.boolean "public", default: true t.boolean "folded", default: false t.boolean "unique", default: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" - t.integer "parent_element_id" + t.timestamps null: false + t.references "creator" + t.references "updater" + t.references "parent_element", index: false t.boolean "fixed", default: false, null: false t.index ["fixed"], name: "index_alchemy_elements_on_fixed" t.index ["page_id", "parent_element_id"], name: "index_alchemy_elements_on_page_id_and_parent_element_id" @@ -55,18 +45,14 @@ def up unless table_exists?("alchemy_elements_alchemy_pages") create_table "alchemy_elements_alchemy_pages", id: false, force: :cascade do |t| - t.integer "element_id" - t.integer "page_id" + t.references "element" + t.references "page" end end unless table_exists?("alchemy_essence_booleans") create_table "alchemy_essence_booleans", force: :cascade do |t| t.boolean "value" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" t.index ["value"], name: "index_alchemy_essence_booleans_on_value" end end @@ -74,34 +60,21 @@ def up unless table_exists?("alchemy_essence_dates") create_table "alchemy_essence_dates", force: :cascade do |t| t.datetime "date" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false end end unless table_exists?("alchemy_essence_files") create_table "alchemy_essence_files", force: :cascade do |t| - t.integer "attachment_id" + t.references "attachment" t.string "title" t.string "css_class" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false t.string "link_text" - t.index ["attachment_id"], name: "index_alchemy_essence_files_on_attachment_id" end end unless table_exists?("alchemy_essence_htmls") create_table "alchemy_essence_htmls", force: :cascade do |t| t.text "source" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false end end @@ -111,25 +84,18 @@ def up t.string "link_title" t.string "link_target" t.string "link_class_name" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" end end unless table_exists?("alchemy_essence_pages") create_table "alchemy_essence_pages", force: :cascade do |t| - t.integer "page_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["page_id"], name: "index_alchemy_essence_pages_on_page_id" + t.references "page" end end unless table_exists?("alchemy_essence_pictures") create_table "alchemy_essence_pictures", force: :cascade do |t| - t.integer "picture_id" + t.references "picture" t.string "caption" t.string "title" t.string "alt_tag" @@ -138,14 +104,9 @@ def up t.string "link_title" t.string "css_class" t.string "link_target" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false t.string "crop_from" t.string "crop_size" t.string "render_size" - t.index ["picture_id"], name: "index_alchemy_essence_pictures_on_picture_id" end end @@ -154,20 +115,12 @@ def up t.text "body" t.text "stripped_body" t.boolean "public" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false end end unless table_exists?("alchemy_essence_selects") create_table "alchemy_essence_selects", force: :cascade do |t| t.string "value" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" t.index ["value"], name: "index_alchemy_essence_selects_on_value" end end @@ -180,17 +133,13 @@ def up t.string "link_class_name" t.boolean "public", default: false t.string "link_target" - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false end end unless table_exists?("alchemy_folded_pages") create_table "alchemy_folded_pages", force: :cascade do |t| - t.integer "page_id", null: false - t.integer "user_id", null: false + t.references "page", null: false, index: false + t.references "user", null: false, index: false t.boolean "folded", default: false t.index ["page_id", "user_id"], name: "index_alchemy_folded_pages_on_page_id_and_user_id", unique: true end @@ -203,27 +152,23 @@ def up t.string "frontpage_name" t.string "page_layout", default: "intro" t.boolean "public", default: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" + t.timestamps null: false + t.references "creator" + t.references "updater" t.boolean "default", default: false t.string "country_code", default: "", null: false - t.integer "site_id", null: false + t.references "site", null: false t.string "locale" t.index ["language_code", "country_code"], name: "index_alchemy_languages_on_language_code_and_country_code" t.index ["language_code"], name: "index_alchemy_languages_on_language_code" - t.index ["site_id"], name: "index_alchemy_languages_on_site_id" end end unless table_exists?("alchemy_legacy_page_urls") create_table "alchemy_legacy_page_urls", force: :cascade do |t| t.string "urlname", null: false - t.integer "page_id", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["page_id"], name: "index_alchemy_legacy_page_urls_on_page_id" + t.references "page", null: false + t.timestamps null: false t.index ["urlname"], name: "index_alchemy_legacy_page_urls_on_urlname" end end @@ -236,25 +181,18 @@ def up t.boolean "nofollow", default: false, null: false t.boolean "external", default: false, null: false t.boolean "folded", default: false, null: false - t.integer "parent_id" + t.references "parent" t.integer "lft", null: false t.integer "rgt", null: false t.integer "depth", default: 0, null: false - t.integer "page_id" - t.integer "language_id", null: false - t.integer "creator_id" - t.integer "updater_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "site_id", null: false - t.index ["creator_id"], name: "index_alchemy_nodes_on_creator_id" - t.index ["language_id"], name: "index_alchemy_nodes_on_language_id" + t.references "page" + t.references "language", null: false + t.references "creator" + t.references "updater" + t.timestamps null: false + t.references "site", null: false t.index ["lft"], name: "index_alchemy_nodes_on_lft" - t.index ["page_id"], name: "index_alchemy_nodes_on_page_id" - t.index ["parent_id"], name: "index_alchemy_nodes_on_parent_id" t.index ["rgt"], name: "index_alchemy_nodes_on_rgt" - t.index ["site_id"], name: "index_alchemy_nodes_on_site_id" - t.index ["updater_id"], name: "index_alchemy_nodes_on_updater_id" end end @@ -270,7 +208,7 @@ def up t.text "meta_description" t.integer "lft" t.integer "rgt" - t.integer "parent_id" + t.references "parent", index: false t.integer "depth" t.boolean "visible", default: false t.integer "locked_by" @@ -279,16 +217,14 @@ def up t.boolean "robot_follow", default: true t.boolean "sitemap", default: true t.boolean "layoutpage", default: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" - t.integer "language_id" + t.timestamps null: false + t.references "creator" + t.references "updater" + t.references "language" t.datetime "published_at" t.datetime "public_on" t.datetime "public_until" t.datetime "locked_at" - t.index ["language_id"], name: "index_pages_on_language_id" t.index ["locked_at", "locked_by"], name: "index_alchemy_pages_on_locked_at_and_locked_by" t.index ["parent_id", "lft"], name: "index_pages_on_parent_id_and_lft" t.index ["public_on", "public_until"], name: "index_alchemy_pages_on_public_on_and_public_until" @@ -303,10 +239,9 @@ def up t.string "image_file_name" t.integer "image_file_width" t.integer "image_file_height" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.integer "creator_id" - t.integer "updater_id" + t.timestamps null: false + t.references "creator" + t.references "updater" t.string "upload_hash" t.string "image_file_uid" t.integer "image_file_size" @@ -318,8 +253,7 @@ def up create_table "alchemy_sites", force: :cascade do |t| t.string "host" t.string "name" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.timestamps null: false t.boolean "public", default: false t.text "aliases" t.boolean "redirect_to_primary_host" diff --git a/db/migrate/20200423073425_create_alchemy_essence_nodes.rb b/db/migrate/20200423073425_create_alchemy_essence_nodes.rb new file mode 100644 index 0000000000..cc766c88eb --- /dev/null +++ b/db/migrate/20200423073425_create_alchemy_essence_nodes.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class CreateAlchemyEssenceNodes < ActiveRecord::Migration[5.2] + def change + create_table :alchemy_essence_nodes do |t| + t.references "node" + t.timestamps + end + add_foreign_key "alchemy_essence_nodes", "alchemy_nodes", column: "node_id" + end +end diff --git a/db/migrate/20200504210159_remove_site_id_from_nodes.rb b/db/migrate/20200504210159_remove_site_id_from_nodes.rb new file mode 100644 index 0000000000..629056023b --- /dev/null +++ b/db/migrate/20200504210159_remove_site_id_from_nodes.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +class RemoveSiteIdFromNodes < ActiveRecord::Migration[5.2] + def up + remove_foreign_key :alchemy_nodes, :alchemy_sites + remove_index :alchemy_nodes, :site_id + remove_column :alchemy_nodes, :site_id, :integer, null: false + end + + def down + add_column :alchemy_nodes, :site_id, :integer, null: true + sql = <<~SQL + UPDATE alchemy_nodes + SET site_id = ( + SELECT alchemy_languages.site_id FROM alchemy_languages WHERE alchemy_nodes.language_id = alchemy_languages.id + ) WHERE + EXISTS ( + SELECT * + FROM alchemy_languages + WHERE alchemy_languages.id = alchemy_nodes.language_id + ); + SQL + + connection.execute(sql) + change_column :alchemy_nodes, :site_id, :integer, null: false + add_index :alchemy_nodes, :site_id + add_foreign_key :alchemy_nodes, :alchemy_sites, column: :site_id + end +end diff --git a/db/migrate/20200505215518_add_language_id_foreign_key_to_alchemy_pages.rb b/db/migrate/20200505215518_add_language_id_foreign_key_to_alchemy_pages.rb new file mode 100644 index 0000000000..83dde8a837 --- /dev/null +++ b/db/migrate/20200505215518_add_language_id_foreign_key_to_alchemy_pages.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddLanguageIdForeignKeyToAlchemyPages < ActiveRecord::Migration[5.2] + def change + add_foreign_key :alchemy_pages, :alchemy_languages, column: :language_id + change_column_null :alchemy_pages, :language_id, false, Alchemy::Language.default&.id + end +end diff --git a/db/migrate/20200511113603_add_menu_type_to_alchemy_nodes.rb b/db/migrate/20200511113603_add_menu_type_to_alchemy_nodes.rb new file mode 100644 index 0000000000..258f31cae1 --- /dev/null +++ b/db/migrate/20200511113603_add_menu_type_to_alchemy_nodes.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +class AddMenuTypeToAlchemyNodes < ActiveRecord::Migration[5.2] + class LocalNode < ActiveRecord::Base + self.table_name = :alchemy_nodes + acts_as_nested_set scope: :language_id + + def self.root_for(node) + return node if node.parent_id.nil? + + root_for(node.parent) + end + end + + def up + add_column :alchemy_nodes, :menu_type, :string + LocalNode.all.each do |node| + root = LocalNode.root_for(node) + menu_type = root.name.parameterize.underscore + node.update(menu_type: menu_type) + end + change_column_null :alchemy_nodes, :menu_type, false + end + + def down + remove_column :alchemy_nodes, :menu_type + end +end diff --git a/db/migrate/20200514091507_make_page_layoutpage_null_false.rb b/db/migrate/20200514091507_make_page_layoutpage_null_false.rb new file mode 100644 index 0000000000..1e68bc1c48 --- /dev/null +++ b/db/migrate/20200514091507_make_page_layoutpage_null_false.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +class MakePageLayoutpageNullFalse < ActiveRecord::Migration[5.2] + def change + change_column_null :alchemy_pages, :layoutpage, false, false + end +end diff --git a/db/migrate/20200519073500_remove_visible_from_alchemy_pages.rb b/db/migrate/20200519073500_remove_visible_from_alchemy_pages.rb new file mode 100644 index 0000000000..566f450255 --- /dev/null +++ b/db/migrate/20200519073500_remove_visible_from_alchemy_pages.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +class RemoveVisibleFromAlchemyPages < ActiveRecord::Migration[5.2] + class LocalPage < ActiveRecord::Base + self.table_name = "alchemy_pages" + + scope :invisible, -> { where(visible: [false, nil]) } + scope :contentpages, -> { where(layoutpage: [false, nil]) } + end + + def up + if LocalPage.invisible.contentpages.where.not(parent_id: nil).any? + abort "You have invisible pages in your database! " \ + "Please re-structure your page tree before running this migration. " \ + "You might also downgrade to Alchemy 4.6 and " \ + "run the `alchemy:upgrade:4.6:restructure_page_tree` rake task." + end + + remove_column :alchemy_pages, :visible + end + + def down + add_column :alchemy_pages, :visible, :boolean, default: false + end +end diff --git a/lib/alchemy/admin/locale.rb b/lib/alchemy/admin/locale.rb index 7300e4b5e1..d5627afa77 100644 --- a/lib/alchemy/admin/locale.rb +++ b/lib/alchemy/admin/locale.rb @@ -64,7 +64,7 @@ def user_has_preferred_language? # Try to get the locale from browser headers. def locale_from_browser - request.env['HTTP_ACCEPT_LANGUAGE'].try(:scan, /\A[a-z]{2}/).try(:first) + request.env["HTTP_ACCEPT_LANGUAGE"].try(:scan, /\A[a-z]{2}/).try(:first) end end end diff --git a/lib/alchemy/admin/preview_url.rb b/lib/alchemy/admin/preview_url.rb new file mode 100644 index 0000000000..2b2325e88a --- /dev/null +++ b/lib/alchemy/admin/preview_url.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "uri" + +module Alchemy + module Admin + # = Preview window URL configuration + # + # By default Alchemy uses its internal page preview renderer, + # but you can configure it to be any URL instead. + # + # Basic Auth is supported. + # + # == Example config/alchemy/config.yml + # + # preview: + # host: https://www.my-static-site.com + # auth: + # username: <%= ENV["BASIC_AUTH_USERNAME"] %> + # password: <%= ENV["BASIC_AUTH_PASSWORD"] %> + # + # Preview config per site is supported as well. + # + # == Example config/alchemy/config.yml + # + # preview: + # My site name: + # host: https://www.my-static-site.com + # auth: + # username: <%= ENV["BASIC_AUTH_USERNAME"] %> + # password: <%= ENV["BASIC_AUTH_PASSWORD"] %> + # + class PreviewUrl + class MissingProtocolError < StandardError; end + + def initialize(routes:) + @routes = routes.url_helpers + end + + def url_for(page) + @preview_config = preview_config_for(page) + + if @preview_config && uri + uri_class.build( + host: uri.host, + path: page.url_path, + userinfo: userinfo, + ).to_s + else + routes.admin_page_path(page) + end + end + + private + + attr_reader :routes + + def preview_config_for(page) + preview_config = Alchemy::Config.get(:preview) + return unless preview_config + + preview_config[page.site.name] || preview_config + end + + def uri + return unless @preview_config["host"] + + URI(@preview_config["host"]) + end + + def uri_class + if uri.class == URI::Generic + raise MissingProtocolError, "Please provide the protocol with preview['host']" + else + uri.class + end + end + + def userinfo + auth = @preview_config["auth"] + auth ? "#{auth["username"]}:#{auth["password"]}" : nil + end + end + end +end diff --git a/lib/alchemy/auth_accessors.rb b/lib/alchemy/auth_accessors.rb index d35fa6afe3..0114eff556 100644 --- a/lib/alchemy/auth_accessors.rb +++ b/lib/alchemy/auth_accessors.rb @@ -53,13 +53,13 @@ module Alchemy # Defaults # - @@user_class_name = 'User' + @@user_class_name = "User" @@user_class_primary_key = :id - @@current_user_method = 'current_user' - @@signup_path = '/signup' - @@login_path = '/login' - @@logout_path = '/logout' - @@logout_method = 'delete' + @@current_user_method = "current_user" + @@signup_path = "/signup" + @@login_path = "/login" + @@logout_path = "/logout" + @@logout_method = "delete" # Returns the user class # @@ -76,7 +76,7 @@ module Alchemy # Prefix with :: when getting to avoid constant name conflicts def self.user_class_name if !@@user_class_name.is_a?(String) - raise TypeError, 'Alchemy.user_class_name must be a String, not a Class.' + raise TypeError, "Alchemy.user_class_name must be a String, not a Class." end "::#{@@user_class_name}" diff --git a/lib/alchemy/cache_digests/template_tracker.rb b/lib/alchemy/cache_digests/template_tracker.rb index ef1e646759..d6f6aa7a88 100644 --- a/lib/alchemy/cache_digests/template_tracker.rb +++ b/lib/alchemy/cache_digests/template_tracker.rb @@ -14,10 +14,10 @@ def initialize(name, template) def dependencies case @name.to_s when /^alchemy\/pages\/show/ - PageLayout.all.map { |p| "alchemy/page_layouts/_#{p['name']}" } + PageLayout.all.map { |p| "alchemy/page_layouts/_#{p["name"]}" } when /^alchemy\/page_layouts\/_(\w+)/ page_layout = PageLayout.get($1) - layout_elements = page_layout.fetch('elements', []) + layout_elements = page_layout.fetch("elements", []) layout_elements.map { |name| "alchemy/elements/_#{name}_view" } + layout_elements.map { |name| "alchemy/elements/_#{name}" } when /^alchemy\/elements\/_(\w+)_view/, /^alchemy\/elements\/_(\w+)/ @@ -32,10 +32,10 @@ def dependencies private def essence_types(name) - element = Element.definitions.detect { |e| e['name'] == name } + element = Element.definitions.detect { |e| e["name"] == name } return [] unless element - element.fetch('contents', []).collect { |c| c['type'] } + element.fetch("contents", []).collect { |c| c["type"] } end end end diff --git a/lib/alchemy/config.rb b/lib/alchemy/config.rb index 7b60a8919d..ebaf634c71 100644 --- a/lib/alchemy/config.rb +++ b/lib/alchemy/config.rb @@ -8,8 +8,10 @@ class << self # @param name [String] # def get(name) + check_deprecation(name) show[name.to_s] end + alias_method :parameter, :get # Returns a merged configuration of the following files @@ -25,11 +27,18 @@ def show @config ||= merge_configs!(alchemy_config, main_app_config, env_specific_config) end + # A list of deprecated configurations + # a value of nil means there is no new default + # any not nil value is the new default + def deprecated_configs + {} + end + private # Alchemy default configuration def alchemy_config - read_file(File.join(File.dirname(__FILE__), '..', '..', 'config/alchemy/config.yml')) + read_file(File.join(File.dirname(__FILE__), "..", "..", "config/alchemy/config.yml")) end # Application specific configuration @@ -54,12 +63,26 @@ def read_file(file) # Merges all given configs together # def merge_configs!(*config_files) - raise LoadError, 'No Alchemy config file found!' if config_files.map(&:blank?).all? + raise LoadError, "No Alchemy config file found!" if config_files.map(&:blank?).all? config = {} config_files.each { |h| config.merge!(h.stringify_keys!) } config end + + def check_deprecation(name) + if deprecated_configs.key?(name.to_sym) + config = deprecated_configs[name.to_sym] + if config.nil? + Alchemy::Deprecation.warn("#{name} configuration is deprecated and will be removed from Alchemy 5.0") + else + value = show[name.to_s] + if value != config + Alchemy::Deprecation.warn("Setting #{name} configuration to #{value} is deprecated and will be always #{config} in Alchemy 5.0") + end + end + end + end end end end diff --git a/lib/alchemy/configuration_methods.rb b/lib/alchemy/configuration_methods.rb index 973f323fcd..a80fab9478 100644 --- a/lib/alchemy/configuration_methods.rb +++ b/lib/alchemy/configuration_methods.rb @@ -30,6 +30,7 @@ def multi_language? # def prefix_locale?(locale = Language.current&.code) return false unless locale + multi_language? && locale != ::I18n.default_locale.to_s end diff --git a/lib/alchemy/controller_actions.rb b/lib/alchemy/controller_actions.rb index 6ac7db7fc7..177323055e 100644 --- a/lib/alchemy/controller_actions.rb +++ b/lib/alchemy/controller_actions.rb @@ -8,7 +8,7 @@ module ControllerActions before_action :set_current_alchemy_site before_action :set_alchemy_language - helper 'alchemy/pages' + helper "alchemy/pages" helper_method :current_alchemy_user, :alchemy_user_signed_in?, diff --git a/lib/alchemy/deprecation.rb b/lib/alchemy/deprecation.rb index c2ccd9c987..37c738abc0 100644 --- a/lib/alchemy/deprecation.rb +++ b/lib/alchemy/deprecation.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true module Alchemy - Deprecation = ActiveSupport::Deprecation.new('5.0', 'Alchemy') + Deprecation = ActiveSupport::Deprecation.new("5.0", "Alchemy") end diff --git a/lib/alchemy/elements_finder.rb b/lib/alchemy/elements_finder.rb index 0befac9a8b..66c06416a2 100644 --- a/lib/alchemy/elements_finder.rb +++ b/lib/alchemy/elements_finder.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'alchemy/logger' +require "alchemy/logger" module Alchemy # Loads elements from given page @@ -86,7 +86,7 @@ def get_page(page_or_layout) Alchemy::Page.find_by( language: Alchemy::Language.current, page_layout: page_or_layout, - restricted: false + restricted: false, ) end end @@ -104,10 +104,10 @@ def fallback_elements def random_function case ActiveRecord::Base.connection_config[:adapter] - when 'postgresql', 'sqlite3' - 'RANDOM()' + when "postgresql", "sqlite3" + "RANDOM()" else - 'RAND()' + "RAND()" end end end diff --git a/lib/alchemy/engine.rb b/lib/alchemy/engine.rb index c041313997..08b2ad8e64 100644 --- a/lib/alchemy/engine.rb +++ b/lib/alchemy/engine.rb @@ -2,42 +2,46 @@ module Alchemy class Engine < Rails::Engine isolate_namespace Alchemy - engine_name 'alchemy' - config.mount_at = '/' + engine_name "alchemy" + config.mount_at = "/" - initializer 'alchemy.lookup_context' do - Alchemy::LOOKUP_CONTEXT = ActionView::LookupContext.new(Rails.root.join('app', 'views', 'alchemy')) + initializer "alchemy.lookup_context" do + Alchemy::LOOKUP_CONTEXT = ActionView::LookupContext.new(Rails.root.join("app", "views", "alchemy")) end - initializer 'alchemy.dependency_tracker' do + initializer "alchemy.admin.preview_url" do + Alchemy::Admin::PREVIEW_URL = Alchemy::Admin::PreviewUrl.new(routes: Alchemy::Engine.routes) + end + + initializer "alchemy.dependency_tracker" do [:erb, :slim, :haml].each do |handler| ActionView::DependencyTracker.register_tracker(handler, CacheDigests::TemplateTracker) end end - initializer 'alchemy.non_digest_assets' do + initializer "alchemy.non_digest_assets" do NonStupidDigestAssets.whitelist += [/^tinymce\//] end # Gutentag downcases all tgas before save. # We support having tags with uppercase characters. # The Gutentag search is case insensitive. - initializer 'alchemy.gutentag_normalizer' do + initializer "alchemy.gutentag_normalizer" do Gutentag.normaliser = ->(value) { value.to_s } end # Custom Ransack sort arrows - initializer 'alchemy.ransack' do + initializer "alchemy.ransack" do Ransack.configure do |config| config.custom_arrows = { up_arrow: '', - down_arrow: '' + down_arrow: '', } end end config.after_initialize do - require_relative './userstamp' + require_relative "./userstamp" end end end diff --git a/lib/alchemy/essence.rb b/lib/alchemy/essence.rb index 81baa58d20..5bb8f77da1 100644 --- a/lib/alchemy/essence.rb +++ b/lib/alchemy/essence.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'active_record' +require "active_record" module Alchemy #:nodoc: # A bogus association that skips eager loading for essences not having an ingredient association @@ -40,7 +40,7 @@ def acts_as_essence(options = {}) register_as_essence_association! configuration = { - ingredient_column: 'body' + ingredient_column: "body", }.update(options) @_classes_with_ingredient_association ||= [] @@ -48,7 +48,7 @@ def acts_as_essence(options = {}) class_eval <<-RUBY, __FILE__, __LINE__ + 1 attr_writer :validation_errors include Alchemy::Essence::InstanceMethods - stampable stamper_class_name: Alchemy.user_class_name + validate :validate_ingredient, on: :update, if: -> { validations.any? } has_one :content, as: :essence, class_name: "Alchemy::Content", inverse_of: :essence @@ -62,7 +62,7 @@ def acts_as_essence(options = {}) delegate :trashed?, to: :element, allow_nil: true delegate :public?, to: :element, allow_nil: true - after_update :touch_content + after_save :touch_element def acts_as_essence_class #{name} @@ -109,7 +109,7 @@ def _reflect_on_association(name) def register_as_essence_association! klass_name = model_name.to_s arguments = [:has_many, klass_name.demodulize.tableize.to_sym, through: :contents, - source: :essence, source_type: klass_name] + source: :essence, source_type: klass_name] %w(Page Element).each { |k| "Alchemy::#{k}".constantize.send(*arguments) } end end @@ -171,7 +171,7 @@ def validate_ingredient end def validations - @validations ||= definition.present? ? definition['validate'] || [] : [] + @validations ||= definition.present? ? definition["validate"] || [] : [] end def validation_errors @@ -195,7 +195,7 @@ def validate_uniqueness(validate = true) end def validate_format(format) - matcher = Config.get('format_matchers')[format] || format + matcher = Config.get("format_matchers")[format] || format if ingredient.to_s.match(Regexp.new(matcher)).nil? errors.add(ingredient_column, :invalid) validation_errors << :invalid @@ -226,21 +226,19 @@ def ingredient=(value) # Returns the setter method for ingredient column def ingredient_setter_method - ingredient_column.to_s + '=' + ingredient_column.to_s + "=" end # Essence definition from config/elements.yml def definition return {} if element.nil? || element.content_definitions.nil? - element.content_definitions.detect { |c| c['name'] == content.name } || {} + element.content_definitions.detect { |c| c["name"] == content.name } || {} end - # Touch content. Called after update. - def touch_content - return nil if content.nil? - - content.touch + # Touches element. Called after save. + def touch_element + element&.touch end # Returns the first x (default 30) characters of ingredient for the Element#preview_text method. @@ -250,11 +248,11 @@ def preview_text(maxlength = 30) end def open_link_in_new_window? - respond_to?(:link_target) && link_target == 'blank' + respond_to?(:link_target) && link_target == "blank" end def partial_name - self.class.name.split('::').last.underscore + self.class.name.split("::").last.underscore end def acts_as_essence? diff --git a/lib/alchemy/filetypes.rb b/lib/alchemy/filetypes.rb index 947b55de4c..d5461342bb 100644 --- a/lib/alchemy/filetypes.rb +++ b/lib/alchemy/filetypes.rb @@ -8,7 +8,7 @@ module Filetypes "audio/mpeg", "audio/mp4", "audio/wav", - "audio/x-wav" + "audio/x-wav", ] IMAGE_FILE_TYPES = [ @@ -17,7 +17,7 @@ module Filetypes "image/png", "image/svg+xml", "image/tiff", - "image/x-psd" + "image/x-psd", ] VCARD_FILE_TYPES = ["text/x-vcard", "application/vcard"] @@ -29,18 +29,18 @@ module Filetypes "video/mpeg", "video/quicktime", "video/x-msvideo", - "video/x-ms-wmv" + "video/x-ms-wmv", ] TEXT_FILE_TYPES = [ "application/rtf", - "text/plain" + "text/plain", ] EXCEL_FILE_TYPES = [ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel", - "text/csv" + "text/csv", ] end end diff --git a/lib/alchemy/forms/builder.rb b/lib/alchemy/forms/builder.rb index 159957ad87..53fd36da68 100644 --- a/lib/alchemy/forms/builder.rb +++ b/lib/alchemy/forms/builder.rb @@ -11,7 +11,7 @@ def input(attribute_name, options = {}, &block) if object.respond_to?(:attribute_fixed?) && object.attribute_fixed?(attribute_name) options[:disabled] = true options[:input_html] = options.fetch(:input_html, {}).merge( - 'data-alchemy-tooltip' => Alchemy.t(:attribute_fixed, attribute_name) + "data-alchemy-tooltip" => Alchemy.t(:attribute_fixed, attribute_name), ) end @@ -22,10 +22,10 @@ def input(attribute_name, options = {}, &block) # def submit(label, options = {}) options = { - wrapper_html: {class: 'submit'} + wrapper_html: {class: "submit"}, }.update(options) - template.content_tag('div', options.delete(:wrapper_html)) do - template.content_tag('button', label, options.delete(:input_html)) + template.content_tag("div", options.delete(:wrapper_html)) do + template.content_tag("button", label, options.delete(:input_html)) end end end diff --git a/lib/alchemy/hints.rb b/lib/alchemy/hints.rb index a7ed3128fa..e767626bc2 100644 --- a/lib/alchemy/hints.rb +++ b/lib/alchemy/hints.rb @@ -35,7 +35,7 @@ module Hints # @return String # def hint - hint = definition['hint'] + hint = definition["hint"] if hint == true Alchemy.t(name, scope: hint_translation_scope) else diff --git a/lib/alchemy/i18n.rb b/lib/alchemy/i18n.rb index 4f139709fd..fdfe934d47 100644 --- a/lib/alchemy/i18n.rb +++ b/lib/alchemy/i18n.rb @@ -79,7 +79,7 @@ def humanize_default_string!(msg, options) end def alchemy_scoped_scope(options) - default_scope = ['alchemy'] + default_scope = ["alchemy"] case options[:scope] when Array default_scope + options[:scope] diff --git a/lib/alchemy/modules.rb b/lib/alchemy/modules.rb index 898f9ce4d0..907fa00225 100644 --- a/lib/alchemy/modules.rb +++ b/lib/alchemy/modules.rb @@ -4,7 +4,7 @@ module Alchemy module Modules mattr_accessor :alchemy_modules - @@alchemy_modules = YAML.load_file(File.expand_path('../../config/alchemy/modules.yml', __dir__)) + @@alchemy_modules = YAML.load_file(File.expand_path("../../config/alchemy/modules.yml", __dir__)) class << self def included(base) @@ -28,11 +28,11 @@ def register_module(module_definition) definition_hash = module_definition.deep_stringify_keys ### Validate controller(s) existence - if definition_hash['navigation'].is_a?(Hash) - defined_controllers = [definition_hash['navigation']['controller']] + if definition_hash["navigation"].is_a?(Hash) + defined_controllers = [definition_hash["navigation"]["controller"]] - if definition_hash['navigation']['sub_navigation'].is_a?(Array) - defined_controllers.concat(definition_hash['navigation']['sub_navigation'].map{ |x| x['controller'] }) + if definition_hash["navigation"]["sub_navigation"].is_a?(Array) + defined_controllers.concat(definition_hash["navigation"]["sub_navigation"].map{ |x| x["controller"] }) end validate_controllers_existence(defined_controllers) @@ -52,7 +52,7 @@ def validate_controllers_existence(controllers) begin controller_name.constantize rescue NameError - raise "Error in AlchemyCMS module definition: '#{definition_hash['name']}'. Could not find the matching controller class #{controller_name.sub(/^::/, '')} for the specified controller: '#{controller_val}'" + raise "Error in AlchemyCMS module definition: '#{definition_hash["name"]}'. Could not find the matching controller class #{controller_name.sub(/^::/, "")} for the specified controller: '#{controller_val}'" end end end @@ -66,11 +66,11 @@ def validate_controllers_existence(controllers) def module_definition_for(name_or_params) case name_or_params when String - alchemy_modules.detect { |m| m['name'] == name_or_params } + alchemy_modules.detect { |m| m["name"] == name_or_params } when Hash name_or_params.stringify_keys! alchemy_modules.detect do |alchemy_module| - module_navi = alchemy_module.fetch('navigation', {}) + module_navi = alchemy_module.fetch("navigation", {}) definition_from_mainnavi(module_navi, name_or_params) || definition_from_subnavi(module_navi, name_or_params) end @@ -86,7 +86,7 @@ def definition_from_mainnavi(module_navi, params) end def definition_from_subnavi(module_navi, params) - subnavi = module_navi['sub_navigation'] + subnavi = module_navi["sub_navigation"] return if subnavi.nil? subnavi.any? do |navi| @@ -95,15 +95,15 @@ def definition_from_subnavi(module_navi, params) end def controller_matches?(navi, params) - remove_slash(navi['controller']) == remove_slash(params['controller']) + remove_slash(navi["controller"]) == remove_slash(params["controller"]) end def action_matches?(navi, params) - navi['action'] == params['action'] + navi["action"] == params["action"] end def remove_slash(str) - str.gsub(/^\//, '') + str.gsub(/^\//, "") end end end diff --git a/lib/alchemy/name_conversions.rb b/lib/alchemy/name_conversions.rb index 9d3b84db03..6962366b2a 100644 --- a/lib/alchemy/name_conversions.rb +++ b/lib/alchemy/name_conversions.rb @@ -10,17 +10,17 @@ module NameConversions # @returns String def convert_to_urlname(name) name - .gsub(/[äÄ]/, 'ae') - .gsub(/[üÜ]/, 'ue') - .gsub(/[öÖ]/, 'oe') - .gsub(/[ß]/, 'ss') + .gsub(/[äÄ]/, "ae") + .gsub(/[üÜ]/, "ue") + .gsub(/[öÖ]/, "oe") + .gsub(/[ß]/, "ss") .parameterize end # Converts a filename and suffix into a human readable name. # def convert_to_humanized_name(name, suffix) - name.gsub(/\.#{::Regexp.quote(suffix)}$/i, '').tr('_', ' ').strip + name.gsub(/\.#{::Regexp.quote(suffix)}$/i, "").tr("_", " ").strip end end end diff --git a/lib/alchemy/page_layout.rb b/lib/alchemy/page_layout.rb index fec48f918c..6827b6eafa 100644 --- a/lib/alchemy/page_layout.rb +++ b/lib/alchemy/page_layout.rb @@ -38,7 +38,7 @@ def add(page_layout) def get(name) return {} if name.blank? - all.detect { |a| a['name'].casecmp(name).zero? } + all.detect { |a| a["name"].casecmp(name).zero? } end def get_all_by_attributes(attributes) @@ -67,7 +67,7 @@ def layouts_for_select(language_id, only_layoutpages = false) # def layouts_with_own_for_select(page_layout_name, language_id, only_layoutpages = false) layouts = selectable_layouts(language_id, only_layoutpages) - if layouts.detect { |l| l['name'] == page_layout_name }.nil? + if layouts.detect { |l| l["name"] == page_layout_name }.nil? @map_array = [[human_layout_name(page_layout_name), page_layout_name]] else @map_array = [] @@ -88,9 +88,9 @@ def selectable_layouts(language_id, only_layoutpages = false) @language_id = language_id all.select do |layout| if only_layoutpages - layout['layoutpage'] && layout_available?(layout) + layout["layoutpage"] && layout_available?(layout) else - !layout['layoutpage'] && layout_available?(layout) + !layout["layoutpage"] && layout_available?(layout) end end end @@ -99,7 +99,7 @@ def selectable_layouts(language_id, only_layoutpages = false) # def element_names_for(page_layout) if definition = get(page_layout) - definition.fetch('elements', []) + definition.fetch("elements", []) else Rails.logger.warn "\n+++ Warning: No layout definition for #{page_layout} found! in page_layouts.yml\n" [] @@ -119,7 +119,7 @@ def element_names_for(page_layout) # The layout name # def human_layout_name(layout) - Alchemy.t(layout, scope: 'page_layout_names', default: layout.to_s.humanize) + Alchemy.t(layout, scope: "page_layout_names", default: layout.to_s.humanize) end private @@ -127,13 +127,13 @@ def human_layout_name(layout) # Returns true if the given layout is unique and not already taken or it should be hidden. # def layout_available?(layout) - !layout['hide'] && !already_taken?(layout) && available_on_site?(layout) + !layout["hide"] && !already_taken?(layout) && available_on_site?(layout) end # Returns true if this layout is unique and already taken by another page. # def already_taken?(layout) - layout['unique'] && page_with_layout_existing?(layout['name']) + layout["unique"] && page_with_layout_existing?(layout["name"]) end # Returns true if one page already has the given layout @@ -154,8 +154,9 @@ def page_with_layout_existing?(layout) # def available_on_site?(layout) return false unless Alchemy::Site.current + Alchemy::Site.current.definition.blank? || - Alchemy::Site.current.definition.fetch('page_layouts', []).include?(layout['name']) + Alchemy::Site.current.definition.fetch("page_layouts", []).include?(layout["name"]) end # Reads the layout definitions from +config/alchemy/page_layouts.yml+. @@ -171,14 +172,14 @@ def read_definitions_file # Returns the page_layouts.yml file path # def layouts_file_path - Rails.root.join 'config/alchemy/page_layouts.yml' + Rails.root.join "config/alchemy/page_layouts.yml" end # Maps given layouts for Rails select form helper. # def mapped_layouts_for_select(layouts) layouts.each do |layout| - @map_array << [human_layout_name(layout['name']), layout["name"]] + @map_array << [human_layout_name(layout["name"]), layout["name"]] end @map_array end diff --git a/lib/alchemy/paths.rb b/lib/alchemy/paths.rb index 241eeca1c5..a3e263d795 100644 --- a/lib/alchemy/paths.rb +++ b/lib/alchemy/paths.rb @@ -29,6 +29,6 @@ module Alchemy # Defaults # - @@admin_path = 'admin' + @@admin_path = "admin" @@admin_constraints = {} end diff --git a/lib/alchemy/permissions.rb b/lib/alchemy/permissions.rb index 81bb3b606b..946e0a643b 100644 --- a/lib/alchemy/permissions.rb +++ b/lib/alchemy/permissions.rb @@ -36,7 +36,7 @@ def initialize(user) module GuestUser def alchemy_guest_user_rules can([:show, :download], Alchemy::Attachment) { |a| !a.restricted? } - can :see, Alchemy::Page, restricted: false, visible: true + can :see, Alchemy::Page, restricted: false can :read, Alchemy::Content, Alchemy::Content.available.not_restricted do |c| c.public? && !c.restricted? && !c.trashed? @@ -64,7 +64,7 @@ def alchemy_member_rules # Resources can [:show, :download], Alchemy::Attachment - can :see, Alchemy::Page, restricted: true, visible: true + can :see, Alchemy::Page, restricted: true can :read, Alchemy::Content, Alchemy::Content.available do |c| c.public? && !c.trashed? @@ -99,7 +99,7 @@ def alchemy_author_rules :alchemy_admin_pages, :alchemy_admin_pictures, :alchemy_admin_tags, - :alchemy_admin_users + :alchemy_admin_users, ] # Controller actions @@ -137,7 +137,7 @@ def alchemy_editor_rules # Navigation can :index, [ :alchemy_admin_languages, - :alchemy_admin_users + :alchemy_admin_users, ] # Controller actions @@ -150,7 +150,7 @@ def alchemy_editor_rules :flush, :order, :sort, - :switch_language + :switch_language, ], Alchemy::Page # Resources which may be locked via template permissions @@ -164,7 +164,7 @@ def alchemy_editor_rules can([ :create, :destroy, - :publish + :publish, ], Alchemy::Page) { |p| p.editable_by?(@user) } can :manage, Alchemy::Picture diff --git a/lib/alchemy/resource.rb b/lib/alchemy/resource.rb index 7bd647217f..c7326b54d0 100644 --- a/lib/alchemy/resource.rb +++ b/lib/alchemy/resource.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'active_support' -require 'active_support/core_ext' -require 'active_support/inflector' +require "active_support" +require "active_support/core_ext" +require "active_support/inflector" module Alchemy # = Alchemy::Resource @@ -120,7 +120,7 @@ def initialize(controller_path, module_definition = nil, custom_model = nil) end def resource_array - @_resource_array ||= controller_path_array.reject { |el| el == 'admin' } + @_resource_array ||= controller_path_array.reject { |el| el == "admin" } end def resources_name @@ -139,7 +139,7 @@ def namespaced_resources_name @_namespaced_resources_name ||= begin resource_name_array = resource_array.dup resource_name_array.delete(engine_name) if in_engine? - resource_name_array.join('_') + resource_name_array.join("_") end end @@ -166,16 +166,16 @@ def attributes { name: col.name, type: resource_column_type(col), - relation: resource_relation(col.name) + relation: resource_relation(col.name), }.delete_if { |_k, v| v.nil? } end.compact end def sorted_attributes @_sorted_attributes ||= attributes. - sort_by { |attr| attr[:name] == 'name' ? 0 : 1 }. + sort_by { |attr| attr[:name] == "name" ? 0 : 1 }. sort_by! { |attr| attr[:type] == :boolean ? 1 : 0 }. - sort_by! { |attr| attr[:name] == 'updated_at' ? 1 : 0 } + sort_by! { |attr| attr[:name] == "updated_at" ? 1 : 0 } end def editable_attributes @@ -207,7 +207,7 @@ def in_engine? end def engine_name - @module_definition && @module_definition['engine_name'] + @module_definition && @module_definition["engine_name"] end # Returns a help text for resource's form @@ -264,16 +264,16 @@ def searchable_relation_attributes(attrs) def searchable_relation_attribute(attribute) { name: "#{attribute[:relation][:model_association].name}_#{attribute[:relation][:attr_method]}", - type: attribute[:relation][:attr_type] + type: attribute[:relation][:attr_type], } end def guess_model_from_controller_path - resource_array.join('/').classify.constantize + resource_array.join("/").classify.constantize end def controller_path_array - @controller_path.split('/') + @controller_path.split("/") end def namespace_diff @@ -296,7 +296,7 @@ def resource_relation(column_name) def map_relations self.resource_relations = {} model.alchemy_resource_relations.each do |name, options| - name = name.to_s.gsub(/_id$/, '') # ensure that we don't have an id + name = name.to_s.gsub(/_id$/, "") # ensure that we don't have an id association = association_from_relation_name(name) foreign_key = association.options[:foreign_key] || "#{association.name}_id".to_sym resource_relations[foreign_key] = options.merge(model_association: association, name: name) diff --git a/lib/alchemy/resources_helper.rb b/lib/alchemy/resources_helper.rb index de75ad51ea..c8a7241576 100644 --- a/lib/alchemy/resources_helper.rb +++ b/lib/alchemy/resources_helper.rb @@ -104,21 +104,21 @@ def resource_attribute_field_options(attribute) options = {hint: resource_handler.help_text_for(attribute)} input_type = attribute[:type].to_s case input_type - when 'boolean' + when "boolean" options - when 'date', 'time', 'datetime' + when "date", "time", "datetime" date = resource_instance_variable.send(attribute[:name]) || Time.current options.merge( - as: 'string', + as: "string", input_html: { - 'data-datepicker-type' => input_type, - value: date ? date.iso8601 : nil - } + "data-datepicker-type" => input_type, + value: date ? date.iso8601 : nil, + }, ) - when 'text' - options.merge(as: 'text', input_html: {rows: 4}) + when "text" + options.merge(as: "text", input_html: {rows: 4}) else - options.merge(as: 'string') + options.merge(as: "string") end end @@ -165,7 +165,7 @@ def sortable_resource_header_column(attribute) def render_resources render partial: resource_name, collection: resources_instance_variable rescue ActionView::MissingTemplate - render partial: 'resource', collection: resources_instance_variable + render partial: "resource", collection: resources_instance_variable end def resource_has_tags @@ -179,8 +179,8 @@ def resource_has_filters def resource_filter_select resource_model.alchemy_resource_filters.map do |filter_scope| [ - Alchemy.t(filter_scope.to_sym, scope: ['resources', resource_name, 'filters']), - filter_scope + Alchemy.t(filter_scope.to_sym, scope: ["resources", resource_name, "filters"]), + filter_scope, ] end end diff --git a/lib/alchemy/routing_constraints.rb b/lib/alchemy/routing_constraints.rb index 54844b2bcc..16390a46ed 100644 --- a/lib/alchemy/routing_constraints.rb +++ b/lib/alchemy/routing_constraints.rb @@ -34,7 +34,7 @@ def handable_format? def no_rails_route? return true if !%w(development test).include?(Rails.env) - (@params['urlname'] =~ /\Arails\//).nil? + (@params["urlname"] =~ /\Arails\//).nil? end end end diff --git a/lib/alchemy/seeder.rb b/lib/alchemy/seeder.rb index 6cb2e35fab..31f1bcfa62 100644 --- a/lib/alchemy/seeder.rb +++ b/lib/alchemy/seeder.rb @@ -45,9 +45,8 @@ def seed_pages contentpages.each do |page| create_page(page, { - parent: Alchemy::Page.root, language: Alchemy::Language.default, - language_root: true + language_root: true, }) end end @@ -55,12 +54,8 @@ def seed_pages def seed_layoutpages desc "Seeding Alchemy layout pages from #{page_seeds_file}" language = Alchemy::Language.default - layout_root = Alchemy::Page.find_or_create_layout_root_for(language.id) layoutpages.each do |page| - create_page(page, { - parent: layout_root, - language: language - }) + create_page(page, { language: language }) end end @@ -83,7 +78,7 @@ def seed_users private def page_seeds_file - @_page_seeds_file ||= Rails.root.join('db', 'seeds', 'alchemy', 'pages.yml') + @_page_seeds_file ||= Rails.root.join("db", "seeds", "alchemy", "pages.yml") end def page_yml @@ -91,23 +86,23 @@ def page_yml end def contentpages - page_yml.reject { |p| p['layoutpage'] } + page_yml.reject { |p| p["layoutpage"] } end def layoutpages - page_yml.select { |p| p['layoutpage'] } + page_yml.select { |p| p["layoutpage"] } end def user_seeds_file - @_user_seeds_file ||= Rails.root.join('db', 'seeds', 'alchemy', 'users.yml') + @_user_seeds_file ||= Rails.root.join("db", "seeds", "alchemy", "users.yml") end def create_page(draft, attributes = {}) - children = draft.delete('children') || [] + children = draft.delete("children") || [] page = Alchemy::Page.create!(draft.merge(attributes)) log "Created page: #{page.name}" children.each do |child| - create_page(child, parent: page) + create_page(child, parent: page, language: page.language) end end @@ -117,14 +112,14 @@ def create_default_language! default_language = Alchemy::Config.get(:default_language) if default_language Alchemy::Language.create!( - name: default_language['name'], - language_code: default_language['code'], - locale: default_language['code'], - frontpage_name: default_language['frontpage_name'], - page_layout: default_language['page_layout'], - public: true, - default: true, - site: Alchemy::Site.default + name: default_language["name"], + language_code: default_language["code"], + locale: default_language["code"], + frontpage_name: default_language["frontpage_name"], + page_layout: default_language["page_layout"], + public: true, + default: true, + site: Alchemy::Site.default, ) else raise DefaultLanguageNotFoundError @@ -134,7 +129,7 @@ def create_default_language! def create_default_site! default_site = Alchemy::Config.get(:default_site) if default_site - Alchemy::Site.create!(name: default_site['name'], host: default_site['host']) + Alchemy::Site.create!(name: default_site["name"], host: default_site["host"]) else raise DefaultSiteNotFoundError end diff --git a/lib/alchemy/shell.rb b/lib/alchemy/shell.rb index b15b2ebbd4..d3b71e2f52 100644 --- a/lib/alchemy/shell.rb +++ b/lib/alchemy/shell.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'thor/shell/color' +require "thor/shell/color" module Alchemy # Provides methods for collecting sentences and displaying them @@ -10,7 +10,7 @@ module Shell clear: Thor::Shell::Color::CLEAR, green: Thor::Shell::Color::GREEN, red: Thor::Shell::Color::RED, - yellow: Thor::Shell::Color::YELLOW + yellow: Thor::Shell::Color::YELLOW, }.freeze def self.silence! @@ -28,11 +28,11 @@ def self.silenced? def desc(message) unless Alchemy::Shell.silenced? puts "\n#{message}" - puts "#{'-' * message.length}\n" + puts "#{"-" * message.length}\n" end end - def todo(todo, title = '') + def todo(todo, title = "") add_todo [title, todo] end @@ -64,7 +64,7 @@ def display_todos todos.each_with_index do |todo, i| title = "\n#{i + 1}. #{todo[0]}" log title, :message - puts '=' * title.length + puts "=" * title.length puts "" log todo[1], :message end diff --git a/lib/alchemy/ssl_protection.rb b/lib/alchemy/ssl_protection.rb deleted file mode 100644 index aa134e848e..0000000000 --- a/lib/alchemy/ssl_protection.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Alchemy - module SSLProtection - private - - # Enforce ssl for login and all admin modules. - # - # Default is +false+ - # - # === Usage - # - # # config/alchemy/config.yml - # ... - # require_ssl: true - # ... - # - # === Note - # - # You have to create a ssl certificate - # if you want to use the ssl protection. - # - def ssl_required? - !Rails.env.test? && Config.get(:require_ssl) - end - - # Redirects current request to https. - def enforce_ssl - redirect_to url_for(request.params.merge(protocol: 'https')) - end - end -end diff --git a/lib/alchemy/tasks/tidy.rb b/lib/alchemy/tasks/tidy.rb index ef2c662cad..e60c37547f 100644 --- a/lib/alchemy/tasks/tidy.rb +++ b/lib/alchemy/tasks/tidy.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'alchemy/shell' +require "alchemy/shell" module Alchemy class Tidy @@ -53,7 +53,7 @@ def remove_orphaned_elements end if orphaned_elements.any? log "Found #{orphaned_elements.size} orphaned elements" - destroy_orphaned_records(orphaned_elements, 'element') + destroy_orphaned_records(orphaned_elements, "element") else log "No orphaned elements found", :skip end @@ -72,7 +72,7 @@ def remove_orphaned_contents end if orphaned_contents.any? log "Found #{orphaned_contents.size} orphaned contents" - destroy_orphaned_records(orphaned_contents, 'content') + destroy_orphaned_records(orphaned_contents, "content") else log "No orphaned contents found", :skip end diff --git a/lib/alchemy/test_support/essence_shared_examples.rb b/lib/alchemy/test_support/essence_shared_examples.rb index e1a2af7c30..1d5ea09418 100644 --- a/lib/alchemy/test_support/essence_shared_examples.rb +++ b/lib/alchemy/test_support/essence_shared_examples.rb @@ -1,36 +1,36 @@ # frozen_string_literal: true -require 'shoulda-matchers' -require 'alchemy/test_support/factories/page_factory' -require 'alchemy/test_support/factories/element_factory' -require 'alchemy/test_support/factories/content_factory' +require "shoulda-matchers" +require "alchemy/test_support/factories/page_factory" +require "alchemy/test_support/factories/element_factory" +require "alchemy/test_support/factories/content_factory" RSpec.shared_examples_for "an essence" do let(:element) { Alchemy::Element.new } - let(:content) { Alchemy::Content.new(name: 'foo') } - let(:content_definition) { {'name' => 'foo'} } + let(:content) { Alchemy::Content.new(name: "foo") } + let(:content_definition) { { "name" => "foo" } } - describe 'eager loading' do + describe "eager loading" do before do 2.times { described_class.create! } end - it 'does not throw error if eager loaded' do + it "does not throw error if eager loaded" do expect { described_class.all.includes(:ingredient_association).to_a }.to_not raise_error end end - it "touches the content after update" do + it "touches the element after save" do element = FactoryBot.create(:alchemy_element) content = FactoryBot.create(:alchemy_content, element: element, essence: essence, essence_type: essence.class.name) - content.update_column(:updated_at, 3.days.ago) + element.update_column(:updated_at, 3.days.ago) content.essence.update(essence.ingredient_column.to_sym => ingredient_value) - content.reload - expect(content.updated_at).to be_within(3.seconds).of(Time.current) + element.reload + expect(element.updated_at).to be_within(3.seconds).of(Time.current) end it "should have correct partial path" do @@ -38,28 +38,28 @@ expect(essence.to_partial_path).to eq("alchemy/essences/#{underscored_essence}_view") end - describe '#definition' do + describe "#definition" do subject { essence.definition } - context 'without element' do + context "without element" do it { is_expected.to eq({}) } end - context 'with element' do + context "with element" do before do expect(essence).to receive(:element).at_least(:once).and_return(element) end - context 'but without content definitions' do + context "but without content definitions" do it { is_expected.to eq({}) } end - context 'and content definitions' do + context "and content definitions" do before do allow(essence).to receive(:content).and_return(content) end - context 'containing the content name' do + context "containing the content name" do before do expect(element).to receive(:content_definitions).at_least(:once).and_return([content_definition]) end @@ -69,7 +69,7 @@ end end - context 'not containing the content name' do + context "not containing the content name" do before do expect(element).to receive(:content_definitions).at_least(:once).and_return([]) end @@ -80,200 +80,200 @@ end end - describe '#ingredient=' do - it 'should set the value to ingredient column' do + describe "#ingredient=" do + it "should set the value to ingredient column" do essence.ingredient = ingredient_value expect(essence.ingredient).to eq ingredient_value end end - describe 'validations' do - context 'without essence definition in elements.yml' do - it 'should return an empty array' do + describe "validations" do + context "without essence definition in elements.yml" do + it "should return an empty array" do allow(essence).to receive(:definition).and_return nil expect(essence.validations).to eq([]) end end - context 'without validations defined in essence definition in elements.yml' do - it 'should return an empty array' do - allow(essence).to receive(:definition).and_return({name: 'test', type: 'EssenceText'}) + context "without validations defined in essence definition in elements.yml" do + it "should return an empty array" do + allow(essence).to receive(:definition).and_return({ name: "test", type: "EssenceText" }) expect(essence.validations).to eq([]) end end - describe 'presence' do - context 'with string given as validation type' do + describe "presence" do + context "with string given as validation type" do before do - allow(essence).to receive(:definition).and_return({'validate' => ['presence']}) + allow(essence).to receive(:definition).and_return({ "validate" => ["presence"] }) end - context 'when the ingredient column is empty' do + context "when the ingredient column is empty" do before do essence.update(essence.ingredient_column.to_sym => nil) end - it 'should not be valid' do + it "should not be valid" do expect(essence).to_not be_valid end end - context 'when the ingredient column is not nil' do + context "when the ingredient column is not nil" do before do essence.update(essence.ingredient_column.to_sym => ingredient_value) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end - context 'with hash given as validation type' do - context 'where the value is true' do + context "with hash given as validation type" do + context "where the value is true" do before do - allow(essence).to receive(:definition).and_return({'validate' => [{'presence' => true}]}) + allow(essence).to receive(:definition).and_return({ "validate" => [{ "presence" => true }] }) end - context 'when the ingredient column is empty' do + context "when the ingredient column is empty" do before do essence.update(essence.ingredient_column.to_sym => nil) end - it 'should not be valid' do + it "should not be valid" do expect(essence).to_not be_valid end end - context 'when the ingredient column is not nil' do + context "when the ingredient column is not nil" do before do essence.update(essence.ingredient_column.to_sym => ingredient_value) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end - context 'where the value is false' do + context "where the value is false" do before do - allow(essence).to receive(:definition).and_return({'validate' => [{'presence' => false}]}) + allow(essence).to receive(:definition).and_return({ "validate" => [{ "presence" => false }] }) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end end - describe 'uniqueness' do + describe "uniqueness" do before do - allow(essence).to receive(:element).and_return(FactoryBot.build_stubbed(:alchemy_element)) + allow(essence).to receive(:element).and_return(FactoryBot.create(:alchemy_element)) essence.update(essence.ingredient_column.to_sym => ingredient_value) end - context 'with string given as validation type' do + context "with string given as validation type" do before do - expect(essence).to receive(:definition).at_least(:once).and_return({'validate' => ['uniqueness']}) + expect(essence).to receive(:definition).at_least(:once).and_return({ "validate" => ["uniqueness"] }) end - context 'when a duplicate exists' do + context "when a duplicate exists" do before do allow(essence).to receive(:duplicates).and_return([essence.dup]) end - it 'should not be valid' do + it "should not be valid" do expect(essence).to_not be_valid end - context 'when validated essence is not public' do + context "when validated essence is not public" do before do expect(essence).to receive(:public?).and_return(false) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end - context 'when no duplicate exists' do + context "when no duplicate exists" do before do expect(essence).to receive(:duplicates).and_return([]) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end - context 'with hash given as validation type' do - context 'where the value is true' do + context "with hash given as validation type" do + context "where the value is true" do before do - expect(essence).to receive(:definition).at_least(:once).and_return({'validate' => [{'uniqueness' => true}]}) + expect(essence).to receive(:definition).at_least(:once).and_return({ "validate" => [{ "uniqueness" => true }] }) end - context 'when a duplicate exists' do + context "when a duplicate exists" do before do allow(essence).to receive(:duplicates).and_return([essence.dup]) end - it 'should not be valid' do + it "should not be valid" do expect(essence).to_not be_valid end - context 'when validated essence is not public' do + context "when validated essence is not public" do before do expect(essence).to receive(:public?).and_return(false) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end - context 'when no duplicate exists' do + context "when no duplicate exists" do before do expect(essence).to receive(:duplicates).and_return([]) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end - context 'where the value is false' do + context "where the value is false" do before do - allow(essence).to receive(:definition).and_return({'validate' => [{'uniqueness' => false}]}) + allow(essence).to receive(:definition).and_return({ "validate" => [{ "uniqueness" => false }] }) end - it 'should be valid' do + it "should be valid" do expect(essence).to be_valid end end end end - describe '#acts_as_essence?' do - it 'should return true' do + describe "#acts_as_essence?" do + it "should return true" do expect(essence.acts_as_essence?).to be_truthy end end end - context 'delegations' do + context "delegations" do it { should delegate_method(:restricted?).to(:page) } it { should delegate_method(:trashed?).to(:element) } - it { should delegate_method(:public?).to(:element) } + it { should delegate_method(:public?).to(:element) } end - describe 'essence relations' do - let(:page) { FactoryBot.create(:alchemy_page, :restricted) } + describe "essence relations" do + let(:page) { FactoryBot.create(:alchemy_page, :restricted) } let(:element) { FactoryBot.create(:alchemy_element) } it "registers itself on page as essence relation" do diff --git a/lib/alchemy/test_support/factories/attachment_factory.rb b/lib/alchemy/test_support/factories/attachment_factory.rb index 515712ef07..085cf90938 100644 --- a/lib/alchemy/test_support/factories/attachment_factory.rb +++ b/lib/alchemy/test_support/factories/attachment_factory.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require 'factory_bot' +require "factory_bot" FactoryBot.define do - factory :alchemy_attachment, class: 'Alchemy::Attachment' do + factory :alchemy_attachment, class: "Alchemy::Attachment" do file do - File.new(Alchemy::Engine.root.join('lib', 'alchemy', 'test_support', 'fixtures', 'image.png')) + File.new(Alchemy::Engine.root.join("lib", "alchemy", "test_support", "fixtures", "image.png")) end - name { 'image' } - file_name { 'image.png' } + name { "image" } + file_name { "image.png" } end end diff --git a/lib/alchemy/test_support/factories/content_factory.rb b/lib/alchemy/test_support/factories/content_factory.rb index 3c5ac27700..44ab890c67 100644 --- a/lib/alchemy/test_support/factories/content_factory.rb +++ b/lib/alchemy/test_support/factories/content_factory.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/element_factory' -require 'alchemy/test_support/factories/essence_file_factory' -require 'alchemy/test_support/factories/essence_picture_factory' -require 'alchemy/test_support/factories/essence_text_factory' +require "factory_bot" +require "alchemy/test_support/factories/element_factory" +require "alchemy/test_support/factories/essence_file_factory" +require "alchemy/test_support/factories/essence_picture_factory" +require "alchemy/test_support/factories/essence_text_factory" FactoryBot.define do - factory :alchemy_content, class: 'Alchemy::Content' do + factory :alchemy_content, class: "Alchemy::Content" do name { "text" } essence_type { "Alchemy::EssenceText" } association :essence, factory: :alchemy_essence_text diff --git a/lib/alchemy/test_support/factories/dummy_user_factory.rb b/lib/alchemy/test_support/factories/dummy_user_factory.rb index a06ecbfc0f..0b40c47df4 100644 --- a/lib/alchemy/test_support/factories/dummy_user_factory.rb +++ b/lib/alchemy/test_support/factories/dummy_user_factory.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true -require 'factory_bot' +require "factory_bot" FactoryBot.define do - factory :alchemy_dummy_user, class: 'DummyUser' do + factory :alchemy_dummy_user, class: "DummyUser" do sequence(:email) { |n| "john.#{n}@doe.com" } - password { 's3cr3t' } - alchemy_roles { ['member'] } + password { "s3cr3t" } + alchemy_roles { ["member"] } trait :as_admin do - alchemy_roles { ['admin'] } + alchemy_roles { ["admin"] } end trait :as_author do - alchemy_roles { ['author'] } + alchemy_roles { ["author"] } end trait :as_editor do - alchemy_roles { ['editor'] } + alchemy_roles { ["editor"] } end end end diff --git a/lib/alchemy/test_support/factories/element_factory.rb b/lib/alchemy/test_support/factories/element_factory.rb index 1ef960c0af..8140b6d3e8 100644 --- a/lib/alchemy/test_support/factories/element_factory.rb +++ b/lib/alchemy/test_support/factories/element_factory.rb @@ -1,22 +1,22 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/page_factory' +require "factory_bot" +require "alchemy/test_support/factories/page_factory" FactoryBot.define do - factory :alchemy_element, class: 'Alchemy::Element' do - name { 'article' } + factory :alchemy_element, class: "Alchemy::Element" do + name { "article" } autogenerate_contents { false } association :page, factory: :alchemy_page trait :fixed do fixed { true } - name { 'right_column' } + name { "right_column" } end trait :unique do unique { true } - name { 'header' } + name { "header" } end trait :trashed do @@ -26,12 +26,12 @@ end trait :with_nestable_elements do - name { 'slider' } + name { "slider" } end trait :nested do - association :parent_element, factory: :alchemy_element, name: 'slider' - name { 'slide' } + association :parent_element, factory: :alchemy_element, name: "slider" + name { "slide" } end trait :with_contents do diff --git a/lib/alchemy/test_support/factories/essence_file_factory.rb b/lib/alchemy/test_support/factories/essence_file_factory.rb index aaf38558f6..aaf1b425f5 100644 --- a/lib/alchemy/test_support/factories/essence_file_factory.rb +++ b/lib/alchemy/test_support/factories/essence_file_factory.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/attachment_factory' +require "factory_bot" +require "alchemy/test_support/factories/attachment_factory" FactoryBot.define do - factory :alchemy_essence_file, class: 'Alchemy::EssenceFile' do + factory :alchemy_essence_file, class: "Alchemy::EssenceFile" do attachment factory: :alchemy_attachment end end diff --git a/lib/alchemy/test_support/factories/essence_page_factory.rb b/lib/alchemy/test_support/factories/essence_page_factory.rb index ce346ad9e0..ad4b7a2494 100644 --- a/lib/alchemy/test_support/factories/essence_page_factory.rb +++ b/lib/alchemy/test_support/factories/essence_page_factory.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/page_factory' +require "factory_bot" +require "alchemy/test_support/factories/page_factory" FactoryBot.define do - factory :alchemy_essence_page, class: 'Alchemy::EssencePage' do + factory :alchemy_essence_page, class: "Alchemy::EssencePage" do page factory: :alchemy_page end end diff --git a/lib/alchemy/test_support/factories/essence_picture_factory.rb b/lib/alchemy/test_support/factories/essence_picture_factory.rb index 2337815a8b..267f2bac45 100644 --- a/lib/alchemy/test_support/factories/essence_picture_factory.rb +++ b/lib/alchemy/test_support/factories/essence_picture_factory.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/content_factory' -require 'alchemy/test_support/factories/picture_factory' +require "factory_bot" +require "alchemy/test_support/factories/content_factory" +require "alchemy/test_support/factories/picture_factory" FactoryBot.define do - factory :alchemy_essence_picture, class: 'Alchemy::EssencePicture' do + factory :alchemy_essence_picture, class: "Alchemy::EssencePicture" do picture factory: :alchemy_picture trait :with_content do diff --git a/lib/alchemy/test_support/factories/essence_text_factory.rb b/lib/alchemy/test_support/factories/essence_text_factory.rb index 6472895cc7..4982cccebb 100644 --- a/lib/alchemy/test_support/factories/essence_text_factory.rb +++ b/lib/alchemy/test_support/factories/essence_text_factory.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'factory_bot' +require "factory_bot" FactoryBot.define do - factory :alchemy_essence_text, class: 'Alchemy::EssenceText' do - body { 'This is a headline' } + factory :alchemy_essence_text, class: "Alchemy::EssenceText" do + body { "This is a headline" } end end diff --git a/lib/alchemy/test_support/factories/language_factory.rb b/lib/alchemy/test_support/factories/language_factory.rb index 3668e42851..3bbc4d47e5 100644 --- a/lib/alchemy/test_support/factories/language_factory.rb +++ b/lib/alchemy/test_support/factories/language_factory.rb @@ -1,31 +1,36 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/site_factory' +require "factory_bot" +require "alchemy/test_support/factories/site_factory" FactoryBot.define do - factory :alchemy_language, class: 'Alchemy::Language' do - name { 'Deutsch' } - code { 'de' } + factory :alchemy_language, class: "Alchemy::Language" do + name { "Your Language" } + code { ::I18n.available_locales.first.to_s } default { true } - frontpage_name { 'Intro' } - page_layout { Alchemy::Config.get(:default_language)['page_layout'] } + frontpage_name { "Intro" } + page_layout { Alchemy::Config.get(:default_language)["page_layout"] } public { true } site { Alchemy::Site.default || create(:alchemy_site, :default) } trait :klingon do - name { 'Klingon' } - code { 'kl' } - frontpage_name { 'Tuq' } + name { "Klingon" } + code { "kl" } + frontpage_name { "Tuq" } default { false } end trait :english do - name { 'English' } - code { 'en' } - frontpage_name { 'Intro' } + name { "English" } + code { "en" } + default { false } + end + + trait :german do + name { "Deutsch" } + code { "de" } default { false } end end diff --git a/lib/alchemy/test_support/factories/node_factory.rb b/lib/alchemy/test_support/factories/node_factory.rb index bedc16e046..05aea03ad3 100644 --- a/lib/alchemy/test_support/factories/node_factory.rb +++ b/lib/alchemy/test_support/factories/node_factory.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/language_factory' -require 'alchemy/test_support/factories/page_factory' +require "factory_bot" +require "alchemy/test_support/factories/language_factory" +require "alchemy/test_support/factories/page_factory" FactoryBot.define do - factory :alchemy_node, class: 'Alchemy::Node' do - site { Alchemy::Site.default || create(:alchemy_site) } + factory :alchemy_node, class: "Alchemy::Node" do language { Alchemy::Language.default || create(:alchemy_language) } - name { 'A Node' } + name { "A Node" } + menu_type { Alchemy::Node.available_menu_names.first } trait :with_page do association :page, factory: :alchemy_page @@ -16,7 +16,7 @@ end trait :with_url do - url { 'https://example.com' } + url { "https://example.com" } end end end diff --git a/lib/alchemy/test_support/factories/page_factory.rb b/lib/alchemy/test_support/factories/page_factory.rb index 03c6f0aa08..25136903e6 100644 --- a/lib/alchemy/test_support/factories/page_factory.rb +++ b/lib/alchemy/test_support/factories/page_factory.rb @@ -1,36 +1,33 @@ # frozen_string_literal: true -require 'factory_bot' -require 'alchemy/test_support/factories/language_factory' +require "factory_bot" +require "alchemy/test_support/factories/language_factory" FactoryBot.define do - factory :alchemy_page, class: 'Alchemy::Page' do - language { Alchemy::Language.default || FactoryBot.create(:alchemy_language) } + factory :alchemy_page, class: "Alchemy::Page" do + language do + @cached_attributes[:parent]&.language || + Alchemy::Language.default || + FactoryBot.create(:alchemy_language) + end sequence(:name) { |n| "A Page #{n}" } page_layout { "standard" } - parent_id do - (Alchemy::Page.find_by(language_root: true) || - FactoryBot.create(:alchemy_page, :language_root, language: language)).id + parent do + Alchemy::Page.find_by(language_root: true, language: language) || + FactoryBot.create(:alchemy_page, :language_root, language: language) end # This speeds up creating of pages dramatically. # Pass autogenerate_elements: true to generate elements autogenerate_elements { false } - trait :root do - name { 'Root' } - language { nil } - parent_id { nil } - page_layout { nil } - end - trait :language_root do - name { 'Startseite' } - page_layout { language.page_layout } + name { language&.frontpage_name || "Intro" } + page_layout { language&.page_layout || "index" } language_root { true } public_on { Time.current } - parent_id { Alchemy::Page.root.id } + parent { nil } end trait :public do @@ -38,17 +35,8 @@ public_on { Time.current } end - trait :system do - name { "Systempage" } - parent_id { Alchemy::Page.root.id } - language_root { false } - page_layout { nil } - language { nil } - end - trait :layoutpage do - name { "Footer" } - parent_id { Alchemy::Page.find_or_create_layout_root_for(Alchemy::Language.current.id).id } + parent { nil } layoutpage { true } page_layout { "footer" } end diff --git a/lib/alchemy/test_support/factories/picture_factory.rb b/lib/alchemy/test_support/factories/picture_factory.rb index bdcf6c323b..881cf83c21 100644 --- a/lib/alchemy/test_support/factories/picture_factory.rb +++ b/lib/alchemy/test_support/factories/picture_factory.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require 'factory_bot' +require "factory_bot" FactoryBot.define do - factory :alchemy_picture, class: 'Alchemy::Picture' do + factory :alchemy_picture, class: "Alchemy::Picture" do image_file do - File.new(Alchemy::Engine.root.join('lib', 'alchemy', 'test_support', 'fixtures', 'image.png')) + File.new(Alchemy::Engine.root.join("lib", "alchemy", "test_support", "fixtures", "image.png")) end - name { 'image' } - image_file_name { 'image.png' } + name { "image" } + image_file_name { "image.png" } upload_hash { Time.current.hash } end end diff --git a/lib/alchemy/test_support/factories/site_factory.rb b/lib/alchemy/test_support/factories/site_factory.rb index 834318090d..5d848fd010 100644 --- a/lib/alchemy/test_support/factories/site_factory.rb +++ b/lib/alchemy/test_support/factories/site_factory.rb @@ -1,17 +1,17 @@ # frozen_string_literal: true -require 'factory_bot' +require "factory_bot" FactoryBot.define do - factory :alchemy_site, class: 'Alchemy::Site' do - name { 'A Site' } - host { 'domain.com' } + factory :alchemy_site, class: "Alchemy::Site" do + name { "A Site" } + host { "domain.com" } trait :default do public { true } - name { Alchemy::Config.get(:default_site)['name'] } - host { Alchemy::Config.get(:default_site)['host'] } + name { Alchemy::Config.get(:default_site)["name"] } + host { Alchemy::Config.get(:default_site)["host"] } end trait :public do diff --git a/lib/alchemy/test_support/shared_contexts.rb b/lib/alchemy/test_support/shared_contexts.rb index c14a28b53e..6946f87a50 100644 --- a/lib/alchemy/test_support/shared_contexts.rb +++ b/lib/alchemy/test_support/shared_contexts.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -RSpec.shared_context 'with invalid file' do +RSpec.shared_context "with invalid file" do let(:invalid_file) do fixture_file_upload( - File.expand_path('../../../spec/fixtures/users.yml', __dir__), - 'text/x-yaml' + File.expand_path("../../../spec/fixtures/users.yml", __dir__), + "text/x-yaml", ) end before do allow(Alchemy::Attachment).to receive(:allowed_filetypes) do - ['png'] + ["png"] end end end diff --git a/lib/alchemy/test_support/shared_uploader_examples.rb b/lib/alchemy/test_support/shared_uploader_examples.rb index f35d5249c7..563d269727 100644 --- a/lib/alchemy/test_support/shared_uploader_examples.rb +++ b/lib/alchemy/test_support/shared_uploader_examples.rb @@ -2,10 +2,10 @@ RSpec.shared_examples_for "having a json uploader error message" do it "renders json response with error message" do subject - expect(response.media_type).to eq('application/json') + expect(response.media_type).to eq("application/json") expect(response.status).to eq(422) json = JSON.parse(response.body) - expect(json).to have_key('growl_message') - expect(json).to have_key('files') + expect(json).to have_key("growl_message") + expect(json).to have_key("files") end end diff --git a/lib/alchemy/tinymce.rb b/lib/alchemy/tinymce.rb index 2d52de9040..27f3862187 100644 --- a/lib/alchemy/tinymce.rb +++ b/lib/alchemy/tinymce.rb @@ -6,23 +6,23 @@ module Tinymce @@plugins = %w(alchemy_link anchor autoresize charmap code directionality fullscreen hr link lists paste tabfocus table) @@init = { - skin: 'alchemy', - width: 'auto', + skin: "alchemy", + width: "auto", resize: true, - autoresize_min_height: '105', - autoresize_max_height: '480', + autoresize_min_height: "105", + autoresize_max_height: "480", menubar: false, statusbar: true, toolbar: [ - 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen', - 'pastetext charmap hr | undo redo | alchemy_link unlink anchor | code' + "bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen", + "pastetext charmap hr | undo redo | alchemy_link unlink anchor | code", ], fix_list_elements: true, convert_urls: false, - entity_encoding: 'raw', + entity_encoding: "raw", paste_as_text: true, - element_format: 'html', - branding: false + element_format: "html", + branding: false, } class << self @@ -42,14 +42,14 @@ def custom_config_contents(page) def content_definitions_from_elements(definitions) definitions.collect do |el| - next if el['contents'].blank? + next if el["contents"].blank? - contents = el['contents'].select do |c| - c['settings'] && c['settings']['tinymce'].is_a?(Hash) + contents = el["contents"].select do |c| + c["settings"] && c["settings"]["tinymce"].is_a?(Hash) end next if contents.blank? - contents.map { |c| c.merge('element' => el['name']) } + contents.map { |c| c.merge("element" => el["name"]) } end.flatten.compact end end diff --git a/lib/alchemy/upgrader.rb b/lib/alchemy/upgrader.rb index 7d628ad41e..fa32620c51 100644 --- a/lib/alchemy/upgrader.rb +++ b/lib/alchemy/upgrader.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'alchemy/shell' +require "alchemy/shell" module Alchemy class Upgrader @@ -10,18 +10,18 @@ class Upgrader class << self def copy_new_config_file desc "Copy configuration file." - config_file = Rails.root.join('config/alchemy/config.yml') - default_config = File.join(File.dirname(__FILE__), '../../config/alchemy/config.yml') + config_file = Rails.root.join("config/alchemy/config.yml") + default_config = File.join(File.dirname(__FILE__), "../../config/alchemy/config.yml") if !File.exist? config_file log "No configuration file found. Creating it." - FileUtils.cp default_config, Rails.root.join('config/alchemy/config.yml') + FileUtils.cp default_config, Rails.root.join("config/alchemy/config.yml") elsif FileUtils.identical? default_config, config_file log "Configuration file already present.", :skip else log "Custom configuration file found." - FileUtils.cp default_config, Rails.root.join('config/alchemy/config.yml.defaults') + FileUtils.cp default_config, Rails.root.join("config/alchemy/config.yml.defaults") log "Copied new default configuration file." - todo "Check the default configuration file (./config/alchemy/config.yml.defaults) for new configuration options and insert them into your config file.", 'Configuration has changed' + todo "Check the default configuration file (./config/alchemy/config.yml.defaults) for new configuration options and insert them into your config file.", "Configuration has changed" end end end diff --git a/lib/alchemy/upgrader/five_point_zero.rb b/lib/alchemy/upgrader/five_point_zero.rb index 9cbaff7c13..338df576ea 100644 --- a/lib/alchemy/upgrader/five_point_zero.rb +++ b/lib/alchemy/upgrader/five_point_zero.rb @@ -1,15 +1,40 @@ # frozen_string_literal: true -require_relative 'tasks/harden_gutentag_migrations' +require_relative "tasks/harden_gutentag_migrations" module Alchemy class Upgrader::FivePointZero < Upgrader class << self def install_gutentag_migrations - desc 'Install Gutentag migrations' - `bundle exec rake gutentag:install:migrations` + desc "Install Gutentag migrations" + Rake::Task["gutentag:install:migrations"].invoke Alchemy::Upgrader::Tasks::HardenGutentagMigrations.new.patch_migrations - `bundle exec rake db:migrate` + Rake::Task["db:migrate"].invoke + end + + def remove_layout_roots + desc "Remove layout root pages" + layout_roots = Alchemy::Page.where(layoutpage: true).where("name LIKE 'Layoutroot for%'") + if layout_roots.size.positive? + log "Removing #{layout_roots.size} layout root pages." + layout_roots.delete_all + Alchemy::Page.where(layoutpage: true).update_all(parent_id: nil) + log "Done.", :success + else + log "No layout root pages found.", :skip + end + end + + def remove_root_page + desc "Remove root page" + root_page = Alchemy::Page.find_by(parent_id: nil, name: "Root") + if root_page + Alchemy::Page.where(parent_id: root_page.id).update_all(parent_id: nil) + root_page.delete + log "Done.", :success + else + log "Root page not found.", :skip + end end end end diff --git a/lib/alchemy/upgrader/four_point_four.rb b/lib/alchemy/upgrader/four_point_four.rb deleted file mode 100644 index 85525df828..0000000000 --- a/lib/alchemy/upgrader/four_point_four.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require_relative 'tasks/element_views_updater' - -module Alchemy - class Upgrader::FourPointFour < Upgrader - class << self - def rename_element_views - desc "Remove '_view' suffix from element views." - Alchemy::Upgrader::Tasks::ElementViewsUpdater.new.rename_element_views - end - - def update_local_variable - desc 'Update element views local variable to element name.' - Alchemy::Upgrader::Tasks::ElementViewsUpdater.new.update_local_variable - end - - def alchemy_4_4_todos - notice = <<-NOTE.strip_heredoc - - ℹ️ Element editor partials are deprecated - ----------------------------------------- - - The element editor partials are not needed anymore. They still work, but in order to - prepare the Alchemy 5 upgrade your should consider removing them now. - - In order to update check if you have any messages in your editor partials and move them - to either a `warning` or `message` in your element definition. - - Also check if you pass any values to EssenceSelects `select_values`. Move static values - to the `settings` of your content definition and either use EssencePage for referencing - pages or create a custom essence for other dynamic values. - - - ℹ️ The `_view` suffix of Element view partials is deprecated - ----------------------------------------------------------- - - The element view partials do not need the `_view` suffix anymore. Your files have been - renamed. - - The local variable in your element views has been replaced by a variable named after the - element itself. A "article" element has a "_article.html.erb" partial and therefore - a `article` local variable now. - - The former `element` variable is still present, though. - - NOTE - todo notice, 'Alchemy v4.4 TODO' - end - end - end -end diff --git a/lib/alchemy/upgrader/tasks/element_views_updater.rb b/lib/alchemy/upgrader/tasks/element_views_updater.rb index dbb0c6ab13..8ca26465f6 100644 --- a/lib/alchemy/upgrader/tasks/element_views_updater.rb +++ b/lib/alchemy/upgrader/tasks/element_views_updater.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'alchemy/upgrader' +require "alchemy/upgrader" module Alchemy::Upgrader::Tasks class ElementViewsUpdater < Thor @@ -11,14 +11,14 @@ def rename_element_views puts "-- Removing '_view' suffix from element views" Dir.glob("#{elements_view_folder}/*_view.*").each do |file| - FileUtils.mv(file, file.to_s.sub(/_view/, '')) + FileUtils.mv(file, file.to_s.sub(/_view/, "")) end end def update_local_variable puts "-- Updating element views local variable to element name" - Alchemy::Element.definitions.map { |e| e['name'] }.each do |name| + Alchemy::Element.definitions.map { |e| e["name"] }.each do |name| view = Dir.glob("#{elements_view_folder}/_#{name}.*").last gsub_file(view, /\b#{name}_view\b/, name) end @@ -28,7 +28,7 @@ def update_local_variable private def elements_view_folder - Rails.root.join('app', 'views', 'alchemy', 'elements') + Rails.root.join("app", "views", "alchemy", "elements") end end end diff --git a/lib/alchemy/upgrader/tasks/harden_gutentag_migrations.rb b/lib/alchemy/upgrader/tasks/harden_gutentag_migrations.rb index 53e8b9abd8..b8533a05d4 100644 --- a/lib/alchemy/upgrader/tasks/harden_gutentag_migrations.rb +++ b/lib/alchemy/upgrader/tasks/harden_gutentag_migrations.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'thor' +require "thor" module Alchemy::Upgrader::Tasks class HardenGutentagMigrations < Thor @@ -10,14 +10,14 @@ class HardenGutentagMigrations < Thor def patch_migrations sentinel = /def up/ - migration_file = Dir.glob('db/migrate/*_gutentag_tables.gutentag.rb').first + migration_file = Dir.glob("db/migrate/*_gutentag_tables.gutentag.rb").first if migration_file inject_into_file migration_file, "\n # inserted by Alchemy CMS upgrader\n return if table_exists?(:gutentag_taggings)\n", { after: sentinel, verbose: true } end - migration_file = Dir.glob('db/migrate/*_gutentag_cache_counter.gutentag.rb').first + migration_file = Dir.glob("db/migrate/*_gutentag_cache_counter.gutentag.rb").first if migration_file inject_into_file migration_file, "\n # inserted by Alchemy CMS upgrader\n return if column_exists?(:gutentag_tags, :taggings_count)\n", diff --git a/lib/alchemy/version.rb b/lib/alchemy/version.rb index a8f0f51f27..1f4eeafe4a 100644 --- a/lib/alchemy/version.rb +++ b/lib/alchemy/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Alchemy - VERSION = "5.0.0.a" + VERSION = "5.0.0.beta2" def self.version VERSION diff --git a/lib/alchemy_cms.rb b/lib/alchemy_cms.rb index 73df9914f3..5cd6e4712c 100644 --- a/lib/alchemy_cms.rb +++ b/lib/alchemy_cms.rb @@ -1,62 +1,63 @@ # frozen_string_literal: true # Instantiate the global Alchemy namespace module Alchemy - Alchemy::YAML_WHITELIST_CLASSES = %w(Symbol Date Regexp) + YAML_WHITELIST_CLASSES = %w(Symbol Date Regexp) end # Require globally used external libraries -require 'acts_as_list' -require 'action_view/dependency_tracker' -require 'active_model_serializers' -require 'awesome_nested_set' -require 'cancan' -require 'dragonfly' -require 'gutentag' -require 'handlebars_assets' -require 'jquery-rails' -require 'jquery-ui-rails' -require 'kaminari' -require 'non-stupid-digest-assets' -require 'ransack' -require 'request_store' -require 'responders' -require 'sassc-rails' -require 'simple_form' -require 'select2-rails' -require 'turbolinks' -require 'userstamp' +require "acts_as_list" +require "action_view/dependency_tracker" +require "active_model_serializers" +require "awesome_nested_set" +require "cancan" +require "dragonfly" +require "gutentag" +require "handlebars_assets" +require "jquery-rails" +require "jquery-ui-rails" +require "kaminari" +require "non-stupid-digest-assets" +require "ransack" +require "request_store" +require "responders" +require "sassc-rails" +require "simple_form" +require "select2-rails" +require "turbolinks" +require "userstamp" +require "webpacker" # Require globally used Alchemy mixins -require_relative 'alchemy/ability_helper' -require_relative 'alchemy/admin/locale' -require_relative 'alchemy/auth_accessors' -require_relative 'alchemy/cache_digests/template_tracker' -require_relative 'alchemy/config' -require_relative 'alchemy/configuration_methods' -require_relative 'alchemy/controller_actions' -require_relative 'alchemy/deprecation' -require_relative 'alchemy/elements_finder' -require_relative 'alchemy/errors' -require_relative 'alchemy/essence' -require_relative 'alchemy/filetypes' -require_relative 'alchemy/forms/builder' -require_relative 'alchemy/hints' -require_relative 'alchemy/i18n' -require_relative 'alchemy/logger' -require_relative 'alchemy/modules' -require_relative 'alchemy/name_conversions' -require_relative 'alchemy/on_page_layout' -require_relative 'alchemy/on_page_layout/callbacks_runner' -require_relative 'alchemy/page_layout' -require_relative 'alchemy/paths' -require_relative 'alchemy/permissions' -require_relative 'alchemy/ssl_protection' -require_relative 'alchemy/resource' -require_relative 'alchemy/tinymce' -require_relative 'alchemy/taggable' +require_relative "alchemy/ability_helper" +require_relative "alchemy/admin/locale" +require_relative "alchemy/admin/preview_url" +require_relative "alchemy/auth_accessors" +require_relative "alchemy/cache_digests/template_tracker" +require_relative "alchemy/config" +require_relative "alchemy/configuration_methods" +require_relative "alchemy/controller_actions" +require_relative "alchemy/deprecation" +require_relative "alchemy/elements_finder" +require_relative "alchemy/errors" +require_relative "alchemy/essence" +require_relative "alchemy/filetypes" +require_relative "alchemy/forms/builder" +require_relative "alchemy/hints" +require_relative "alchemy/i18n" +require_relative "alchemy/logger" +require_relative "alchemy/modules" +require_relative "alchemy/name_conversions" +require_relative "alchemy/on_page_layout" +require_relative "alchemy/on_page_layout/callbacks_runner" +require_relative "alchemy/page_layout" +require_relative "alchemy/paths" +require_relative "alchemy/permissions" +require_relative "alchemy/resource" +require_relative "alchemy/tinymce" +require_relative "alchemy/taggable" # Require hacks -require_relative 'kaminari/scoped_pagination_url_helper' +require_relative "kaminari/scoped_pagination_url_helper" # Finally require Alchemy itself -require_relative 'alchemy/engine' +require_relative "alchemy/engine" diff --git a/lib/rails/generators/alchemy/base.rb b/lib/generators/alchemy/base.rb similarity index 83% rename from lib/rails/generators/alchemy/base.rb rename to lib/generators/alchemy/base.rb index 43c3c1811a..0074ac243e 100644 --- a/lib/rails/generators/alchemy/base.rb +++ b/lib/generators/alchemy/base.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -require 'rails' +require "rails" module Alchemy module Generators class Base < ::Rails::Generators::Base - class_option :template_engine, type: :string, aliases: '-e', desc: 'Template engine for the views. Available options are "erb", "haml", and "slim".' + class_option :template_engine, type: :string, aliases: "-e", desc: 'Template engine for the views. Available options are "erb", "haml", and "slim".' private def conditional_template(source, destination) - files = Dir.glob(destination.gsub(/\.([a-z]+)$/, '*')) + files = Dir.glob(destination.gsub(/\.([a-z]+)$/, "*")) if files.any? ext = File.extname(files.first)[1..-1] @@ -29,7 +29,7 @@ def template_engine # Rails is clever enough to default this to whatever template # engine is configured through its generator configuration, # but we'll default it to erb anyway, just in case. - options[:template_engine] || 'erb' + options[:template_engine] || "erb" end def load_alchemy_yaml(name) diff --git a/lib/rails/generators/alchemy/elements/elements_generator.rb b/lib/generators/alchemy/elements/elements_generator.rb similarity index 56% rename from lib/rails/generators/alchemy/elements/elements_generator.rb rename to lib/generators/alchemy/elements/elements_generator.rb index e3c182dfda..966dc85af6 100644 --- a/lib/rails/generators/alchemy/elements/elements_generator.rb +++ b/lib/generators/alchemy/elements/elements_generator.rb @@ -1,25 +1,20 @@ # frozen_string_literal: true -require_relative '../base' +require_relative "../base" module Alchemy module Generators class ElementsGenerator < Base desc "This generator generates your elements view partials." - source_root File.expand_path('templates', __dir__) + source_root File.expand_path("templates", __dir__) def create_partials - @elements = load_alchemy_yaml('elements.yml') + @elements = load_alchemy_yaml("elements.yml") return unless @elements @elements.each do |element| @element = element @contents = element["contents"] || [] - if element["name"] =~ /\A[a-z0-9_-]+\z/ - @element_name = element["name"].underscore - else - raise "Element name '#{element['name']}' has wrong format. Only lowercase and non whitespace characters allowed." - end - + @element_name = element_name(element) conditional_template "view.html.#{template_engine}", "#{elements_dir}/_#{@element_name}.html.#{template_engine}" end end @@ -29,6 +24,14 @@ def create_partials def elements_dir @_elements_dir ||= "app/views/alchemy/elements" end + + def element_name(element) + if element["name"] =~ Alchemy::Element::NAME_REGEXP + element["name"].underscore + else + raise "Element name '#{element["name"]}' has wrong format. Only lowercase and non whitespace characters allowed." + end + end end end end diff --git a/lib/rails/generators/alchemy/elements/templates/view.html.erb b/lib/generators/alchemy/elements/templates/view.html.erb similarity index 100% rename from lib/rails/generators/alchemy/elements/templates/view.html.erb rename to lib/generators/alchemy/elements/templates/view.html.erb diff --git a/lib/rails/generators/alchemy/elements/templates/view.html.haml b/lib/generators/alchemy/elements/templates/view.html.haml similarity index 100% rename from lib/rails/generators/alchemy/elements/templates/view.html.haml rename to lib/generators/alchemy/elements/templates/view.html.haml diff --git a/lib/rails/generators/alchemy/elements/templates/view.html.slim b/lib/generators/alchemy/elements/templates/view.html.slim similarity index 100% rename from lib/rails/generators/alchemy/elements/templates/view.html.slim rename to lib/generators/alchemy/elements/templates/view.html.slim diff --git a/lib/rails/generators/alchemy/essence/essence_generator.rb b/lib/generators/alchemy/essence/essence_generator.rb similarity index 93% rename from lib/rails/generators/alchemy/essence/essence_generator.rb rename to lib/generators/alchemy/essence/essence_generator.rb index 72688d8a4d..8946856ba8 100644 --- a/lib/rails/generators/alchemy/essence/essence_generator.rb +++ b/lib/generators/alchemy/essence/essence_generator.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -require 'rails' +require "rails" module Alchemy module Generators class EssenceGenerator < ::Rails::Generators::Base desc "This generator generates an Alchemy essence for you." argument :essence_name, banner: "YourEssenceName" - source_root File.expand_path('templates', __dir__) + source_root File.expand_path("templates", __dir__) def init @essence_name = essence_name.underscore - @essence_view_path = 'app/views/alchemy/essences' + @essence_view_path = "app/views/alchemy/essences" end def create_model diff --git a/lib/rails/generators/alchemy/essence/templates/editor.html.erb b/lib/generators/alchemy/essence/templates/editor.html.erb similarity index 100% rename from lib/rails/generators/alchemy/essence/templates/editor.html.erb rename to lib/generators/alchemy/essence/templates/editor.html.erb diff --git a/lib/rails/generators/alchemy/essence/templates/view.html.erb b/lib/generators/alchemy/essence/templates/view.html.erb similarity index 100% rename from lib/rails/generators/alchemy/essence/templates/view.html.erb rename to lib/generators/alchemy/essence/templates/view.html.erb diff --git a/lib/rails/generators/alchemy/install/files/_article.html.erb b/lib/generators/alchemy/install/files/_article.html.erb similarity index 100% rename from lib/rails/generators/alchemy/install/files/_article.html.erb rename to lib/generators/alchemy/install/files/_article.html.erb diff --git a/lib/rails/generators/alchemy/install/files/_standard.html.erb b/lib/generators/alchemy/install/files/_standard.html.erb similarity index 100% rename from lib/rails/generators/alchemy/install/files/_standard.html.erb rename to lib/generators/alchemy/install/files/_standard.html.erb diff --git a/lib/rails/generators/alchemy/install/files/alchemy.en.yml b/lib/generators/alchemy/install/files/alchemy.en.yml similarity index 100% rename from lib/rails/generators/alchemy/install/files/alchemy.en.yml rename to lib/generators/alchemy/install/files/alchemy.en.yml diff --git a/lib/generators/alchemy/install/files/alchemy_admin.js b/lib/generators/alchemy/install/files/alchemy_admin.js new file mode 100644 index 0000000000..4691676a39 --- /dev/null +++ b/lib/generators/alchemy/install/files/alchemy_admin.js @@ -0,0 +1 @@ +import "@alchemy_cms/admin" diff --git a/lib/rails/generators/alchemy/install/files/all.css b/lib/generators/alchemy/install/files/all.css similarity index 100% rename from lib/rails/generators/alchemy/install/files/all.css rename to lib/generators/alchemy/install/files/all.css diff --git a/lib/rails/generators/alchemy/install/files/all.js b/lib/generators/alchemy/install/files/all.js similarity index 100% rename from lib/rails/generators/alchemy/install/files/all.js rename to lib/generators/alchemy/install/files/all.js diff --git a/lib/rails/generators/alchemy/install/files/application.html.erb b/lib/generators/alchemy/install/files/application.html.erb similarity index 100% rename from lib/rails/generators/alchemy/install/files/application.html.erb rename to lib/generators/alchemy/install/files/application.html.erb diff --git a/lib/rails/generators/alchemy/install/files/article.scss b/lib/generators/alchemy/install/files/article.scss similarity index 100% rename from lib/rails/generators/alchemy/install/files/article.scss rename to lib/generators/alchemy/install/files/article.scss diff --git a/lib/generators/alchemy/install/install_generator.rb b/lib/generators/alchemy/install/install_generator.rb new file mode 100644 index 0000000000..53c5db14d8 --- /dev/null +++ b/lib/generators/alchemy/install/install_generator.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true +require "rails/generators" + +module Alchemy + module Generators + class InstallGenerator < ::Rails::Generators::Base + desc "Installs Alchemy into your App." + + class_option :skip_demo_files, + type: :boolean, + default: false, + desc: "Skip creation of demo element, page and application layout." + + class_option :skip_webpacker_installer, + type: :boolean, + default: false, + desc: "Skip running the webpacker installer." + + source_root File.expand_path("files", __dir__) + + def copy_config + copy_file "#{gem_config_path}/config.yml", app_config_path.join("alchemy", "config.yml") + end + + def copy_yml_files + %w(elements page_layouts menus).each do |file| + template "#{__dir__}/templates/#{file}.yml.tt", app_config_path.join("alchemy", "#{file}.yml") + end + end + + def install_assets + copy_file "all.js", app_vendor_assets_path.join("javascripts", "alchemy", "admin", "all.js") + copy_file "all.css", app_vendor_assets_path.join("stylesheets", "alchemy", "admin", "all.css") + end + + def copy_demo_views + return if options[:skip_demo_files] + + copy_file "application.html.erb", app_views_path.join("layouts", "application.html.erb") + copy_file "article.scss", app_assets_path.join("stylesheets", "alchemy", "elements", "article.scss") + + stylesheet_require = " *= require_tree ./alchemy/elements\n" + if File.exist?(app_assets_path.join("stylesheets", "application.css")) + insert_into_file app_assets_path.join("stylesheets", "application.css"), stylesheet_require, + before: " */" + else + create_file app_assets_path.join("stylesheets", "application.css"), "/*\n#{stylesheet_require} */\n" + end + + copy_file "_article.html.erb", app_views_path.join("alchemy", "elements", "_article.html.erb") + copy_file "_standard.html.erb", app_views_path.join("alchemy", "page_layouts", "_standard.html.erb") + copy_file "alchemy.en.yml", app_config_path.join("locales", "alchemy.en.yml") + end + + def copy_dragonfly_config + template "#{__dir__}/templates/dragonfly.rb.tt", app_config_path.join("initializers", "dragonfly.rb") + end + + def install_gutentag_migrations + rake "gutentag:install:migrations" + end + + def run_webpacker_installer + unless options[:skip_webpacker_installer] + # Webpacker does not create a package.json, but we need one + unless File.exist? app_root.join("package.json") + in_root { run "echo '{}' > package.json" } + end + rake("webpacker:install", abort_on_failure: true) + end + end + + def add_npm_package + run "yarn add @alchemy_cms/admin" + end + + def copy_alchemy_entry_point + webpack_config = YAML.load_file(app_root.join("config", "webpacker.yml"))[Rails.env] + copy_file "alchemy_admin.js", + app_root.join(webpack_config["source_path"], webpack_config["source_entry_path"], "alchemy/admin.js") + end + + private + + def gem_config_path + @_config_path ||= File.expand_path("../../../../config/alchemy", __dir__) + end + + def app_config_path + @_app_config_path ||= app_root.join("config") + end + + def app_views_path + @_app_views_path ||= app_root.join("app", "views") + end + + def app_assets_path + @_app_assets_path ||= app_root.join("app", "assets") + end + + def app_vendor_assets_path + @_app_vendor_assets_path ||= app_root.join("vendor", "assets") + end + + def app_root + @_app_root ||= Rails.root + end + end + end +end diff --git a/lib/rails/generators/alchemy/install/templates/dragonfly.rb.tt b/lib/generators/alchemy/install/templates/dragonfly.rb.tt similarity index 100% rename from lib/rails/generators/alchemy/install/templates/dragonfly.rb.tt rename to lib/generators/alchemy/install/templates/dragonfly.rb.tt diff --git a/lib/rails/generators/alchemy/install/templates/elements.yml.tt b/lib/generators/alchemy/install/templates/elements.yml.tt similarity index 100% rename from lib/rails/generators/alchemy/install/templates/elements.yml.tt rename to lib/generators/alchemy/install/templates/elements.yml.tt diff --git a/lib/rails/generators/alchemy/install/templates/menus.yml.tt b/lib/generators/alchemy/install/templates/menus.yml.tt similarity index 100% rename from lib/rails/generators/alchemy/install/templates/menus.yml.tt rename to lib/generators/alchemy/install/templates/menus.yml.tt diff --git a/lib/rails/generators/alchemy/install/templates/page_layouts.yml.tt b/lib/generators/alchemy/install/templates/page_layouts.yml.tt similarity index 100% rename from lib/rails/generators/alchemy/install/templates/page_layouts.yml.tt rename to lib/generators/alchemy/install/templates/page_layouts.yml.tt diff --git a/lib/rails/generators/alchemy/menus/menus_generator.rb b/lib/generators/alchemy/menus/menus_generator.rb similarity index 88% rename from lib/rails/generators/alchemy/menus/menus_generator.rb rename to lib/generators/alchemy/menus/menus_generator.rb index 9545a1c138..3e0bfca40d 100644 --- a/lib/rails/generators/alchemy/menus/menus_generator.rb +++ b/lib/generators/alchemy/menus/menus_generator.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require_relative '../base' +require_relative "../base" module Alchemy module Generators class MenusGenerator < Base desc "This generator generates Alchemy menu partials." - source_root File.expand_path('templates', __dir__) + source_root File.expand_path("templates", __dir__) def create_partials menus = Alchemy::Node.available_menu_names diff --git a/lib/rails/generators/alchemy/menus/templates/node.html.erb b/lib/generators/alchemy/menus/templates/node.html.erb similarity index 74% rename from lib/rails/generators/alchemy/menus/templates/node.html.erb rename to lib/generators/alchemy/menus/templates/node.html.erb index ed6099437e..cd92392c61 100644 --- a/lib/rails/generators/alchemy/menus/templates/node.html.erb +++ b/lib/generators/alchemy/menus/templates/node.html.erb @@ -9,10 +9,7 @@ rel: node.nofollow? ? 'nofollow' : nil %> <%% if node.children.any? %> <%% end %> <%% end %> diff --git a/lib/rails/generators/alchemy/menus/templates/node.html.haml b/lib/generators/alchemy/menus/templates/node.html.haml similarity index 71% rename from lib/rails/generators/alchemy/menus/templates/node.html.haml rename to lib/generators/alchemy/menus/templates/node.html.haml index 555998f0cc..f186012dad 100644 --- a/lib/rails/generators/alchemy/menus/templates/node.html.haml +++ b/lib/generators/alchemy/menus/templates/node.html.haml @@ -10,7 +10,4 @@ rel: node.nofollow? ? 'nofollow' : nil - if node.children.any? %ul.dropdown-menu - = render partial: options[:node_partial_name], - collection: node.children.includes(:page, :children), - locals: { options: options }, - as: 'node' + = render node.children.includes(:page, :children), as: 'node' diff --git a/lib/rails/generators/alchemy/menus/templates/node.html.slim b/lib/generators/alchemy/menus/templates/node.html.slim similarity index 70% rename from lib/rails/generators/alchemy/menus/templates/node.html.slim rename to lib/generators/alchemy/menus/templates/node.html.slim index f7d6d3e166..b07bb2355f 100644 --- a/lib/rails/generators/alchemy/menus/templates/node.html.slim +++ b/lib/generators/alchemy/menus/templates/node.html.slim @@ -10,7 +10,4 @@ rel: node.nofollow? ? 'nofollow' : nil - if node.children.any? ul.dropdown-menu - = render partial: options[:node_partial_name], - collection: node.children.includes(:page, :children), - locals: { options: options }, - as: 'node' + = render node.children.includes(:page, :children), as: 'node' diff --git a/lib/rails/generators/alchemy/menus/templates/wrapper.html.erb b/lib/generators/alchemy/menus/templates/wrapper.html.erb similarity index 76% rename from lib/rails/generators/alchemy/menus/templates/wrapper.html.erb rename to lib/generators/alchemy/menus/templates/wrapper.html.erb index 5480e7055f..5d8767acba 100644 --- a/lib/rails/generators/alchemy/menus/templates/wrapper.html.erb +++ b/lib/generators/alchemy/menus/templates/wrapper.html.erb @@ -1,6 +1,6 @@ <%% cache menu do %>