diff --git a/.codeclimate.yml b/.codeclimate.yml index 8bcc7e88195..443cb8c4414 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -10,7 +10,7 @@ engines: enabled: true exclude_paths: - "lib/alchemy/permissions.rb" - - "lib/tasks/alchemy/tidy.rake" + - "lib/alchemy/tasks/tidy.rb" - "lib/tasks/alchemy/upgrade.rake" config: languages: @@ -32,3 +32,4 @@ exclude_paths: - "spec/" - "lib/alchemy/upgrader/" - "lib/rails/" +- "vendor/assets/" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..5a4bb754215 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +patreon: alchemy_cms +open_collective: alchemy_cms diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 00000000000..b589aa7ccf3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +### Steps to reproduce + +How can we make this happen + +### Expected behavior + +Tell us what should happen + +### Actual behavior + +Tell us what happens instead + +### System configuration + +- Alchemy Version: +- Rails Version: diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 00000000000..5384295126e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..dc48c7b9efc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## What is this pull request for? + +Describe you pull request here... + +Closes # (Remove if not related to any issue) + +### Notable changes (remove if none) + +Explain any changes (maybe breaking?) that have been made. + +### Screenshots + +Remove if no visual changes have been made. + +## Checklist +- [ ] I have followed [Pull Request guidelines](https://github.com/AlchemyCMS/alchemy_cms/blob/master/CONTRIBUTING.md) +- [ ] I have added a detailed description into each commit message +- [ ] I have added tests to cover this change diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 00000000000..823c0d1b191 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,13 @@ +name: Greetings + +on: [pull_request, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: 'Hey. Thanks for reporting this issue and welcome to AlchemyCMS. The maintainer have been notified. Please be patient while waiting for an answer. Open Source is done by volunteers, so give them some time to react. Meanwhile please think about sending a PR that fixes this issue. It is way more likely that it will be accepted than this bug fixed for you. Remember OpenSource is done by all of us. Again, thanks for reporting.' + pr-message: 'Hey. Thanks for sending this PR. It is very much appreciated. Please be patient and give the maintainers some time to review your work. Meanwhile make sure that the PR meets our [contributing guidelines](https://github.com/AlchemyCMS/alchemy_cms/blob/master/CONTRIBUTING.md). This mostly means [great commit messages](https://chris.beams.io/posts/git-commit/) and adding tests. Thanks again for your contribution. Your help makes Alchemy better.' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000000..262e51d5a9e --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - 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 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 c80a4f72bd2..d22182308f2 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 @@ -20,3 +33,11 @@ spec/dummy/public/assets .rbenv-version .ruby-gemset .ruby-version +.env +.rspec +node_modules +yarn-error.log +yarn-debug.log* +.yarn-integrity +yarn.lock +/spec/dummy/public/pictures diff --git a/.hound.yml b/.hound.yml index dc6c8238f2d..c950b073a9a 100644 --- a/.hound.yml +++ b/.hound.yml @@ -1,5 +1,6 @@ -ruby: +rubocop: config_file: .rubocop.yml + version: 0.80.0 scss: enabled: false coffeescript: diff --git a/.localeapp/config.rb b/.localeapp/config.rb new file mode 100644 index 00000000000..e5be26e7e27 --- /dev/null +++ b/.localeapp/config.rb @@ -0,0 +1,8 @@ +require 'dotenv/load' + +Localeapp.configure do |config| + config.api_key = ENV['LOCALEAPP_API_KEY'] + config.translation_data_directory = 'config/locales' + config.synchronization_data_file = '.localeapp/log.yml' + config.daemon_pid_file = '.localeapp/localeapp.pid' +end diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000000..348cb46f7bc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "trailingComma": "none", + "vueIndentScriptAndStyle": true, + "arrowParens": "always" +} diff --git a/.rspec b/.rspec deleted file mode 100644 index 5052887a0fb..00000000000 --- a/.rspec +++ /dev/null @@ -1 +0,0 @@ ---color \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml index 0844065d735..05dd2cc3242 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,24 +1,29 @@ # Relaxed.Ruby.Style AllCops: + TargetRubyVersion: 2.4 Exclude: - 'bin/rspec' - 'vendor/**/*' - - 'spec/dummy/**/*' - TargetRubyVersion: 2.1 + - 'spec/dummy/db/**/*' + - 'spec/dummy/config/**/*' + - 'alchemy_cms.gemspec' + - 'Rakefile' + - 'node_modules/**/*' -# Sometimes I believe this reads better -# This also causes spacing issues on multi-line fixes -Style/BracesAroundHashParameters: +# Really, rubocop? +Bundler/OrderedGems: + Enabled: false + +Style/EmptyLiteral: Enabled: false # We use class vars and will have to continue doing so for compatability Style/ClassVars: Enabled: false -# We need these names for backwards compatability -Style/PredicateName: - Enabled: false +Style/FloatDivision: + EnforcedStyle: left_coerce # This has been used for customization Style/MutableConstant: @@ -30,46 +35,96 @@ Style/ClassAndModuleChildren: Style/GuardClause: Enabled: false +# We support older versions of Ruby (2.1) that do not have the %i syntax +Style/SymbolArray: + Enabled: false + +Style/SymbolProc: + Exclude: + - 'lib/alchemy/permissions.rb' + Style/WordArray: Enabled: false Style/ConditionalAssignment: Enabled: false -Performance/Count: +Style/MixinUsage: + Exclude: + - spec/**/* + +Layout/ArgumentAlignment: + Enabled: false + +Layout/HashAlignment: Enabled: false -Lint/EndAlignment: +Layout/ParameterAlignment: Enabled: false -Style/ElseAlignment: +Layout/ClosingParenthesisIndentation: Enabled: false -Style/IndentationWidth: +Layout/DotPosition: Enabled: false + StyleGuide: http://relaxed.ruby.style/#styledotposition -Style/AlignParameters: +Layout/ElseAlignment: Enabled: false -Style/ClosingParenthesisIndentation: +Layout/EmptyLineAfterMagicComment: Enabled: false -Style/CollectionMethods: +Layout/EmptyLinesAroundAccessModifier: + Exclude: + - lib/alchemy/test_support/factories/*.rb + +Layout/FirstArrayElementIndentation: + Enabled: false + +Layout/FirstHashElementIndentation: Enabled: false -Style/MultilineMethodCallIndentation: +Layout/IndentationWidth: Enabled: false -Style/IndentArray: +Layout/LineLength: Enabled: false -Style/IndentHash: +Layout/MultilineHashBraceLayout: Enabled: false -Style/AlignHash: +Layout/MultilineMethodCallBraceLayout: Enabled: false -Style/AccessorMethodName: +Layout/MultilineMethodCallIndentation: + Enabled: false + +Layout/MultilineOperationIndentation: + EnforcedStyle: indented + +Layout/SpaceBeforeBlockBraces: + Enabled: false + StyleGuide: http://relaxed.ruby.style/#stylespacebeforeblockbraces + +Layout/SpaceInsideHashLiteralBraces: + Enabled: false + +Layout/SpaceInsideParens: + Enabled: false + StyleGuide: http://relaxed.ruby.style/#stylespaceinsideparens + +Layout/EndAlignment: + Enabled: false + +Layout/RescueEnsureAlignment: + Enabled: false + +Lint/SuppressedException: + Exclude: + - 'config/initializers/mini_profiler.rb' + +Style/CollectionMethods: Enabled: false Style/Alias: @@ -88,14 +143,13 @@ Style/Documentation: Enabled: false StyleGuide: http://relaxed.ruby.style/#styledocumentation -Style/DotPosition: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#styledotposition - Style/DoubleNegation: Enabled: false StyleGuide: http://relaxed.ruby.style/#styledoublenegation +Style/EmptyMethod: + Enabled: false + Style/EndBlock: Enabled: false StyleGuide: http://relaxed.ruby.style/#styleendblock @@ -128,6 +182,9 @@ Style/NegatedWhile: Enabled: false StyleGuide: http://relaxed.ruby.style/#stylenegatedwhile +Style/NumericLiteralPrefix: + Enabled: false + Style/ParallelAssignment: Enabled: false StyleGuide: http://relaxed.ruby.style/#styleparallelassignment @@ -159,29 +216,40 @@ Style/SingleLineMethods: Enabled: false StyleGuide: http://relaxed.ruby.style/#stylesinglelinemethods -Style/SpaceBeforeBlockBraces: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylespacebeforeblockbraces - -Style/SpaceInsideHashLiteralBraces: - Enabled: false - -Style/SpaceInsideParens: - Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylespaceinsideparens - Style/SpecialGlobalVars: Enabled: false 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 StyleGuide: http://relaxed.ruby.style/#stylewhileuntilmodifier +# We use a lot of +# +# expect { +# something +# }.to { happen } +# +# syntax in the specs files. +Lint/AmbiguousBlockAssociation: + Exclude: + - 'spec/**/*' + Lint/AmbiguousRegexpLiteral: Enabled: false StyleGuide: http://relaxed.ruby.style/#lintambiguousregexpliteral @@ -193,7 +261,7 @@ Lint/AssignmentInCondition: # We use eval to get the correct url proxy object of engines in: # - app/helpers/alchemy/admin/navigation_helper.rb:139 # - lib/alchemy/resources_helper.rb:24 -Lint/Eval: +Security/Eval: Exclude: - app/helpers/alchemy/admin/navigation_helper.rb - lib/alchemy/resources_helper.rb @@ -201,6 +269,9 @@ Lint/Eval: Metrics/AbcSize: Enabled: false +Metrics/BlockLength: + Enabled: false + Metrics/BlockNesting: Enabled: false @@ -213,9 +284,6 @@ Metrics/ModuleLength: Metrics/CyclomaticComplexity: Enabled: false -Metrics/LineLength: - Enabled: false - Metrics/MethodLength: Enabled: false @@ -224,3 +292,20 @@ Metrics/ParameterLists: Metrics/PerceivedComplexity: Enabled: false + +Naming/AccessorMethodName: + Enabled: false + +Naming/HeredocDelimiterNaming: + Enabled: false + +# This cop is great but has no config option to support the style `@_method_name` we are using. +Naming/MemoizedInstanceVariableName: + Enabled: false + +# We need these names for backwards compatability +Naming/PredicateName: + Enabled: false + +Naming/VariableNumber: + Enabled: false diff --git a/.teatro.yml b/.teatro.yml deleted file mode 100644 index c5178988889..00000000000 --- a/.teatro.yml +++ /dev/null @@ -1,8 +0,0 @@ -project: - after: - - apt-get -y install imagemagick -stage: - before: cd spec/dummy - database: bin/rake db:setup RAILS_ENV=production - assets: bin/rake assets:precompile RAILS_ENV=production - run: bin/rails server -e production diff --git a/.travis.yml b/.travis.yml index 459c54822f0..6cf91d21ccc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,48 @@ language: ruby -sudo: false +os: linux +dist: bionic +services: + - mysql +addons: + postgresql: "10" cache: bundler: true + yarn: true directories: - - "travis_phantomjs" + - /home/travis/.webdrivers/ rvm: -- 2.1 -- 2.2 -- 2.3.0 -branches: - only: - - master + - 2.6.6 + - 2.7.1 + +before_script: + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build + - nvm use 12 + +script: bundle exec rake + +after_script: + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT + +jobs: + include: + - language: node_js + node_js: 12 + install: yarn install + script: yarn jest --coverage + env: NODE_ENV=test + before_script: skip + after_script: skip -before_install: - - "phantomjs --version" - - "export PATH=$PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH" - - "phantomjs --version" - - "if [ $(phantomjs --version) != '2.1.1' ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi" - - "if [ $(phantomjs --version) != '2.1.1' ]; then wget https://assets.membergetmember.co/software/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2; fi" - - "if [ $(phantomjs --version) != '2.1.1' ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi" - - "phantomjs --version" - - gem install bundler -before_script: bundle exec rake alchemy:spec:prepare -script: bundle exec rspec -after_success: bundle exec codeclimate-test-reporter env: -- DB=mysql -- DB=postgresql + global: + CC_TEST_REPORTER_ID=bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 + jobs: + - DB=mysql RAILS_VERSION=5.2 + - DB=mysql RAILS_VERSION=6.0 + - DB=postgresql RAILS_VERSION=5.2 + - DB=postgresql RAILS_VERSION=6.0 notifications: slack: secure: QzOFw1Ph69pzwWBFgtIVkOnjbcRxB9HPRQ+RYjK+2tg+fsbiTJ+wYgHcZL49tPYcLAls4kymkFWzWBF3PCAXJMfKgUCqXzdQ2FuJC/JoVRTLll4wDnZFPG33jsm5tVznmycZ3ma4+ZWfJQ+C+elEBOba6v1kG9eGIy6sH2cvXfE= diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bb7ce00dc..b19ddce4f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,193 +1,711 @@ -# Change Log +## 5.0.1 (2020-09-29) + +- Better image alt text support [#1940](https://github.com/AlchemyCMS/alchemy_cms/pull/1940) ([tvdeyen](https://github.com/tvdeyen)) + +## 5.0.0 (2020-07-17) + +- Do not convert JPEG images into JPG [#1904](https://github.com/AlchemyCMS/alchemy_cms/pull/1904) ([tvdeyen](https://github.com/tvdeyen)) +- Do not enable image cropper if file is missing [#1903](https://github.com/AlchemyCMS/alchemy_cms/pull/1903) ([tvdeyen](https://github.com/tvdeyen)) +- Always show original image as zoomed image [#1902](https://github.com/AlchemyCMS/alchemy_cms/pull/1902) ([tvdeyen](https://github.com/tvdeyen)) +- Rename Attachment#urlname into slug [#1848](https://github.com/AlchemyCMS/alchemy_cms/pull/1848) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate toolbar helper ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate redirect_to_public_child ([tvdeyen](https://github.com/tvdeyen)) +- Add --auto-accept option to installer ([tvdeyen](https://github.com/tvdeyen)) +- Move all installer code into install generator ([tvdeyen](https://github.com/tvdeyen)) +- 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)) +- Remove references to nonexistent "scaffold" generator [#1755](https://github.com/AlchemyCMS/alchemy_cms/pull/1755) ([mamhoff](https://github.com/mamhoff)) +- Remove Tasks::Helper module [#1754](https://github.com/AlchemyCMS/alchemy_cms/pull/1754) ([mamhoff](https://github.com/mamhoff)) +- Update rubocop [#1753](https://github.com/AlchemyCMS/alchemy_cms/pull/1753) ([tvdeyen](https://github.com/tvdeyen)) +- chores: use same _old_ Rubo:cop: version as Hound [#1752](https://github.com/AlchemyCMS/alchemy_cms/pull/1752) ([alexanderadam](https://github.com/alexanderadam)) +- Fix date comparison in resource feature spec [#1750](https://github.com/AlchemyCMS/alchemy_cms/pull/1750) ([tvdeyen](https://github.com/tvdeyen)) +- Fail spec prepare task if sub command fails [#1749](https://github.com/AlchemyCMS/alchemy_cms/pull/1749) ([tvdeyen](https://github.com/tvdeyen)) +- Add MySQL service as service [#1748](https://github.com/AlchemyCMS/alchemy_cms/pull/1748) ([mamhoff](https://github.com/mamhoff)) +- Allow importing to a different port [#1747](https://github.com/AlchemyCMS/alchemy_cms/pull/1747) ([mamhoff](https://github.com/mamhoff)) +- Sortable resources tables [#1744](https://github.com/AlchemyCMS/alchemy_cms/pull/1744) ([tvdeyen](https://github.com/tvdeyen)) +- Fix update check spec [#1743](https://github.com/AlchemyCMS/alchemy_cms/pull/1743) ([tvdeyen](https://github.com/tvdeyen)) +- Compress migrations [#1657](https://github.com/AlchemyCMS/alchemy_cms/pull/1657) ([tvdeyen](https://github.com/tvdeyen)) +- Install Gutentag migrations while installing Alchemy [#1688](https://github.com/AlchemyCMS/alchemy_cms/pull/1688) ([tvdeyen](https://github.com/tvdeyen)) +- Remove old upgrade tasks [#1687](https://github.com/AlchemyCMS/alchemy_cms/pull/1687) ([tvdeyen](https://github.com/tvdeyen)) +- Remove deprecated features [#1686](https://github.com/AlchemyCMS/alchemy_cms/pull/1686) ([tvdeyen](https://github.com/tvdeyen)) +- Remove deprecations [#1656](https://github.com/AlchemyCMS/alchemy_cms/pull/1656) ([tvdeyen](https://github.com/tvdeyen)) +- Add element editor decorator [#1653](https://github.com/AlchemyCMS/alchemy_cms/pull/1653) ([tvdeyen](https://github.com/tvdeyen)) +- Remove deprecated render_essence_* helpers [#1652](https://github.com/AlchemyCMS/alchemy_cms/pull/1652) ([tvdeyen](https://github.com/tvdeyen)) +- Remove deprecated render element editor helpers [#1651](https://github.com/AlchemyCMS/alchemy_cms/pull/1651) ([tvdeyen](https://github.com/tvdeyen)) +- 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)) +- Programmatic menus [#1756](https://github.com/AlchemyCMS/alchemy_cms/pull/1756) ([mamhoff](https://github.com/mamhoff)) +- Fix syntax in non-erb Menu templates [#1751]((https://github.com/AlchemyCMS/alchemy_cms/pull/1751)) ([Alexander ADAM](https://github.com/alexanderadam)) +- Fix `render_menu` for custom controllers [#1746]((https://github.com/AlchemyCMS/alchemy_cms/pull/1746)) ([Alexander ADAM](https://github.com/alexanderadam)) + +## 4.4.4 (2020-02-28) + +- Fix new menu form [#1740](https://github.com/AlchemyCMS/alchemy_cms/pull/1740) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.4.3 (2020-02-26) + +- Scope nodes to sites [#1738](https://github.com/AlchemyCMS/alchemy_cms/pull/1738) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.4.2 (2020-02-25) + +- Do not use deprecated methods [#1737](https://github.com/AlchemyCMS/alchemy_cms/pull/1737) ([tvdeyen](https://github.com/tvdeyen)) +- Order contents by their position in its element [#1733](https://github.com/AlchemyCMS/alchemy_cms/pull/1733) ([tvdeyen](https://github.com/tvdeyen)) +- Eager load relations in elements trash [#1732](https://github.com/AlchemyCMS/alchemy_cms/pull/1732) ([tvdeyen](https://github.com/tvdeyen)) +- Run CI builds with Sprockets 3.7.2 [#1731](https://github.com/AlchemyCMS/alchemy_cms/pull/1731) ([tvdeyen](https://github.com/tvdeyen)) +- Re-organize development dependencies [#1730](https://github.com/AlchemyCMS/alchemy_cms/pull/1730) ([tvdeyen](https://github.com/tvdeyen)) +- Update pr template [#1729](https://github.com/AlchemyCMS/alchemy_cms/pull/1729) ([tvdeyen](https://github.com/tvdeyen)) +- Generate views without _view in the filename [#1728](https://github.com/AlchemyCMS/alchemy_cms/pull/1728) ([tvdeyen](https://github.com/tvdeyen)) +- Fix CI Builds [#1727](https://github.com/AlchemyCMS/alchemy_cms/pull/1727) ([tvdeyen](https://github.com/tvdeyen)) +- Fix page tagging condition: should_attach_to_menu? [#1725](https://github.com/AlchemyCMS/alchemy_cms/pull/1725) ([mickenorlen](https://github.com/mickenorlen)) +- Fix Alchemy.user_class_name constant conflict [#1724](https://github.com/AlchemyCMS/alchemy_cms/pull/1724) ([mickenorlen](https://github.com/mickenorlen)) + +## 4.4.1 (2020-01-08) + +- Fix updating page preview after element create/save [#1710](https://github.com/AlchemyCMS/alchemy_cms/pull/1710) ([tvdeyen](https://github.com/tvdeyen)) +- Element editor layout changes [#1709](https://github.com/AlchemyCMS/alchemy_cms/pull/1709) ([tvdeyen](https://github.com/tvdeyen)) +- Add Alchemy.user_class_primary_key setting [#1708](https://github.com/AlchemyCMS/alchemy_cms/pull/1708) ([tvdeyen](https://github.com/tvdeyen)) +- Add Element views upgrade tasks [#1707](https://github.com/AlchemyCMS/alchemy_cms/pull/1707) ([tvdeyen](https://github.com/tvdeyen)) +- Use postMessage to send messages between preview and element windows [#1704](https://github.com/AlchemyCMS/alchemy_cms/pull/1704) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.4.0 (2020-01-06) + +- Use contents settings for size in EssencePicture#picture_url [#1703](https://github.com/AlchemyCMS/alchemy_cms/pull/1703) ([tvdeyen](https://github.com/tvdeyen)) +- Remove title tag from preview elements [#1701](https://github.com/AlchemyCMS/alchemy_cms/pull/1701) ([tvdeyen](https://github.com/tvdeyen)) +- Remove custom JS logging [#1700](https://github.com/AlchemyCMS/alchemy_cms/pull/1700) ([tvdeyen](https://github.com/tvdeyen)) +- Remove demo locale files [#1699](https://github.com/AlchemyCMS/alchemy_cms/pull/1699) ([tvdeyen](https://github.com/tvdeyen)) +- Use alchemyPageSelect for Node page select [#1698](https://github.com/AlchemyCMS/alchemy_cms/pull/1698) ([tvdeyen](https://github.com/tvdeyen)) +- Cache menu partials [#1697](https://github.com/AlchemyCMS/alchemy_cms/pull/1697) ([tvdeyen](https://github.com/tvdeyen)) +- Update page tree to menu nodes Rake task [#1696](https://github.com/AlchemyCMS/alchemy_cms/pull/1696) ([tvdeyen](https://github.com/tvdeyen)) +- Validate nodes name if page is absent [#1695](https://github.com/AlchemyCMS/alchemy_cms/pull/1695) ([tvdeyen](https://github.com/tvdeyen)) +- Update the application layout installer template [#1691](https://github.com/AlchemyCMS/alchemy_cms/pull/1691) ([tvdeyen](https://github.com/tvdeyen)) +- Update note about missing user class [#1690](https://github.com/AlchemyCMS/alchemy_cms/pull/1690) ([tvdeyen](https://github.com/tvdeyen)) +- Use a Sprockets 3/4 manifest file [#1689](https://github.com/AlchemyCMS/alchemy_cms/pull/1689) ([tvdeyen](https://github.com/tvdeyen)) +- Use select2 for internal page link in link overlay [#1685](https://github.com/AlchemyCMS/alchemy_cms/pull/1685) ([tvdeyen](https://github.com/tvdeyen)) +- Do not consider nested elements "orphaned" [#1684](https://github.com/AlchemyCMS/alchemy_cms/pull/1684) ([mamhoff](https://github.com/mamhoff)) +- Destroy page-dependent elements [#1683](https://github.com/AlchemyCMS/alchemy_cms/pull/1683) ([mamhoff](https://github.com/mamhoff)) +- Add anchor link tab to link overlay [#1682](https://github.com/AlchemyCMS/alchemy_cms/pull/1682) ([tvdeyen](https://github.com/tvdeyen)) +- Ensure the apt/cache folder exists while installing [#1678](https://github.com/AlchemyCMS/alchemy_cms/pull/1678) ([tvdeyen](https://github.com/tvdeyen)) +- Cache apt packages between CI runs [#1677](https://github.com/AlchemyCMS/alchemy_cms/pull/1677) ([tvdeyen](https://github.com/tvdeyen)) +- Use select2 with AJAX search for essence page select [#1675](https://github.com/AlchemyCMS/alchemy_cms/pull/1675) ([tvdeyen](https://github.com/tvdeyen)) +- Eager load associated records [#1674](https://github.com/AlchemyCMS/alchemy_cms/pull/1674) ([tvdeyen](https://github.com/tvdeyen)) +- Add support for testing with multiple Rails versions [#1673](https://github.com/AlchemyCMS/alchemy_cms/pull/1673) ([tvdeyen](https://github.com/tvdeyen)) +- Page api pagination [#1672](https://github.com/AlchemyCMS/alchemy_cms/pull/1672) ([tvdeyen](https://github.com/tvdeyen)) +- Adjust select2 loading-more indicator [#1671](https://github.com/AlchemyCMS/alchemy_cms/pull/1671) ([tvdeyen](https://github.com/tvdeyen)) +- Test support fixes [#1669](https://github.com/AlchemyCMS/alchemy_cms/pull/1669) ([tvdeyen](https://github.com/tvdeyen)) +- Build fixes [#1668](https://github.com/AlchemyCMS/alchemy_cms/pull/1668) ([tvdeyen](https://github.com/tvdeyen)) +- Add Menus [#1667](https://github.com/AlchemyCMS/alchemy_cms/pull/1667) ([tvdeyen](https://github.com/tvdeyen)) +- Add a label component [#1666](https://github.com/AlchemyCMS/alchemy_cms/pull/1666) ([tvdeyen](https://github.com/tvdeyen)) +- Run bundle install on CI even if cache hits [#1665](https://github.com/AlchemyCMS/alchemy_cms/pull/1665) ([tvdeyen](https://github.com/tvdeyen)) +- Moves switch_language method into languages_controller. [#1664](https://github.com/AlchemyCMS/alchemy_cms/pull/1664) ([tvdeyen](https://github.com/tvdeyen)) +- Cache gems between CI runs [#1663](https://github.com/AlchemyCMS/alchemy_cms/pull/1663) ([tvdeyen](https://github.com/tvdeyen)) +- Remove production gems from local Gemfile [#1662](https://github.com/AlchemyCMS/alchemy_cms/pull/1662) ([tvdeyen](https://github.com/tvdeyen)) +- Touch contents updated_at column in pure SQL [#1661](https://github.com/AlchemyCMS/alchemy_cms/pull/1661) ([tvdeyen](https://github.com/tvdeyen)) +- Convert page editing user methods into AR relations [#1658](https://github.com/AlchemyCMS/alchemy_cms/pull/1658) ([tvdeyen](https://github.com/tvdeyen)) +- Ensure the admin locale is only set by available locales [#1655](https://github.com/AlchemyCMS/alchemy_cms/pull/1655) ([tvdeyen](https://github.com/tvdeyen)) +- Add a GitHub actions ci.yml [#1654](https://github.com/AlchemyCMS/alchemy_cms/pull/1654) ([tvdeyen](https://github.com/tvdeyen)) +- Adjust install generator to latest changes [#1649](https://github.com/AlchemyCMS/alchemy_cms/pull/1649) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate _view suffix of element views [#1648](https://github.com/AlchemyCMS/alchemy_cms/pull/1648) ([tvdeyen](https://github.com/tvdeyen)) +- Add a configurable logout method (default: delete) [#1647](https://github.com/AlchemyCMS/alchemy_cms/pull/1647) ([delphaber](https://github.com/delphaber)) +- Deprecate render_essence helpers [#1644](https://github.com/AlchemyCMS/alchemy_cms/pull/1644) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate element editors [#1643](https://github.com/AlchemyCMS/alchemy_cms/pull/1643) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate local options in essence editors [#1642](https://github.com/AlchemyCMS/alchemy_cms/pull/1642) ([tvdeyen](https://github.com/tvdeyen)) +- Ensure the EssencePage id regexp matches only numbers [#1641](https://github.com/AlchemyCMS/alchemy_cms/pull/1641) ([tvdeyen](https://github.com/tvdeyen)) +- Use EssencePage in contact forms [#1640](https://github.com/AlchemyCMS/alchemy_cms/pull/1640) ([tvdeyen](https://github.com/tvdeyen)) +- Add Alchemy::EssencePage [#1639](https://github.com/AlchemyCMS/alchemy_cms/pull/1639) ([tvdeyen](https://github.com/tvdeyen)) +- FEAT: Render message and warnings in element editor [#1637](https://github.com/AlchemyCMS/alchemy_cms/pull/1637) ([tvdeyen](https://github.com/tvdeyen)) +- Tackle Rails 6 deprecations [#1636](https://github.com/AlchemyCMS/alchemy_cms/pull/1636) ([tvdeyen](https://github.com/tvdeyen)) +- Preload assets in tests [#1635](https://github.com/AlchemyCMS/alchemy_cms/pull/1635) ([tvdeyen](https://github.com/tvdeyen)) +- Allow acts-as-list 1.0 [#1634](https://github.com/AlchemyCMS/alchemy_cms/pull/1634) ([tvdeyen](https://github.com/tvdeyen)) +- Add Sprockets manifest file to dummy app [#1632](https://github.com/AlchemyCMS/alchemy_cms/pull/1632) ([tvdeyen](https://github.com/tvdeyen)) +- Master now tracks 4.4.0.alpha [#1627](https://github.com/AlchemyCMS/alchemy_cms/pull/1627) ([tvdeyen](https://github.com/tvdeyen)) +- Fix Cell Migration to maintain positions [#1625](https://github.com/AlchemyCMS/alchemy_cms/pull/1625) ([mamhoff](https://github.com/mamhoff)) +- Cell Upgrader: Match quotation marks in cell name string [#1624](https://github.com/AlchemyCMS/alchemy_cms/pull/1624) ([mamhoff](https://github.com/mamhoff)) +- Cell Migrator: Maintain element order in fixed elements [#1623](https://github.com/AlchemyCMS/alchemy_cms/pull/1623) ([mamhoff](https://github.com/mamhoff)) +- Enhance cells upgrader to deal with render_elements from_page: x [#1622](https://github.com/AlchemyCMS/alchemy_cms/pull/1622) ([mamhoff](https://github.com/mamhoff)) + +## 4.3.2 (2019-11-08) + +- Allow simple form 5 [#1660](https://github.com/AlchemyCMS/alchemy_cms/pull/1633) ([jkimmeyer](https://github.com/jkimmeyer)) + +## 4.3.1 (2019-09-27) + +- Fix Cell Migration to maintain positions [#1625](https://github.com/AlchemyCMS/alchemy_cms/pull/1625) ([mamhoff](https://github.com/mamhoff)) +- Cell Upgrader: Match quotation marks in cell name string [#1624](https://github.com/AlchemyCMS/alchemy_cms/pull/1624) ([mamhoff](https://github.com/mamhoff)) +- Cell Migrator: Maintain element order in fixed elements [#1623](https://github.com/AlchemyCMS/alchemy_cms/pull/1623) ([mamhoff](https://github.com/mamhoff)) +- Enhance cells upgrader to deal with render_elements from_page: x [#1622](https://github.com/AlchemyCMS/alchemy_cms/pull/1622) ([mamhoff](https://github.com/mamhoff)) + +## 4.3.0 (2019-08-23) + +- Add Rails 6 support [#1616](https://github.com/AlchemyCMS/alchemy_cms/pull/1616) ([tvdeyen](https://github.com/tvdeyen)) +- Use media_type over content_type in specs [#1615](https://github.com/AlchemyCMS/alchemy_cms/pull/1615) ([tvdeyen](https://github.com/tvdeyen)) +- Remove unused Picture#security_token method [#1614](https://github.com/AlchemyCMS/alchemy_cms/pull/1614) ([tvdeyen](https://github.com/tvdeyen)) +- rspec-rails 4 [#1613](https://github.com/AlchemyCMS/alchemy_cms/pull/1613) ([tvdeyen](https://github.com/tvdeyen)) +- Update Dummy test app to Rails 5.2 [#1612](https://github.com/AlchemyCMS/alchemy_cms/pull/1612) ([tvdeyen](https://github.com/tvdeyen)) +- add default styling for number inputs [#1598](https://github.com/AlchemyCMS/alchemy_cms/pull/1598) ([alexanderadam](https://github.com/alexanderadam)) +- Upgrade responders to version 3.0.0 [#1593](https://github.com/AlchemyCMS/alchemy_cms/pull/1593) ([depfu](https://github.com/apps/depfu)) +- Update jquery fileupload plugin [#1592](https://github.com/AlchemyCMS/alchemy_cms/pull/1592) ([tvdeyen](https://github.com/tvdeyen)) +- Only return visible elements from Pages elements relations [#1590](https://github.com/AlchemyCMS/alchemy_cms/pull/1590) ([tvdeyen](https://github.com/tvdeyen)) +- Only return visible elements from Elements nested_elements relation [#1589](https://github.com/AlchemyCMS/alchemy_cms/pull/1589) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.2.3 (2019-09-27) + +- Fix Cell Migration to maintain positions [#1625](https://github.com/AlchemyCMS/alchemy_cms/pull/1625) ([mamhoff](https://github.com/mamhoff)) +- Cell Upgrader: Match quotation marks in cell name string [#1624](https://github.com/AlchemyCMS/alchemy_cms/pull/1624) ([mamhoff](https://github.com/mamhoff)) +- Cell Migrator: Maintain element order in fixed elements [#1623](https://github.com/AlchemyCMS/alchemy_cms/pull/1623) ([mamhoff](https://github.com/mamhoff)) +- Enhance cells upgrader to deal with render_elements from_page: x [#1622](https://github.com/AlchemyCMS/alchemy_cms/pull/1622) ([mamhoff](https://github.com/mamhoff)) + +## 4.2.2 (2019-08-13) + +- Fallback to default locale if unavailable locale requested [#1610](https://github.com/AlchemyCMS/alchemy_cms/pull/1610) ([mamhoff](https://github.com/mamhoff)) + +## 4.2.1 (2019-08-08) + +- Use strings as JSON root keys in API controllers [#1602](https://github.com/AlchemyCMS/alchemy_cms/pull/1602) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.2.0 (2019-05-30) + +- remove unused .teatro.yml [#1585](https://github.com/AlchemyCMS/alchemy_cms/pull/1585) ([kirillshevch](https://github.com/kirillshevch)) +- Use Set to collect callbacks in OnPageLayout [#1583](https://github.com/AlchemyCMS/alchemy_cms/pull/1583) ([mamhoff](https://github.com/mamhoff)) +- Allow Ransack 1.x [#1580](https://github.com/AlchemyCMS/alchemy_cms/pull/1580) ([tvdeyen](https://github.com/tvdeyen)) +- Set a max-width to preview frame [#1578](https://github.com/AlchemyCMS/alchemy_cms/pull/1578) ([tvdeyen](https://github.com/tvdeyen)) +- Rebuild locked pages tabs in flexbox [#1577](https://github.com/AlchemyCMS/alchemy_cms/pull/1577) ([tvdeyen](https://github.com/tvdeyen)) +- Use where.not instead of Arel [#1576](https://github.com/AlchemyCMS/alchemy_cms/pull/1576) ([tvdeyen](https://github.com/tvdeyen)) +- Add missing requires to factories [#1575](https://github.com/AlchemyCMS/alchemy_cms/pull/1575) ([tvdeyen](https://github.com/tvdeyen)) +- Upgrade sqlite3 to version 1.4.1 [#1574](https://github.com/AlchemyCMS/alchemy_cms/pull/1574) ([depfu](https://github.com/apps/depfu)) +- Fix elements window ajax errors [#1573](https://github.com/AlchemyCMS/alchemy_cms/pull/1573) ([tvdeyen](https://github.com/tvdeyen)) +- Use SimpleForm field for datepicker in resources form [#1572](https://github.com/AlchemyCMS/alchemy_cms/pull/1572) ([tvdeyen](https://github.com/tvdeyen)) +- Render warning message in warning helper [#1571](https://github.com/AlchemyCMS/alchemy_cms/pull/1571) ([tvdeyen](https://github.com/tvdeyen)) +- Prohibit coffee-rails 5.0 [#1570](https://github.com/AlchemyCMS/alchemy_cms/pull/1570) ([tvdeyen](https://github.com/tvdeyen)) +- Add Date column support to resources [#1567](https://github.com/AlchemyCMS/alchemy_cms/pull/1567) ([tvdeyen](https://github.com/tvdeyen)) +- Fix pluralization of resource table header [#1566](https://github.com/AlchemyCMS/alchemy_cms/pull/1566) ([tvdeyen](https://github.com/tvdeyen)) +- Fix compact elements style [#1565](https://github.com/AlchemyCMS/alchemy_cms/pull/1565) ([tvdeyen](https://github.com/tvdeyen)) +- Show Ruby and Rails versions in info dialog [#1564](https://github.com/AlchemyCMS/alchemy_cms/pull/1564) ([tvdeyen](https://github.com/tvdeyen)) +- Set spinner color to current text color [#1563](https://github.com/AlchemyCMS/alchemy_cms/pull/1563) ([tvdeyen](https://github.com/tvdeyen)) +- Update links in post install message [#1562](https://github.com/AlchemyCMS/alchemy_cms/pull/1562) ([tvdeyen](https://github.com/tvdeyen)) +- Allow cancancan 3 [#1561](https://github.com/AlchemyCMS/alchemy_cms/pull/1561) ([tvdeyen](https://github.com/tvdeyen)) +- Fix Member Page permissions syntax [#1560](https://github.com/AlchemyCMS/alchemy_cms/pull/1560) ([tvdeyen](https://github.com/tvdeyen)) +- Update upgrader [#1558](https://github.com/AlchemyCMS/alchemy_cms/pull/1558) ([tvdeyen](https://github.com/tvdeyen)) +- Use element name local in generators [#1556](https://github.com/AlchemyCMS/alchemy_cms/pull/1556) ([tvdeyen](https://github.com/tvdeyen)) +- Remove invalid bytecode handler [#1555](https://github.com/AlchemyCMS/alchemy_cms/pull/1555) ([tvdeyen](https://github.com/tvdeyen)) +- Separate render element calls [#1554](https://github.com/AlchemyCMS/alchemy_cms/pull/1554) ([tvdeyen](https://github.com/tvdeyen)) +- Expose the element into partials as local object [#1553](https://github.com/AlchemyCMS/alchemy_cms/pull/1553) ([tvdeyen](https://github.com/tvdeyen)) +- Allow admins to switch all languages [#1552](https://github.com/AlchemyCMS/alchemy_cms/pull/1552) ([tvdeyen](https://github.com/tvdeyen)) +- Raise targeted Ruby version to 2.3 [#1545](https://github.com/AlchemyCMS/alchemy_cms/pull/1545) ([tvdeyen](https://github.com/tvdeyen)) +- Introduces an Elements finder class [#1544](https://github.com/AlchemyCMS/alchemy_cms/pull/1544) ([tvdeyen](https://github.com/tvdeyen)) +- Fixate sqlite dep for bug fix [#1543](https://github.com/AlchemyCMS/alchemy_cms/pull/1543) ([tvdeyen](https://github.com/tvdeyen)) +- Upgrade shoulda-matchers to version 4.0.0 [#1542](https://github.com/AlchemyCMS/alchemy_cms/pull/1542) ([depfu](https://github.com/apps/depfu)) +- Upgrade factory_bot_rails to version 5.0.1 [#1540](https://github.com/AlchemyCMS/alchemy_cms/pull/1540) ([depfu](https://github.com/apps/depfu)) +- Use Flatpickr as Datepicker [#1533](https://github.com/AlchemyCMS/alchemy_cms/pull/1533) ([mamhoff](https://github.com/mamhoff)) +- Use system tests over feature specs [#1528](https://github.com/AlchemyCMS/alchemy_cms/pull/1528) ([tvdeyen](https://github.com/tvdeyen)) +- Flexible width for admin navigation entry labels [#1527](https://github.com/AlchemyCMS/alchemy_cms/pull/1527) ([tvdeyen](https://github.com/tvdeyen)) +- Render new page when there is a flash message [#1525](https://github.com/AlchemyCMS/alchemy_cms/pull/1525) ([jedrekdomanski](https://github.com/jedrekdomanski)) +- Responsive elements window and sidebar [#1519](https://github.com/AlchemyCMS/alchemy_cms/pull/1519) ([tvdeyen](https://github.com/tvdeyen)) +- Change element eye icon on public state [#1517](https://github.com/AlchemyCMS/alchemy_cms/pull/1517) ([oniram88](https://github.com/oniram88)) +- Maximize element window if Tinymce is fullscreen [#1515](https://github.com/AlchemyCMS/alchemy_cms/pull/1515) ([tvdeyen](https://github.com/tvdeyen)) +- Remove cells in favour of fixed elements [#1514](https://github.com/AlchemyCMS/alchemy_cms/pull/1514) ([tvdeyen](https://github.com/tvdeyen)) +- Feature: Autogenerate nestable elements [#1513](https://github.com/AlchemyCMS/alchemy_cms/pull/1513) ([tvdeyen](https://github.com/tvdeyen)) +- Allow "data" key for module navigations [#1512](https://github.com/AlchemyCMS/alchemy_cms/pull/1512) ([mamhoff](https://github.com/mamhoff)) +- Allow to define layout for page previews [#1500](https://github.com/AlchemyCMS/alchemy_cms/pull/1500) ([westonganger](https://github.com/westonganger)) +- Disable page publish/view page buttons according to published_at [#1498](https://github.com/AlchemyCMS/alchemy_cms/pull/1498) ([westonganger](https://github.com/westonganger)) +- Fix capitalization for login/logout/leave [#1497](https://github.com/AlchemyCMS/alchemy_cms/pull/1497) ([westonganger](https://github.com/westonganger)) +- Verify controller keys within `register_module` [#1495](https://github.com/AlchemyCMS/alchemy_cms/pull/1495) ([westonganger](https://github.com/westonganger)) +- Update bundled Tinymce to 4.8.3 [#1491](https://github.com/AlchemyCMS/alchemy_cms/pull/1491) ([tvdeyen](https://github.com/tvdeyen)) +- Use dynamic attributes in factories [#1484](https://github.com/AlchemyCMS/alchemy_cms/pull/1484) ([tvdeyen](https://github.com/tvdeyen)) +- Migrating to active_model_serializers ~> 0.10.0 [#1478](https://github.com/AlchemyCMS/alchemy_cms/pull/1478) ([pmashchak](https://github.com/pmashchak)) +- Replace picture galleries with nestable elements [#1358](https://github.com/AlchemyCMS/alchemy_cms/pull/1358) ([tvdeyen](https://github.com/tvdeyen)) +- Add a compact nested element style [#1357](https://github.com/AlchemyCMS/alchemy_cms/pull/1357) by [tvdeyen](https://github.com/tvdeyen) + +## 4.1.0 (2018-09-22) + +- Use console.warn for Alchemy.debug [#1476](https://github.com/AlchemyCMS/alchemy_cms/pull/1476) ([tvdeyen](https://github.com/tvdeyen)) +- Fixes picture per page in overlay [#1475](https://github.com/AlchemyCMS/alchemy_cms/pull/1475) ([tvdeyen](https://github.com/tvdeyen)) +- Style adjustments [#1474](https://github.com/AlchemyCMS/alchemy_cms/pull/1474) ([tvdeyen](https://github.com/tvdeyen)) +- Simplify pagination implementation [#1471](https://github.com/AlchemyCMS/alchemy_cms/pull/1471) ([mamhoff](https://github.com/mamhoff)) +- Try .any? to prevent error in dashboard on online users [#1469](https://github.com/AlchemyCMS/alchemy_cms/pull/1469) ([askl56](https://github.com/askl56)) +- Update changelog for 4.0.4 release [#1468](https://github.com/AlchemyCMS/alchemy_cms/pull/1468) ([tvdeyen](https://github.com/tvdeyen)) +- Do not cache sitemap in Turbolinks [#1463](https://github.com/AlchemyCMS/alchemy_cms/pull/1463) ([tvdeyen](https://github.com/tvdeyen)) +- Fix sorting in Resources controller [#1462](https://github.com/AlchemyCMS/alchemy_cms/pull/1462) ([mamhoff](https://github.com/mamhoff)) +- Fix removing picture essences [#1460](https://github.com/AlchemyCMS/alchemy_cms/pull/1460) ([mamhoff](https://github.com/mamhoff)) +- Upgrade ransack to version 2.0.0 [#1458](https://github.com/AlchemyCMS/alchemy_cms/pull/1458) ([depfu](https://github.com/marketplace/depfu)) +- Toolbar icon vertical alignment fixes [#1450](https://github.com/AlchemyCMS/alchemy_cms/pull/1450) ([tvdeyen](https://github.com/tvdeyen)) +- Fix tidy task [#1449](https://github.com/AlchemyCMS/alchemy_cms/pull/1449) ([mamhoff](https://github.com/mamhoff)) +- Update changelog [#1448](https://github.com/AlchemyCMS/alchemy_cms/pull/1448) ([tvdeyen](https://github.com/tvdeyen)) +- New thumbnail style [#1447](https://github.com/AlchemyCMS/alchemy_cms/pull/1447) ([tvdeyen](https://github.com/tvdeyen)) +- Styling fixes [#1446](https://github.com/AlchemyCMS/alchemy_cms/pull/1446) ([tvdeyen](https://github.com/tvdeyen)) +- Do not prevent default click handling in Element editor [#1445](https://github.com/AlchemyCMS/alchemy_cms/pull/1445) ([mamhoff](https://github.com/mamhoff)) +- Fix content container height [#1443](https://github.com/AlchemyCMS/alchemy_cms/pull/1443) ([tvdeyen](https://github.com/tvdeyen)) +- Use max instead of sort.last in update check [#1442](https://github.com/AlchemyCMS/alchemy_cms/pull/1442) ([tvdeyen](https://github.com/tvdeyen)) +- Use optional: true for optional belongs_to associations [#1441](https://github.com/AlchemyCMS/alchemy_cms/pull/1441) ([tvdeyen](https://github.com/tvdeyen)) +- Set parent element id when pasting from clipboard [#1440](https://github.com/AlchemyCMS/alchemy_cms/pull/1440) ([tvdeyen](https://github.com/tvdeyen)) +- Add must_revalidate to cache-control header [#1439](https://github.com/AlchemyCMS/alchemy_cms/pull/1439) ([afdev82](https://github.com/afdev82)) +- Update README.md [#1438](https://github.com/AlchemyCMS/alchemy_cms/pull/1438) ([agorneo](https://github.com/agorneo)) +- Add a pull request template [#1436](https://github.com/AlchemyCMS/alchemy_cms/pull/1436) ([tvdeyen](https://github.com/tvdeyen)) +- Add a feature request template [#1435](https://github.com/AlchemyCMS/alchemy_cms/pull/1435) ([tvdeyen](https://github.com/tvdeyen)) +- Add a GitHub issue template [#1434](https://github.com/AlchemyCMS/alchemy_cms/pull/1434) ([tvdeyen](https://github.com/tvdeyen)) +- Picture zoom UX enhancements [#1431](https://github.com/AlchemyCMS/alchemy_cms/pull/1431) ([tvdeyen](https://github.com/tvdeyen)) +- Fix draggable trash item feature [#1428](https://github.com/AlchemyCMS/alchemy_cms/pull/1428) ([tvdeyen](https://github.com/tvdeyen)) +- Load Jcrop selection gif via asset pipeline [#1427](https://github.com/AlchemyCMS/alchemy_cms/pull/1427) ([tvdeyen](https://github.com/tvdeyen)) +- Color and styles adjustments [#1426](https://github.com/AlchemyCMS/alchemy_cms/pull/1426) ([tvdeyen](https://github.com/tvdeyen)) +- Extract tags css rules into own file [#1424](https://github.com/AlchemyCMS/alchemy_cms/pull/1424) ([tvdeyen](https://github.com/tvdeyen)) +- Adjust the welcome screen to new color theme [#1423](https://github.com/AlchemyCMS/alchemy_cms/pull/1423) ([tvdeyen](https://github.com/tvdeyen)) +- Fixes menubar layout [#1422](https://github.com/AlchemyCMS/alchemy_cms/pull/1422) ([tvdeyen](https://github.com/tvdeyen)) +- Update jquery-ui-rails to 6.0 [#1420](https://github.com/AlchemyCMS/alchemy_cms/pull/1420) ([tvdeyen](https://github.com/tvdeyen)) +- Allow CanCanCan 2.x [#1418](https://github.com/AlchemyCMS/alchemy_cms/pull/1418) ([tvdeyen](https://github.com/tvdeyen)) +- Add a Heroku Deploy button [#1416](https://github.com/AlchemyCMS/alchemy_cms/pull/1416) ([tvdeyen](https://github.com/tvdeyen)) +- Upgrade simple_form to version 4.0 [#1413](https://github.com/AlchemyCMS/alchemy_cms/pull/1413) ([depfu](https://github.com/marketplace/depfu)) +- Do not require localeapp gem [#1412](https://github.com/AlchemyCMS/alchemy_cms/pull/1412) ([tvdeyen](https://github.com/tvdeyen)) +- Fix all Rubocop offenses and update some config [#1411](https://github.com/AlchemyCMS/alchemy_cms/pull/1411) ([tvdeyen](https://github.com/tvdeyen)) +- Upgrade mysql2 to version 0.5.1 [#1410](https://github.com/AlchemyCMS/alchemy_cms/pull/1410) ([depfu](https://github.com/marketplace/depfu)) +- Capybara 3.0 support [#1409](https://github.com/AlchemyCMS/alchemy_cms/pull/1409) ([tvdeyen](https://github.com/tvdeyen)) +- Handle zero plural resource names [#1407](https://github.com/AlchemyCMS/alchemy_cms/pull/1407) ([dbwinger](https://github.com/dbwinger)) +- Update rubocop config [#1404](https://github.com/AlchemyCMS/alchemy_cms/pull/1404) ([tvdeyen](https://github.com/tvdeyen)) +- Explicitly set the Rails version in dummy app [#1403](https://github.com/AlchemyCMS/alchemy_cms/pull/1403) ([tvdeyen](https://github.com/tvdeyen)) +- Do not reload essence classes in dev mode [#1400](https://github.com/AlchemyCMS/alchemy_cms/pull/1400) ([tvdeyen](https://github.com/tvdeyen)) +- Move all translations into `alchemy_i18n` extension [#1398](https://github.com/AlchemyCMS/alchemy_cms/pull/1398) ([tvdeyen](https://github.com/tvdeyen)) +- Add `nested_elements` to serialized element json [#1397](https://github.com/AlchemyCMS/alchemy_cms/pull/1397) ([tvdeyen](https://github.com/tvdeyen)) +- Fix YAML safe_load [#1395](https://github.com/AlchemyCMS/alchemy_cms/pull/1395) ([tvdeyen](https://github.com/tvdeyen)) +- Fix passing html options to form builder submit input [#1394](https://github.com/AlchemyCMS/alchemy_cms/pull/1394) ([tvdeyen](https://github.com/tvdeyen)) +- Darker blue dialogs [#1393](https://github.com/AlchemyCMS/alchemy_cms/pull/1393) ([tvdeyen](https://github.com/tvdeyen)) +- New orange logo [#1392](https://github.com/AlchemyCMS/alchemy_cms/pull/1392) ([tvdeyen](https://github.com/tvdeyen)) +- Use Open Sans for admin font [#1391](https://github.com/AlchemyCMS/alchemy_cms/pull/1391) ([tvdeyen](https://github.com/tvdeyen)) +- Use ActiveRecord touching [#1390](https://github.com/AlchemyCMS/alchemy_cms/pull/1390) ([tvdeyen](https://github.com/tvdeyen)) +- correct scss typo [#1388](https://github.com/AlchemyCMS/alchemy_cms/pull/1388) ([oniram88](https://github.com/oniram88)) +- Add Rails 5.2 support [#1387](https://github.com/AlchemyCMS/alchemy_cms/pull/1387) ([tvdeyen](https://github.com/tvdeyen)) +- Update Gutentag [#1386](https://github.com/AlchemyCMS/alchemy_cms/pull/1386) ([tvdeyen](https://github.com/tvdeyen)) +- Fix resources search [#1384](https://github.com/AlchemyCMS/alchemy_cms/pull/1384) ([tvdeyen](https://github.com/tvdeyen)) +- Fixate Gutentag to 2.1.0 for now [#1383](https://github.com/AlchemyCMS/alchemy_cms/pull/1383) ([tvdeyen](https://github.com/tvdeyen)) +- Skip migrate-to-gutentag migration for fresh installs [#1381](https://github.com/AlchemyCMS/alchemy_cms/pull/1381) ([tvdeyen](https://github.com/tvdeyen)) +- More prominent active menu color [#1380](https://github.com/AlchemyCMS/alchemy_cms/pull/1380) ([tvdeyen](https://github.com/tvdeyen)) +- Use at least jquery-rails 4.0.4 [#1378](https://github.com/AlchemyCMS/alchemy_cms/pull/1378) ([tvdeyen](https://github.com/tvdeyen)) +- Give capybara more time to render pictures [#1377](https://github.com/AlchemyCMS/alchemy_cms/pull/1377) ([tvdeyen](https://github.com/tvdeyen)) +- A few admin layout refinements [#1374](https://github.com/AlchemyCMS/alchemy_cms/pull/1374) ([tvdeyen](https://github.com/tvdeyen)) +- Upgrade cancancan to version 2.1 [#1372](https://github.com/AlchemyCMS/alchemy_cms/pull/1372) ([depfu](https://github.com/apps/depfu)) +- Upgrade kaminari to version 1.1 [#1370](https://github.com/AlchemyCMS/alchemy_cms/pull/1370) ([depfu](https://github.com/apps/depfu)) +- Remove all old migration files [#1367](https://github.com/AlchemyCMS/alchemy_cms/pull/1367) ([tvdeyen](https://github.com/tvdeyen)) +- Upgrade mysql2 to version 0.4.10 [#1366](https://github.com/AlchemyCMS/alchemy_cms/pull/1366) ([depfu](https://github.com/marketplace/depfu)) +- Upgrade pg to version 1.0.0 [#1365](https://github.com/AlchemyCMS/alchemy_cms/pull/1365) ([depfu](https://github.com/marketplace/depfu)) +- Use Gutentag for tags [#1364](https://github.com/AlchemyCMS/alchemy_cms/pull/1364) ([tvdeyen](https://github.com/tvdeyen)) +- Update Rubocop config [#1363](https://github.com/AlchemyCMS/alchemy_cms/pull/1363) ([tvdeyen](https://github.com/tvdeyen)) +- Compress 4.0 migrations [#1362](https://github.com/AlchemyCMS/alchemy_cms/pull/1362) ([tvdeyen](https://github.com/tvdeyen)) +- Removes 3.x upgraders [#1361](https://github.com/AlchemyCMS/alchemy_cms/pull/1361) ([tvdeyen](https://github.com/tvdeyen)) +- Get rid of Rails 5.2 deprecations [#1360](https://github.com/AlchemyCMS/alchemy_cms/pull/1360) ([tvdeyen](https://github.com/tvdeyen)) +- Fix API response for users able to edit content [#1356](https://github.com/AlchemyCMS/alchemy_cms/pull/1356) ([tvdeyen](https://github.com/tvdeyen)) +- Remove jasmine gems [#1355](https://github.com/AlchemyCMS/alchemy_cms/pull/1355) ([tvdeyen](https://github.com/tvdeyen)) +- Remove translations provided by ActiveModel [#1354](https://github.com/AlchemyCMS/alchemy_cms/pull/1354) ([pelargir](https://github.com/pelargir)) +- Update bundled TinyMCE to 4.7.5 [#1353](https://github.com/AlchemyCMS/alchemy_cms/pull/1353) ([tvdeyen](https://github.com/tvdeyen)) +- New color theme [#1352](https://github.com/AlchemyCMS/alchemy_cms/pull/1352) ([tvdeyen](https://github.com/tvdeyen)) +- Only scroll to element if focused from preview [#1351](https://github.com/AlchemyCMS/alchemy_cms/pull/1351) ([tvdeyen](https://github.com/tvdeyen)) +- Don't prevent submit event of element save button [#1349](https://github.com/AlchemyCMS/alchemy_cms/pull/1349) ([tvdeyen](https://github.com/tvdeyen)) +- Use FA calendar icons for EssenceDate picker [#1348](https://github.com/AlchemyCMS/alchemy_cms/pull/1348) ([tvdeyen](https://github.com/tvdeyen)) +- Use headless chrome for feature tests [#1347](https://github.com/AlchemyCMS/alchemy_cms/pull/1347) ([tvdeyen](https://github.com/tvdeyen)) +- Refactor the preview window JS code [#1346](https://github.com/AlchemyCMS/alchemy_cms/pull/1346) ([tvdeyen](https://github.com/tvdeyen)) +- New table styles [#1344](https://github.com/AlchemyCMS/alchemy_cms/pull/1344) ([tvdeyen](https://github.com/tvdeyen)) +- Remove pleaseWait overlay from links with GET requests [#1343](https://github.com/AlchemyCMS/alchemy_cms/pull/1343) by [tvdeyen](https://github.com/tvdeyen) +- Replaces PNG icons with FontAwesome icon font [#1342](https://github.com/AlchemyCMS/alchemy_cms/pull/1342) by [tvdeyen](https://github.com/tvdeyen) +- Ensure to use pg < 1.0 in tests [#1341](https://github.com/AlchemyCMS/alchemy_cms/pull/1341) ([tvdeyen](https://github.com/tvdeyen)) +- Add must_revalidate to cache-control header [#1340](https://github.com/AlchemyCMS/alchemy_cms/pull/1340) ([afdev82](https://github.com/afdev82)) +- Removed fixed table headers from admin resource tables [#1339](https://github.com/AlchemyCMS/alchemy_cms/pull/1339) by [tvdeyen](https://github.com/tvdeyen) +- Removed Bourbon Sass library [#1339](https://github.com/AlchemyCMS/alchemy_cms/pull/1339) by [tvdeyen](https://github.com/tvdeyen) +- Add possibility to add a suffix to the page title [#1331](https://github.com/AlchemyCMS/alchemy_cms/pull/1331) ([jrieger](https://github.com/jrieger)) +- Do not add leading slash to default admin path [#1329](https://github.com/AlchemyCMS/alchemy_cms/pull/1329) ([tvdeyen](https://github.com/tvdeyen)) +- Check if file exists on disk before calling identify [#1327](https://github.com/AlchemyCMS/alchemy_cms/pull/1327) ([chalmagean](https://github.com/chalmagean)) +- Skip folded deeper levels when rendering page tree [#1324](https://github.com/AlchemyCMS/alchemy_cms/pull/1324) ([pascalj](https://github.com/pascalj)) + +## 4.0.5 (2018-09-17) + +- Do not cache sitemap in Turbolinks [#1463](https://github.com/AlchemyCMS/alchemy_cms/pull/1463) ([tvdeyen](https://github.com/tvdeyen)) +- Skip folded deeper levels when rendering page tree [#1324](https://github.com/AlchemyCMS/alchemy_cms/pull/1324) ([pascalj](https://github.com/pascalj)) + +## 4.0.4 (2018-09-05) + +- Allow Kaminari 1.x [#1467](https://github.com/AlchemyCMS/alchemy_cms/pull/1467) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.0.3 (2018-05-14) + +- Add must_revalidate to cache-control header [#1340](https://github.com/AlchemyCMS/alchemy_cms/pull/1340) ([afdev82](https://github.com/afdev82)) + +## 4.0.2 (2018-05-08) + +- Fix draggable trash item feature [#1429](https://github.com/AlchemyCMS/alchemy_cms/pull/1429) ([tvdeyen](https://github.com/tvdeyen)) +- Allow CanCanCan 2.x [#1418](https://github.com/AlchemyCMS/alchemy_cms/pull/1418) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.0.1 (2018-04-23) + +- Add more classes to YAML.safe_load [#1414](https://github.com/AlchemyCMS/alchemy_cms/pull/1414) ([tvdeyen](https://github.com/tvdeyen)) + +## 4.0.0 (2017-11-06) + +- Fixes image cropping issues [#1320](https://github.com/AlchemyCMS/alchemy_cms/pull/1320) and [#1321](https://github.com/AlchemyCMS/alchemy_cms/pull/1321) by [tvdeyen](https://github.com/tvdeyen) + This includes the change that images will not be cropped anymore unless `crop: true` is explicitly given in either the contents settings or passed via options to `render_essence`. The former behavior of implicitly cropping only because crop values (`crop_from` or `crop_size`) were present on the `EssencePicture` database record was erroneous and confusing. +- Allow Dragonfly 1.1 and above [#1314](https://github.com/AlchemyCMS/alchemy_cms/pull/1314) by [tvdeyen](https://github.com/tvdeyen) +- Added Rails 5.1 support [#1310](https://github.com/AlchemyCMS/alchemy_cms/pull/1310) by [tvdeyen](https://github.com/tvdeyen) +- Always use `border-box` box model for all CSS components in the admin [#1309](https://github.com/AlchemyCMS/alchemy_cms/pull/1309) by [tvdeyen](https://github.com/tvdeyen) + +## 4.0.0.rc2 (2017-08-18) + +- Removed deprecated `:image_size` option from `EssencePicture` + Use `:size` instead. +- Remove deprecated `take_me_for_preview` content definition option + Use `as_element_title` instead. +- Removed deprecated picture url helpers `show_alchemy_picture_path` and `show_alchemy_picture_url` + Use `picture.url` instead. +- Removed deprecated pages helper module. +- Removed deprecated translation methods `_t` and `Alchemy::I18n.t`. + Use `Alchemy.t` instead. +- Removed deprecated `redirect_index` configuration + Use `redirect_to_public_child` configuration instead. + +## 4.0.0.rc1 (2017-08-17) + +- Removed `merge_params` from `Alchemy::Admin::BaseHelper` + Use `ActionController::Parameters#merge` instead +- Removed `merge_params_only` from `Alchemy::Admin::BaseHelper` + Use methods from `ActionController::Parameters` instead +- Removed `merge_params_without` from `Alchemy::Admin::BaseHelper` + Use `ActionController::Parameters#delete_if` instead +- Removed `tag_list_tag_active?` from `Alchemy::Admin::TagsHelper` + Use `filtered_by_tag?` instead +- Removed `add_to_tag_filter` and `remove_from_tag_filter` from `Alchemy::Admin::TagsHelper` + Use `tags_for_filter` and pass the `current` tag instead +- Removes the possibility to pass options param as JSON string. [#1291](https://github.com/AlchemyCMS/alchemy_cms/pull/1291) by [tvdeyen](https://github.com/tvdeyen) + Pass normal params instead. +- Removed `redirect_back_or_to_default` from `Alchemy::Admin::BaseController` + Use Rails' `redirect_back` with a `fallback_location` instead +- Deprecated controller requests test helpers [#1284](https://github.com/AlchemyCMS/alchemy_cms/pull/1284) by [tvdeyen](https://github.com/tvdeyen) + +## 4.0.0.beta (2017-06-20) + +- Rails 5 + +## 3.6.5 (2018-05-08) + +- Fix draggable trash item feature [#1430](https://github.com/AlchemyCMS/alchemy_cms/pull/1430) ([tvdeyen](https://github.com/tvdeyen)) -## 3.5.0 (unreleased) +## 3.6.4 (2018-04-23) + +- Add more classes to YAML.safe_load [#1396](https://github.com/AlchemyCMS/alchemy_cms/pull/1396) ([tvdeyen](https://github.com/tvdeyen)) + +## 3.6.3 (2017-10-24) + +- Remove `:display` cancan alias [#1318](https://github.com/AlchemyCMS/alchemy_cms/pull/1318) by [tvdeyen](https://github.com/tvdeyen) + +## 3.6.2 (2017-09-01) + +- Handle custom errors in `Alchemy::Picture#url` [#1305](https://github.com/AlchemyCMS/alchemy_cms/pull/1305) by [tvdeyen](https://github.com/tvdeyen) +- Do not move elements in tidy cells task [#1303](https://github.com/AlchemyCMS/alchemy_cms/pull/1303) by [tvdeyen](https://github.com/tvdeyen) +- Add a store image file format rake task [#1302](https://github.com/AlchemyCMS/alchemy_cms/pull/1302) by [tvdeyen](https://github.com/tvdeyen) + +## 3.6.1 (2017-08-16) + +- Do not ask `systempage?` everytime we load the page definition [#1239](https://github.com/AlchemyCMS/alchemy_cms/pull/1283) by [tvdeyen](https://github.com/tvdeyen) + This speeds up rendering large sitemaps by about 6 times. + +## 3.6.0 (2017-06-20) + +__Notable Changes__ + +- The seeder does not generate default site and root page anymore (#1239) by tvdeyen + Alchemy handles this auto-magically now. No need to run `Alchemy::Seeder.seed!` any more |o/ +- Security: Sanitize ActiveRecord queries in `Alchemy::Element`, `Alchemy::Page` and + `Alchemy::PagesHelper` (#1257) by jessedoyle +- Remove post install message reference to the `alchemy` standalone installer (#1256) by jessedoyle +- Fixes tag filtering for pictures and attachments in overlay (#1266) by robinboening +- Fix js error on page#update with single quote in page name (#1263) by robinboening +- Change meta charset from 'utf8' to 'utf-8' (#1253) by rbjoern84 +- Render "text" as type for datepicker input fields (#1246) by robinboening +- Remove unused Page attr_accessors (#1240) by tvdeyen +- Permit search params while redirecting in library (#1236) by tvdeyen +- Only allow floats and ints as fixed ratio for crop (#1234) by tvdeyen +- Use at least dragonfly 1.0.7 (#1225) by tvdeyen +- Add handlebars-assets gem (#1203) by tvdeyen +- Add a new spinner animation (#1202) by tvdeyen +- Re-color the Turbolinks progressbar (#1199) by tvdeyen +- Use normal view for pages sort action (#1197) by tvdeyen +- Add srcset and sizes support for EssencePicture (#1193) by tvdeyen + +## 3.5.0 (2016-12-22) __New Features__ -* New API endpoint for retrieving a nested page tree (#1155) +- New API endpoint for retrieving a nested page tree (#1155) `api/pages/nested` returns a nested JSON tree of all pages. -* Add page and user seeding support (#1160) -* Files of attachments are replaceable now (#1167) -* Add fixed page attributes (#1168) +- Add page and user seeding support (#1160) +- Files of attachments are replaceable now (#1167) +- Add fixed page attributes (#1168) Page attributes can be defined as fixed_attributes to prevent changes by the user. -* Allow to declare which user role can edit page content on the page layout level. +- Allow to declare which user role can edit page content on the page layout level. __Notable Changes__ -* Removed the standalone installer (#1206) -* The essence date input field is now 100% width (#1191) -* The essence view partials don't get cached anymore (#1099) -* The essence editor partials don't get cached anymore (#1171) -* Removes update_essence_select_elements (#1103) -* The admin resource form now uses the datetime-picker instead of the date-picker for datetime fields. -* The `preview_mode_code` helper is moved to a partial in `alchemy/preview_mode_code`. (#1110) -* The `render_meta_data` helper is moved to a partial in `alchemy/pages/meta_data` and can be rendered with the same options as before but now passed in as locals. (#1110) -* The view helpers `preview_mode_code`, `render_meta_data`, `render_meta_tag`, `render_page_title`, `render_title_tag` are now deprecated. (#1110) -* An easy way to include several edit mode related partials is now available (#1120): +- Removed the standalone installer (#1206) +- The essence date input field is now 100% width (#1191) +- The essence view partials don't get cached anymore (#1099) +- The essence editor partials don't get cached anymore (#1171) +- Removes update_essence_select_elements (#1103) +- The admin resource form now uses the datetime-picker instead of the date-picker for datetime fields. +- The `preview_mode_code` helper is moved to a partial in `alchemy/preview_mode_code`. (#1110) +- The `render_meta_data` helper is moved to a partial in `alchemy/pages/meta_data` and can be rendered with the same options as before but now passed in as locals. (#1110) +- The view helpers `preview_mode_code`, `render_meta_data`, `render_meta_tag`, `render_page_title`, `render_title_tag` are now deprecated. (#1110) +- An easy way to include several edit mode related partials is now available (#1120): `render 'alchemy/edit_mode'` loads `menubar` and `preview_mode_code` at once -* Add support for Turbolinks 5.0 (#1095) -* Use Dragonfly middleware to render pictures and remove our custom solution (#1084) -* `image_size` option is now deprecated. Please use just `size` (#1084) -* `show_alchemy_picture_path` helper is now deprecated. Please use `picture.url` instead (#1084) -* Display download information on the Attachment Modal Dialog (#1137) -* Added foreign keys to important associations (#1149) -* Also destroy trashed elements when page gets destroyed (#1149) -* Upgrade tasks can now be run separately (#1152) -* Update to Tinymce 4.4.3 -* New sitemap UI (#1172) -* Removed picture cache flushing (#1185) -* Removed Mountpoint class (#1186) +- Add support for Turbolinks 5.0 (#1095) +- Use Dragonfly middleware to render pictures and remove our custom solution (#1084) +- `image_size` option is now deprecated. Please use just `size` (#1084) +- `show_alchemy_picture_path` helper is now deprecated. Please use `picture.url` instead (#1084) +- Display download information on the Attachment Modal Dialog (#1137) +- Added foreign keys to important associations (#1149) +- Also destroy trashed elements when page gets destroyed (#1149) +- Upgrade tasks can now be run separately (#1152) +- Update to Tinymce 4.4.3 +- New sitemap UI (#1172) +- Removed picture cache flushing (#1185) +- Removed Mountpoint class (#1186) __Fixed Bugs__ -* Fix setting of locale when `current_alchemy_user.language` doesn't return a Symbol (#1097) -* Presence validation of EssenceFile is not working (#1096) -* Allow to define unique nestable elements (#852) +- Fix setting of locale when `current_alchemy_user.language` doesn't return a Symbol (#1097) +- Presence validation of EssenceFile is not working (#1096) +- Allow to define unique nestable elements (#852) -## 3.4.2 (unreleased) +## 3.4.2 (2016-12-22) __Notable Changes__ -* Allow users to manually publish changes on global pages +- Allow users to manually publish changes on global pages __Fixed Bugs__ -* The `language_links` helper now only renders languages from the current site +- The `language_links` helper now only renders languages from the current site ## 3.4.1 (2016-08-31) __Fixed Bugs__ -* Remove trailing new lines in the AddImageFileFormatToAlchemyPictures migration. (#1107) +- Remove trailing new lines in the AddImageFileFormatToAlchemyPictures migration. (#1107) If you migrated already, use the `alchemy:upgrade:fix_picture_format` rake task. -* Don't overwrite the fallback options when rendering a picture (#1113) -* Fixes the messages mailer views generator (#1118) +- Don't overwrite the fallback options when rendering a picture (#1113) +- Fixes the messages mailer views generator (#1118) ## 3.4.0 (2016-08-02) __New Features__ -* `MessagesMailer` (formerly known as `Messages`) now inherits from `ApplicationMailer` +- `MessagesMailer` (formerly known as `Messages`) now inherits from `ApplicationMailer` when it is defined. -* Adds time based published pages: The public status of a page is now made of two time stamps: +- Adds time based published pages: The public status of a page is now made of two time stamps: `public_on` and `public_until` -* Send page expiration cache headers -* Adds an +EssencePictureView+ class responsible for rendering the `essence_picture_view` partial -* Adds a file type filter to file archive -* Allow setting the type of EssenceText input fields in the elements.yml via `settings[:input_type]` -* Adds support for defining custom searchable attributes in resources -* Automatically add tag management to admin module views, when the resource model +- Send page expiration cache headers +- Adds an +EssencePictureView+ class responsible for rendering the `essence_picture_view` partial +- Adds a file type filter to file archive +- Allow setting the type of EssenceText input fields in the elements.yml via `settings[:input_type]` +- Adds support for defining custom searchable attributes in resources +- Automatically add tag management to admin module views, when the resource model has been set to `acts_as_taggable`. -* Automatically add scope filters to admin module views, when the resource model +- Automatically add scope filters to admin module views, when the resource model has the class method `alchemy_resource_filters` defined. __Notable Changes__ -* `Messages` mailer class has been renamed to `MessagesMailer` -* Removed the auto-magically merge of Ability classes (#1022) -* Replace jQueryUI datepicker with $.datetimepicker -* Thumbnails now render in original file format, but GIFs will always be flattened -* Pictures will be rendered in original file format by default -* Allow SVG files to be rendered as EssencePicture -* When using Alchemy content outside of Alchemy, `current_ability` is no longer +- `Messages` mailer class has been renamed to `MessagesMailer` +- Removed the auto-magically merge of Ability classes (#1022) +- Replace jQueryUI datepicker with $.datetimepicker +- Thumbnails now render in original file format, but GIFs will always be flattened +- Pictures will be rendered in original file format by default +- Allow SVG files to be rendered as EssencePicture +- When using Alchemy content outside of Alchemy, `current_ability` is no longer included with `Alchemy::ControllerActions` to prevent method clashes. If you need access to `current_ability` you also need to include `Alchemy::AbilityHelper` -* Asset manifests are now installed into `vendor/assets` folder in order to provide easy customization +- Asset manifests are now installed into `vendor/assets` folder in order to provide easy customization Please don't use alchemy/custom files any more. Instead require your customizations in the manifests. -* Removes the default_scope from Language on_site current while ensuring to load languages by code +- Removes the default_scope from Language on_site current while ensuring to load languages by code from current site only. -* Removes the `Language.get_default` method alias for `Language.default` -* Move site select into pages and languages module to avoid confusion about curent site (#1067) -* List pages from all sites in currently locked pages tabs and Dashboard widget (#1067) -* The locked value on page is now a timestamp (`locked_at`), so we can order locked pages by (#1070) -* Persist user in dummy app -* When publishing a page with the publish button, `Page#public_on` does not get +- Removes the `Language.get_default` method alias for `Language.default` +- Move site select into pages and languages module to avoid confusion about curent site (#1067) +- List pages from all sites in currently locked pages tabs and Dashboard widget (#1067) +- The locked value on page is now a timestamp (`locked_at`), so we can order locked pages by (#1070) +- Persist user in dummy app +- When publishing a page with the publish button, `Page#public_on` does not get reset to the current time when it is already set and in the past, and `Page#public_until` does not get nilled when it is in the future. __Fixed Bugs__ -* Fix table width for attachments and resources on small window sizes. -* Generators don't delete directories any more (#850) -* Some elements crashed the backend's JS when being saved (#1091) +- Fix table width for attachments and resources on small window sizes. +- Generators don't delete directories any more (#850) +- Some elements crashed the backend's JS when being saved (#1091) ## 3.3.3 (2016-09-11) -* Fix bug that rendered duplicate nested elements within a cell after copying a parent element. +- Fix bug that rendered duplicate nested elements within a cell after copying a parent element. ## 3.3.2 (2016-08-02) -* Use relative url for page preview frame in order to prevent cross origin errors (#1076) +- Use relative url for page preview frame in order to prevent cross origin errors (#1076) ## 3.3.1 (2016-06-20) -* Fix use of Alchemy::Resource with namespaced models (#729) -* Allow setting the type of EssenceText input fields in the elements.yml via `settings[:input_type]` -* Admin locale switching does not switch language tree any more (#1065) -* Fixes bug where old site session causes not found errors (#1047) -* Fix inability to add nested elements on pages with cells (#1039) -* Skip upgrader if no element definitions are found (#1060) -* Fix selecting the active cell for elements with nested elements (#1041) +- Fix use of Alchemy::Resource with namespaced models (#729) +- Allow setting the type of EssenceText input fields in the elements.yml via `settings[:input_type]` +- Admin locale switching does not switch language tree any more (#1065) +- Fixes bug where old site session causes not found errors (#1047) +- Fix inability to add nested elements on pages with cells (#1039) +- Skip upgrader if no element definitions are found (#1060) +- Fix selecting the active cell for elements with nested elements (#1041) ## 3.3.0 (2016-05-18) __New Features__ -* Add support for Sprockets 3 -* Add support for jquery-rails 4.1 -* Show a welcome page, if no users or pages are present yet -* Namespace spec files -* Image library slideshow -* Global "current locked pages" tabs -* New option `linkable: false` for `EssencePicture` -* Allow custom routing for admin backend -* Resource forms can now have Tinymce enabled by adding `.tinymce` class -* `Alchemy::EssenceFile` now has a `link_text` attribute, so the editor is able to change the linked text of the download link. -* Enable to pass multiple page layout names to `on_page_layout` callbacks -* Client side rendering of the pages admin -* Deprecate `redirect_index` configuration -* Add Nestable elements feature -* Default site in seeder is now configurable -* Frontpage name and page layout are now editable when creating new language trees +- Add support for Sprockets 3 +- Add support for jquery-rails 4.1 +- Show a welcome page, if no users or pages are present yet +- Namespace spec files +- Image library slideshow +- Global "current locked pages" tabs +- New option `linkable: false` for `EssencePicture` +- Allow custom routing for admin backend +- Resource forms can now have Tinymce enabled by adding `.tinymce` class +- `Alchemy::EssenceFile` now has a `link_text` attribute, so the editor is able to change the linked text of the download link. +- Enable to pass multiple page layout names to `on_page_layout` callbacks +- Client side rendering of the pages admin +- Deprecate `redirect_index` configuration +- Add Nestable elements feature +- Default site in seeder is now configurable +- Frontpage name and page layout are now editable when creating new language trees __Notable Changes__ -* Essence generator does not namespace the model into `Alchemy` namespace anymore -* New simplified uploader that allows to drag and drop images onto the archive everywhere in your app +- Essence generator does not namespace the model into `Alchemy` namespace anymore +- New simplified uploader that allows to drag and drop images onto the archive everywhere in your app - Model names in uploader `allowed_filetypes` setting are now namespaced. Please be sure to run `rake alchemy:upgrade` to update your settings. -* Allow uppercase country codes -* Uses Time.current instead of Time.now for proper timezone support -* Adds year to `created_at` column of attachments table -* Removes "available contents" feature. -* Use Ransack for Admin Resources filtering, sorting and searching -* Renames Alchemy translation helpers from `_t` to `Alchemy.t` -* Do not append geometry string to preprocess option -* Skip the default locale in urls -* Add a proper index route and do not redirect to page anymore -* Updates Tinymce to 4.2.3 -* Moves page status info into reusable partial -* Refactors factories into individual requirable files -* Do not raise error if `element_ids` params is missing while ordering elements -* Removes old middleware for rescueing legacy sessions -* Use rails tag helpers instead of plain HTML for meta tags -* Remove the duplication of `#decription` vs. `#definition` -* Resource CSV export now includes ID column and does not truncate large text columns anymore -* `Alchemy::Attachment#urlname` now returns always an escaped urlname w/o format suffix and does not convert the `file_name` once on create anymore -* Speed up the admin interface significantly when handling a large amount of pages +- Allow uppercase country codes +- Uses Time.current instead of Time.now for proper timezone support +- Adds year to `created_at` column of attachments table +- Removes "available contents" feature. +- Use Ransack for Admin Resources filtering, sorting and searching +- Renames Alchemy translation helpers from `_t` to `Alchemy.t` +- Do not append geometry string to preprocess option +- Skip the default locale in urls +- Add a proper index route and do not redirect to page anymore +- Updates Tinymce to 4.2.3 +- Moves page status info into reusable partial +- Refactors factories into individual requirable files +- Do not raise error if `element_ids` params is missing while ordering elements +- Removes old middleware for rescueing legacy sessions +- Use rails tag helpers instead of plain HTML for meta tags +- Remove the duplication of `#decription` vs. `#definition` +- Resource CSV export now includes ID column and does not truncate large text columns anymore +- `Alchemy::Attachment#urlname` now returns always an escaped urlname w/o format suffix and does not convert the `file_name` once on create anymore +- Speed up the admin interface significantly when handling a large amount of pages __Fixed Bugs__ -* Add `locale` to `Alchemy::Language` to avoid errors for languages with missing locale files #831 -* Fixes `Alchemy::PageLayout.get_all_by_attributes` -* Fix tag list display in picture library -* Animated GIFs display correctly -* EssenceSelect grouped options tags -* Add missing element partials for dummy app -* Eliminate an SQL lookup on frontend cached element partials -* Add missing german and spanish translation for element toolbar -* Use the site_id parameter and the session only in the Admin area -* Render 404 if accessing an unpublished index page that has "on page layout" callbacks +- Add `locale` to `Alchemy::Language` to avoid errors for languages with missing locale files #831 +- Fixes `Alchemy::PageLayout.get_all_by_attributes` +- Fix tag list display in picture library +- Animated GIFs display correctly +- EssenceSelect grouped options tags +- Add missing element partials for dummy app +- Eliminate an SQL lookup on frontend cached element partials +- Add missing german and spanish translation for element toolbar +- Use the site_id parameter and the session only in the Admin area +- Render 404 if accessing an unpublished index page that has "on page layout" callbacks [Full Change Log](https://github.com/AlchemyCMS/alchemy_cms/compare/v3.2.1...v3.3.0) @@ -195,12 +713,12 @@ __Fixed Bugs__ __Fixed Bugs__ -* Fix constant lookup issues with registered abilites -* Fix: `EssenceSelect` grouped `select_values` -* Respect `:reverse` option when sorting elements -* Directly updates position in database while sorting contents -* Don't show trashed elements when using a fallback -* Fixes wrong week number in datepicker +- Fix constant lookup issues with registered abilites +- Fix: `EssenceSelect` grouped `select_values` +- Respect `:reverse` option when sorting elements +- Directly updates position in database while sorting contents +- Don't show trashed elements when using a fallback +- Fixes wrong week number in datepicker [Full Change Log](https://github.com/AlchemyCMS/alchemy_cms/compare/v3.2.0...v3.2.1) diff --git a/Gemfile b/Gemfile index d829ac1b06b..8f691833853 100644 --- a/Gemfile +++ b/Gemfile @@ -1,49 +1,34 @@ -source 'https://rubygems.org' +# frozen_string_literal: true +source "https://rubygems.org" gemspec -# Profiling -gem 'rack-mini-profiler', group: :development, require: false +rails_version = ENV.fetch("RAILS_VERSION", 6.0).to_f +gem "rails", "~> #{rails_version}.0" -gem 'sqlite3' if ENV['DB'].nil? || ENV['DB'] == 'sqlite' -gem 'mysql2', '~> 0.3.18' if ENV['DB'] == 'mysql' -gem 'pg' if ENV['DB'] == 'postgresql' -gem 'sassc-rails' +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" group :development, :test do - gem 'jasmine-rails', github: 'searls/jasmine-rails' - gem 'jasmine-jquery-rails', github: 'travisjeffery/jasmine-jquery-rails' - gem 'simplecov', require: false - if ENV['TRAVIS'] - gem 'codeclimate-test-reporter', '~> 1.0', require: false - end - unless ENV['CI'] - gem 'launchy' - gem 'annotate' - gem 'bumpy' - gem 'yard' - gem 'redcarpet' - gem 'pry-byebug' - gem 'spring' - gem 'spring-commands-rspec' - gem 'rubocop', require: false + if ENV["TRAVIS"] + 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.93.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 "rufo", require: false end - gem 'capybara', '~> 2.4' - gem 'database_cleaner', '~> 1.3' - gem 'factory_girl_rails', '~> 4.5' - gem 'poltergeist', '~> 1.10' - gem 'rspec-activemodel-mocks', '~> 1.0' - gem 'rspec-rails', '~> 3.0' - gem 'shoulda-matchers', '~> 3.1' -end - -# We need this if we want to start the dummy app in development mode -group :development, :production do - gem 'quiet_assets' -end - -# We need this if we want to start the dummy app in production, ie on Teatro.io -group :production do - gem 'uglifier', '>= 1.0.3' - gem 'therubyracer' end diff --git a/README.md b/README.md index af06d7bbf9c..427a10f4494 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,60 @@ +# AlchemyCMS + [![Gem Version](https://badge.fury.io/rb/alchemy_cms.svg)](http://badge.fury.io/rb/alchemy_cms) -[![Build Status](https://travis-ci.org/AlchemyCMS/alchemy_cms.svg?branch=master)](https://travis-ci.org/AlchemyCMS/alchemy_cms) -[![Code Climate](https://codeclimate.com/github/AlchemyCMS/alchemy_cms.svg)](https://codeclimate.com/github/AlchemyCMS/alchemy_cms) -[![Test Coverage](https://codeclimate.com/github/AlchemyCMS/alchemy_cms/badges/coverage.svg)](https://codeclimate.com/github/AlchemyCMS/alchemy_cms) -[![Slack Status](http://slack.alchemy-cms.com/badge.svg)](http://slack.alchemy-cms.com) +[![Build Status](https://travis-ci.com/AlchemyCMS/alchemy_cms.svg?branch=master)](https://travis-ci.com/AlchemyCMS/alchemy_cms) +[![Maintainability](https://api.codeclimate.com/v1/badges/196c56c56568ed24a697/maintainability)](https://codeclimate.com/github/AlchemyCMS/alchemy_cms/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/196c56c56568ed24a697/test_coverage)](https://codeclimate.com/github/AlchemyCMS/alchemy_cms/test_coverage) +[![Depfu](https://badges.depfu.com/badges/ebe56d2dd7b7044a8ae700cc81212a8e/overview.svg)](https://depfu.com/github/AlchemyCMS/alchemy_cms?project_id=4600) -**CAUTION: This master branch is a development branch that *can* contain bugs. For productive environments you should use the [current Ruby gem version](https://rubygems.org/gems/alchemy_cms), or the [latest stable branch (3.4-stable)](https://github.com/AlchemyCMS/alchemy_cms/tree/3.4-stable).** +[![Slack Status](https://slackin.alchemy-cms.com/badge.svg)](https://slackin.alchemy-cms.com) +[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) +[![Backers on Open Collective](https://opencollective.com/alchemy_cms/backers/badge.svg?color=blue)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/alchemy_cms/sponsors/badge.svg?color=blue)](#sponsors) -## About + -![Alchemy CMS](app/assets/images/alchemy/alchemy-logo.svg) +Alchemy is an open source CMS engine written in Ruby on Rails. -Alchemy is a powerful, flexible and user centric Rails CMS. +Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [guidelines](https://guides.alchemy-cms.com). -Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [guidelines](http://guides.alchemy-cms.com/stable/). +**CAUTION: This master branch is a development branch that *can* contain bugs. For productive environments you should use the [current Ruby gem version](https://rubygems.org/gems/alchemy_cms), or the [latest stable branch (5.0-stable)](https://github.com/AlchemyCMS/alchemy_cms/tree/5.0-stable).** -## Features +## ✅ Features -- Highly flexible templating that completely separates content from markup -- End-User centric graphical user interface +- Flexible templating that separates content from markup +- A rich RESTful API +- Intuitive admin interface with live preview - Multi language and multi domain - SEO friendly urls - User Access Control - Build in contact form mailer - Attachments and downloads -- On-the-fly image rendering +- On-the-fly image cropping and resizing - Extendable via Rails engines - Integrates into existing Rails Apps +- Resourceful Rails admin - Flexible caching - Hostable on any Server that supports Ruby on Rails, a SQL Database and ImageMagick +## 🎮 Demo + +Deploy your own free demo on Heroku + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/AlchemyCMS/alchemy-demo) -## Rails Version +or visit the existing demo at https://alchemy-demo.herokuapp.com -**This version of Alchemy CMS runs with Rails 4.2** +- Login: `demo` +- Password: `demo123` +## 🚂 Rails Version + +**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). * For a Rails 3.1 compatible version use the [`2.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/2.1-stable). @@ -43,42 +62,54 @@ Read more about Alchemy on the [website](https://alchemy-cms.com) and in the [gu * For a Rails 2.3 compatible version use the [`1.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/1.6-stable). -## Ruby Version +## 💎 Ruby Version + +Alchemy runs with Ruby >= 2.3.0. -Alchemy runs with Ruby >= 2.0.0. +For a Ruby 2.2 compatible version use the [`4.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/4.1-stable). + +For a Ruby 2.1 compatible version use the [`3.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.6-stable). + +For a Ruby 2.0.0 compatible version use the [`3.2-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.2-stable). For a Ruby 1.9.3 compatible version use the [`3.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.1-stable). For a Ruby 1.8.7 compatible version use the [`2.3-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/2.3-stable). -## Installation +## ⌨️ Installation -#### 1. Add the Alchemy gem: +### Stand Alone Installation -Put this into your `Gemfile`: +If you do not have a Rails project yet or just want to check out Alchemy, then use this Rails template. -```ruby -gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: 'master' -``` +Make sure you have Rails installed first: -**NOTE:** You normally want to use a stable branch, like `3.4-stable`. +``` +$ gem install rails +``` -If you want to use Russian translation and have better i18n support, you should put: +Then create a new Rails project with: -```ruby -gem 'russian', '~> 0.6.0' +``` +$ rails new -m https://raw.githubusercontent.com/AlchemyCMS/rails-templates/master/all.rb ``` -or gem with similar functionality into your Gemfile. +and follow the on screen instructions. -#### 2. Update your bundle: +### Manual Installation + +If you want to manually install Alchemy into your Rails project follow these steps. + +#### Add the Alchemy gem: + +Put Alchemy into your `Gemfile` with: -```shell -$ bundle install +``` +$ bundle add alchemy_cms ``` -#### 3. Set the authentication user +#### Set the authentication user Now you have to decide, if you want to use your own user model or if you want to use the Devise based user model that Alchemy provides and was extracted [into its own gem](https://github.com/AlchemyCMS/alchemy-devise). @@ -87,17 +118,14 @@ the Devise based user model that Alchemy provides and was extracted [into its ow If you don't have your own user class, you can use the Alchemy user model. Just add the following gem into your `Gemfile`: -```ruby -gem 'alchemy-devise', github: 'AlchemyCMS/alchemy-devise', branch: 'master' +``` +$ bundle add alchemy-devise ``` -**NOTE:** You normally want to use a stable branch, like `3.4-stable`. - -Then run: +Then run the `alchemy-devise` installer: -```shell -$ bundle install -$ bin/rake alchemy_devise:install:migrations +```bash +$ bin/rails g alchemy:devise:install ``` ##### Use your User model @@ -108,11 +136,12 @@ The best practice is to use an initializer: ```ruby # config/initializers/alchemy.rb -Alchemy.user_class_name = 'YourUserClass' # Defaults to 'User' -Alchemy.current_user_method = 'current_admin_user' # Defaults to 'current_user' -Alchemy.signup_path = '/your/signup/path' # Defaults to '/signup' -Alchemy.login_path = '/your/login/path' # Defaults to '/login' -Alchemy.logout_path = '/your/logout/path' # Defaults to '/logout' +Alchemy.user_class_name = 'YourUserClass' # Defaults to 'User' +Alchemy.current_user_method = 'current_admin_user' # Defaults to 'current_user' +Alchemy.signup_path = '/your/signup/path' # Defaults to '/signup' +Alchemy.login_path = '/your/login/path' # Defaults to '/login' +Alchemy.logout_path = '/your/logout/path' # Defaults to '/logout' +Alchemy.logout_method = 'http_verb_for_logout' # Defaults to 'delete' ``` The only thing Alchemy needs to know from your user class is the `alchemy_roles` method. @@ -137,8 +166,8 @@ Please follow [this guide](http://guides.alchemy-cms.com/stable/custom_authentic **After** you set the user model you need to run the Alchemy install task: -```shell -$ bin/rake alchemy:install +```bash +$ bin/rails alchemy:install ``` Now everything should be set up and you should be able to visit the Alchemy Dashboard at: @@ -148,24 +177,25 @@ Now everything should be set up and you should be able to visit the Alchemy Dash *) Use your custom path if you mounted Alchemy at something else then `'/'` -## Customizing +## 🏗 Customization -Alchemy has very flexible ways to organize and manage content. Please be sure to read [the introduction guide](http://guides.alchemy-cms.com/stable/index.html) in order to understand the basic idea of how Alchemy works. +Alchemy has very flexible ways to organize and manage content. Please be sure to read [the introduction guide](https://guides.alchemy-cms.com/about.html) in order to understand the basic idea of how Alchemy works. ### Custom Controllers -Beginning with Alchemy 3.1 we do not patch the `ApplicationController` anymore. If you have controllers that loads Alchemy content or uses Alchemy helpers in the views (i.e. `render_navigation` or `render_elements`) you can either inherit from `Alchemy::BaseController` or you `include Alchemy::ControllerActions` in your controller (**that's the recommended way**). +Beginning with Alchemy 3.1 we do not patch the `ApplicationController` anymore. If you have controllers that loads Alchemy content or uses Alchemy helpers in the views (i.e. `render_menu` or `render_elements`) you can either inherit from `Alchemy::BaseController` or you `include Alchemy::ControllerActions` in your controller (**that's the recommended way**). ### Custom admin interface routing By default, Alchemy Dashboard is accessible at . You can change this by setting `Alchemy.admin_path` and `Alchemy.admin_constraints`. + For example, these settings: ```ruby # config/initializers/alchemy.rb -Alchemy.admin_path = '/backend' +Alchemy.admin_path = 'backend' Alchemy.admin_constraints = {subdomain: 'hidden'} ``` @@ -175,26 +205,33 @@ will move the dashboard to . Alchemy uses the Dragonfly gem to render pictures on-the-fly. -To make this as performant as possible the rendered picture gets stored into `public/pictures` -so the web server can pick up the file and serve it without hitting the Rails process at all. +To make this as performant as possible the rendered picture gets stored into `public/pictures` so the web server can pick up the file and serve it without hitting the Rails process at all. -This may or may not what you want. Especially for multi server setups you eventually want to use -something like S3. +This may or may not what you want. Especially for multi server setups you eventually want to use something like S3. Please follow the guidelines about picture caching on the Dragonfly homepage for further instructions: -http://markevans.github.io/dragonfly/cache/ +http://markevans.github.io/dragonfly/cache +We also provide an [extension for Cloudinary](https://github.com/AlchemyCMS/alchemy_cloudinary) -## Upgrading +## 🌍 Localization -The Alchemy team takes upgrades very seriously and tries to make them as smooth as we can. -Therefore we have build in upgrade tasks, that try to automate as much as possible. +Alchemy ships with one default English translation for the admin interface. If you want to use the admin interface in other languages please have a look at the [`alchemy_i18n` project](https://github.com/AlchemyCMS/alchemy_i18n). -That's why after updating the Alchemy gem you should **always run the upgrader**: +## ✨ Upgrading -```shell +We, the Alchemy team, take upgrades very seriously and we try to make them as smooth as possible. Therefore we have build an upgrade task, that tries to automate the upgrade procedure as much as possible. + +That's why after the Alchemy gem has been updated, with explicit call to: + +```bash $ bundle update alchemy_cms +``` + +you should **always run the upgrader**: + +```bash $ bin/rake alchemy:upgrade ``` @@ -207,11 +244,11 @@ Also, `git diff` is your friend. ### Customize the upgrade preparation -The Alchemy upgrader comes prepared with with several rake tasks in a specific order. +The Alchemy upgrader comes prepared with several rake tasks in a specific order. This is sometimes not what you want or could even break upgrades. In order to customize the upgrade preparation process you can instead run each of the tasks on their own. -```shell +```bash $ bin/rake alchemy:install:migrations $ bin/rake db:migrate $ bin/rake alchemy:db:seed @@ -227,7 +264,7 @@ we can't ensure smooth upgrades for you. You can also run an individual upgrade on its own: -```shell +```bash $ bin/rake -T alchemy:upgrade ``` @@ -235,38 +272,38 @@ provides you with a list of each upgrade you can run individually. #### Example -```shell -$ bin/rake alchemy:upgrade:3.2 +```bash +$ bin/rake alchemy:upgrade:4.1 ``` -runs only the Alchemy 3.2 upgrade +runs only the Alchemy 4.1 upgrade -## Deployment +## 🚀 Deployment Alchemy has an official Capistrano extension which takes care of everything you need to deploy an Alchemy site. -Please use https://github.com/AlchemyCMS/capistrano-alchemy, if you want to deploy with Capistrano. +Please use [capistrano-alchemy](https://github.com/AlchemyCMS/capistrano-alchemy), if you want to deploy with Capistrano. ### Without Capistrano If you don't use Capistrano you have to **make sure** that the `uploads`, `tmp/cache/assets`, `public/assets` and `public/pictures` folders get **shared between deployments**, otherwise you **will loose data**. No, not really, but you know, just keep them in sync. -## Testing +## 🚧 Testing -If you want to contribute to Alchemy ([and we encourage you to do so](https://github.com/AlchemyCMS/alchemy_cms/blob/master/CONTRIBUTING.md)) we have a strong test suite that helps you to not break anything. +If you want to contribute to Alchemy ([and we encourage you to do so](CONTRIBUTING.md)) we have a strong test suite that helps you to not break anything. ### Preparation First of all you need to clone your fork to your local development machine. Then you need to install the dependencies with bundler. -```shell +```bash $ bundle install ``` To prepare the tests of your Alchemy fork please make sure to run the preparation task: -```shell +```bash $ bundle exec rake alchemy:spec:prepare ``` @@ -274,13 +311,13 @@ to set up the database for testing. ### Run your tests with: -```shell +```bash $ bundle exec rspec ``` **Alternatively** you can just run*: -```shell +```bash $ bundle exec rake ``` @@ -290,42 +327,52 @@ $ bundle exec rake You can even start the dummy app and use it to manually test your changes with: -```shell +```bash $ cd spec/dummy -$ bin/rake db:setup +$ bin/setup $ bin/rails s ``` -**A note about RSpec version:** -Alchemy specs are written **in RSpec 3**. Please **do not use deprecated RSpec 2.x syntax**. Thanks +## 📦 Releasing + +### Update the changelog + +```bash +$ export GITHUB_ACCESS_TOKEN=... +$ PREVIOUS_VERSION=4.1.0 bundle exec rake alchemy:changelog:update +``` + +### Release a new version + +```bash +$ bundle exec rake release +``` -## Getting Help +## ❓Getting Help -* Read the guidelines: http://guides.alchemy-cms.com. -* Read the documentation: http://rubydoc.info/github/AlchemyCMS/alchemy_cms -* If you found a bug please use the [issue tracker on Github](https://github.com/AlchemyCMS/alchemy_cms/issues). -* For questions about general usage please use [Stack Overflow](http://stackoverflow.com/questions/tagged/alchemy-cms), [the User Group](http://groups.google.com/group/alchemy-cms) or the [IRC channel](irc://irc.freenode.net#alchemy_cms). +* Read the guidelines: https://guides.alchemy-cms.com. +* Read the documentation: https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms +* If you think you found a bug please use the [issue tracker on Github](https://github.com/AlchemyCMS/alchemy_cms/issues). +* For questions about general usage please use [Stack Overflow](http://stackoverflow.com/questions/tagged/alchemy-cms) or the [Slack](https://slackin.alchemy-cms.com). * New features should be discussed on our [Trello Board](https://trello.com/alchemycms). -**PLEASE** don't use the Github issues for feature requests. If you want to contribute to Alchemy please [read the contribution guidelines](https://github.com/AlchemyCMS/alchemy_cms/blob/master/CONTRIBUTING.md) before doing so. +**PLEASE** don't use the Github issues for feature requests. If you want to contribute to Alchemy please [read the contribution guidelines](CONTRIBUTING.md) before doing so. -## Resources +## 🔗 Resources -* Homepage: -* Live-Demo: (user: demo, password: demo123) -* API Documentation: +* Homepage: +* Live-Demo: (user: demo, password: demo123) +* API Documentation: * Issue-Tracker: * Sourcecode: -* User Group: -* IRC Channel: -* Discussion Board: +* Slack: * Twitter: -## Authors +## ✍️ Authors * Thomas von Deyen: * Robin Böning: @@ -334,15 +381,47 @@ Alchemy specs are written **in RSpec 3**. Please **do not use deprecated RSpec 2 * Carsten Fregin: -## License +## 🏅 Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + -* BSD: +## 💵 Backers -## Spread the love +Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/alchemy_cms#backer)] -If you like Alchemy, please help us to spread the word about Alchemy and star this repo [on GitHub](https://github.com/AlchemyCMS/alchemy_cms), upvote it [on The Ruby Toolbox](https://www.ruby-toolbox.com/projects/alchemy_cms), mention us [on Twitter](https://twitter.com/alchemy_cms) and vote for it [on Bitnami](https://bitnami.com/stack/alchemy). + + + +## 💰 Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/alchemy_cms#sponsor)] + + + + + + + + + + + + + + +## 📜 License + +[BSD-3-Clause](LICENSE) + + +## 📢 Spread the love + +If you like Alchemy, please help us to spread the word about Alchemy and star this repo [on GitHub](https://github.com/AlchemyCMS/alchemy_cms), upvote it [on The Ruby Toolbox](https://www.ruby-toolbox.com/projects/alchemy_cms), mention us [on Twitter](https://twitter.com/alchemy_cms). That will help us to keep Alchemy awesome. -Thank you <3! +Thank you! + +## ❤️ diff --git a/Rakefile b/Rakefile index c067d675987..6d8bd6c2bfd 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -#!/usr/bin/env rake begin require 'bundler/setup' rescue LoadError @@ -24,7 +23,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('app/**/*.rb') end -APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' require 'rspec/core' @@ -40,7 +39,37 @@ namespace :alchemy do namespace :spec do desc "Prepares database for testing Alchemy" task :prepare do - system 'cd spec/dummy && RAILS_ENV=test bundle exec rake db:drop db:create db:migrate:reset && cd -' + system( + <<~BASH + cd spec/dummy && \ + export RAILS_ENV=test && \ + bin/rake db:create && \ + bin/rake db:environment:set && \ + bin/rake db:migrate:reset && \ + bin/rails g alchemy:install --skip --skip-demo-files --auto-accept && \ + cd - + BASH + ) || fail + end + end + + namespace :changelog do + desc "Update CHANGELOG from GitHub (Set GITHUB_ACCESS_TOKEN and PREVIOUS_VERSION to a version you want to write changelog changes for)" + task :update do + original_file = './CHANGELOG.md' + new_file = original_file + '.new' + backup = original_file + '.old' + changes = `git rev-list v#{ENV['PREVIOUS_VERSION']}..HEAD | bundle exec github_fast_changelog AlchemyCMS/alchemy_cms` + File.open(new_file, 'w') do |fo| + fo.puts changes + File.foreach(original_file) do |li| + fo.puts li + end + fo.puts "" + end + File.rename(original_file, backup) + File.rename(new_file, original_file) + File.delete(backup) end end end diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index f22dbbaa0a5..70b3a73ab7a 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -1,5 +1,5 @@ # coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'alchemy/version' @@ -8,48 +8,58 @@ 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 4' - gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails 4 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.0.0' + gem.required_ruby_version = '>= 2.3.0' gem.license = 'BSD New' gem.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) } gem.require_paths = ['lib'] - gem.add_runtime_dependency 'active_model_serializers', ['~> 0.9.0'] - gem.add_runtime_dependency 'acts_as_list', ['~> 0.3'] - gem.add_runtime_dependency 'acts-as-taggable-on', ['~> 4.0'] + gem.add_runtime_dependency 'active_model_serializers', ['~> 0.10.0'] + gem.add_runtime_dependency 'acts_as_list', ['>= 0.3', '< 2'] gem.add_runtime_dependency 'awesome_nested_set', ['~> 3.1'] - gem.add_runtime_dependency 'bourbon', ['~> 4.2'] - gem.add_runtime_dependency 'cancancan', ['~> 1.9'] - gem.add_runtime_dependency 'coffee-rails', ['~> 4.0'] - gem.add_runtime_dependency 'dragonfly', ['~> 1.0.1'] + gem.add_runtime_dependency 'cancancan', ['>= 2.1', '< 4.0'] + gem.add_runtime_dependency 'coffee-rails', ['>= 4.0', '< 6.0'] + gem.add_runtime_dependency 'dragonfly', ['~> 1.0', '>= 1.0.7'] gem.add_runtime_dependency 'dragonfly_svg', ['~> 0.0.4'] + gem.add_runtime_dependency 'gutentag', ['~> 2.2', '>= 2.2.1'] gem.add_runtime_dependency 'handlebars_assets', ['~> 0.23'] - gem.add_runtime_dependency 'jquery-rails', ['~> 4.0'] - gem.add_runtime_dependency 'jquery-ui-rails', ['~> 5.0.0'] - gem.add_runtime_dependency 'kaminari', ['~> 0.15'] - gem.add_runtime_dependency 'magiclabs-userstamp', ['~> 3.0'] + gem.add_runtime_dependency 'jquery-rails', ['~> 4.0', '>= 4.0.4'] + gem.add_runtime_dependency 'jquery-ui-rails', ['~> 6.0'] + 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', ['>= 4.2.0', '< 5.0'] - gem.add_runtime_dependency 'ransack', ['~> 1.4'] + 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'] + gem.add_runtime_dependency 'responders', ['>= 2.0', '< 4.0'] + gem.add_runtime_dependency 'sassc-rails', ['~> 2.1'] gem.add_runtime_dependency 'select2-rails', ['>= 3.5.9.1', '< 4.0'] - gem.add_runtime_dependency 'simple_form', ['~> 3.0'] + 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', ['~> 6.0'] + gem.add_development_dependency 'puma', ['~> 5.0'] + gem.add_development_dependency 'rails-controller-testing', ['~> 1.0'] + gem.add_development_dependency 'rspec-activemodel-mocks', ['~> 1.0'] + gem.add_development_dependency 'rspec-rails', ['>= 4.0.0.beta2'] + gem.add_development_dependency 'simplecov', ['~> 0.17.1'] + gem.add_development_dependency 'webdrivers', ['~> 4.0'] + gem.add_development_dependency 'webmock', ['~> 3.3'] + gem.add_development_dependency 'shoulda-matchers', ['~> 4.0'] gem.post_install_message = <<-MSG ------------------------------------------------------------- Thank you for installing Alchemy CMS ------------------------------------------------------------- -- Create a new standalone Alchemy project: - - $ alchemy new my_project_name - - Complete the installation in an existing Rails application: $ bin/rake alchemy:install @@ -62,9 +72,8 @@ and follow the onscreen instructions. Need help? Try: -* http://stackoverflow.com/questions/tagged/alchemy-cms -* http://groups.google.com/group/alchemy-cms -* irc://irc.freenode.net#alchemy_cms +* https://stackoverflow.com/questions/tagged/alchemy-cms +* https://slackin.alchemy-cms.com ------------------------------------------------------------- MSG diff --git a/app/assets/config/alchemy_manifest.js b/app/assets/config/alchemy_manifest.js new file mode 100644 index 00000000000..383a389638e --- /dev/null +++ b/app/assets/config/alchemy_manifest.js @@ -0,0 +1,15 @@ +//= link alchemy/admin/all.css +//= link alchemy/admin/all.js +//= link alchemy/preview.js +//= link alchemy/menubar.css +//= link alchemy/menubar.js +//= link alchemy/print.css +//= link alchemy/welcome.css +//= link tinymce/plugins/alchemy_link/plugin.min.js +//= link tinymce/tinymce.min.js +//= link_directory ../stylesheets/tinymce/skins/alchemy/ .css +//= link_directory ../stylesheets/tinymce/skins/alchemy/img/ +//= link_directory ../stylesheets/tinymce/skins/alchemy/fonts/ +//= link_tree ../images/alchemy/ +//= link_tree ../../../vendor/assets/fonts/ +//= link_tree ../../../vendor/assets/images/ diff --git a/app/assets/fonts/alchemy/icons.eot b/app/assets/fonts/alchemy/icons.eot deleted file mode 100755 index 74b1e0813cd..00000000000 Binary files a/app/assets/fonts/alchemy/icons.eot and /dev/null differ diff --git a/app/assets/fonts/alchemy/icons.svg b/app/assets/fonts/alchemy/icons.svg deleted file mode 100755 index 0f9a13f7b0d..00000000000 --- a/app/assets/fonts/alchemy/icons.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - -Generated by IcoMoon - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/assets/fonts/alchemy/icons.ttf b/app/assets/fonts/alchemy/icons.ttf deleted file mode 100755 index ee00ecbdaa7..00000000000 Binary files a/app/assets/fonts/alchemy/icons.ttf and /dev/null differ diff --git a/app/assets/fonts/alchemy/icons.woff b/app/assets/fonts/alchemy/icons.woff deleted file mode 100755 index c7c5668b734..00000000000 Binary files a/app/assets/fonts/alchemy/icons.woff and /dev/null differ diff --git a/app/assets/images/alchemy/alchemy-logo.png b/app/assets/images/alchemy/alchemy-logo.png new file mode 100644 index 00000000000..300170436ac Binary files /dev/null and b/app/assets/images/alchemy/alchemy-logo.png differ diff --git a/app/assets/images/alchemy/alchemy-logo.svg b/app/assets/images/alchemy/alchemy-logo.svg index 61b9f4e923a..26fbd094609 100644 --- a/app/assets/images/alchemy/alchemy-logo.svg +++ b/app/assets/images/alchemy/alchemy-logo.svg @@ -1,70 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/assets/images/alchemy/favicon.ico b/app/assets/images/alchemy/favicon.ico index 0c3761001d9..d7a87e6fa67 100644 Binary files a/app/assets/images/alchemy/favicon.ico and b/app/assets/images/alchemy/favicon.ico differ diff --git a/app/assets/images/alchemy/icon-white.svg b/app/assets/images/alchemy/icon-white.svg new file mode 100644 index 00000000000..4ed2772d03b --- /dev/null +++ b/app/assets/images/alchemy/icon-white.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/alchemy/icon.svg b/app/assets/images/alchemy/icon.svg index 17f4623b334..ed5a99352fc 100644 --- a/app/assets/images/alchemy/icon.svg +++ b/app/assets/images/alchemy/icon.svg @@ -1,14 +1 @@ - - - - - - - - - - + diff --git a/app/assets/images/alchemy/icons.png b/app/assets/images/alchemy/icons.png deleted file mode 100644 index c4e2a0e2917..00000000000 Binary files a/app/assets/images/alchemy/icons.png and /dev/null differ diff --git a/app/assets/images/alchemy/missing-image.svg b/app/assets/images/alchemy/missing-image.svg new file mode 100644 index 00000000000..57897792ab3 --- /dev/null +++ b/app/assets/images/alchemy/missing-image.svg @@ -0,0 +1 @@ + diff --git a/app/assets/images/alchemy/ui-icons_666666_256x240.png b/app/assets/images/alchemy/ui-icons_666666_256x240.png deleted file mode 100644 index a32c57d80be..00000000000 Binary files a/app/assets/images/alchemy/ui-icons_666666_256x240.png and /dev/null differ diff --git a/app/assets/images/sassy-ie-overlay.png b/app/assets/images/sassy-ie-overlay.png deleted file mode 100644 index a7fddca2765..00000000000 Binary files a/app/assets/images/sassy-ie-overlay.png and /dev/null differ diff --git a/app/assets/javascripts/alchemy/admin.js b/app/assets/javascripts/alchemy/admin.js index 37525b24505..32c2d118e36 100644 --- a/app/assets/javascripts/alchemy/admin.js +++ b/app/assets/javascripts/alchemy/admin.js @@ -3,23 +3,17 @@ //= require jquery2 //= require jquery_ujs //= require turbolinks -//= require jquery-ui/draggable -//= require jquery-ui/effect-drop -//= require jquery-ui/effect-fade -//= require jquery-ui/sortable -//= require jquery-ui/tabs +//= require jquery-ui/effects/effect-fade +//= require jquery-ui/widgets/draggable +//= require jquery-ui/widgets/sortable +//= require jquery-ui/widgets/tabs //= require tinymce/tinymce.min //= require_tree ../../../../vendor/assets/javascripts/jquery_plugins/ +//= require flatpickr/flatpickr.min //= require clipboard.min //= require keymaster //= require requestAnimationFrame //= require select2 -//= require select2_locale_de -//= require select2_locale_es -//= require select2_locale_fr -//= require select2_locale_nl -//= require select2_locale_ru -//= require select2_locale_it //= require handlebars //= require alchemy/templates //= require alchemy/alchemy.base @@ -34,10 +28,10 @@ //= require alchemy/alchemy.dragndrop //= require alchemy/alchemy.element_editors //= require alchemy/alchemy.elements_window +//= require alchemy/alchemy.fixed_elements //= 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 @@ -51,5 +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 eee46b61d74..20b7de18a87 100644 --- a/app/assets/javascripts/alchemy/alchemy.base.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.base.js.coffee @@ -62,14 +62,14 @@ $.extend Alchemy, removePicture: (selector) -> $form_field = $(selector) $element = $form_field.closest(".element-editor") - if $form_field + $content = $form_field.closest(".content_editor") + if $form_field[0] $form_field.val "" - $form_field.prev().remove() - $form_field.parent().addClass "missing" + $content.find(".thumbnail_background").html('') Alchemy.setElementDirty $element false - # Initializes all select tag with .alchemy_selectbox class as selectBoxIt instance + # Initializes all select tag with .alchemy_selectbox class as select2 instance # Pass a jQuery scope to only init a subset of selectboxes. SelectBox: (scope) -> $("select.alchemy_selectbox", scope).select2 @@ -77,43 +77,6 @@ $.extend Alchemy, dropdownAutoWidth: true return - # Selects cell tab for given name. - # Creates it if it's not present yet. - selectOrCreateCellTab: (cell_name, label) -> - $cells = $('#cells') - $tab = $("#cell_#{cell_name}") - if $tab.length == 0 - $("
  • #{label}
  • ") - .appendTo('#cells .ui-tabs-nav') - $tab = $("
    ") - $cells.append($tab) - $cells.tabs().tabs('refresh') - $cells.tabs().tabs('option', 'active', $('#cells > div').index($tab)) - return - - # Inits the cell tabs - buildTabbedCells: (label) -> - $cells = $('
    ') - $('#cell_for_other_elements').wrap($cells) - $('#cells').prepend("") - .tabs 'paging', - follow: true - followOnSelect: true - return - - # Logs exception to js console, if present. - debug: (e) -> - if window["console"] - console.debug e - console.trace() - return - - # Logs errors to js console, if present. - log_error: (e) -> - if window["console"] - console.error e - return - getUrlParam: (name) -> results = new RegExp("[\\?&]" + name + "=([^&#]*)").exec(window.location.href) results[1] or 0 if results diff --git a/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee b/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee index 0e24fa3fb05..13134a0054b 100644 --- a/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee @@ -23,9 +23,11 @@ Alchemy.Buttons = spinner = new Alchemy.Spinner('small') $button.data('content', $button.html()) $button.attr('disabled', true) + $button.attr('tabindex', '-1') $button.addClass('disabled') $button.css width: $button.outerWidth() + height: $button.outerHeight() $button.empty() spinner.spin($button) return true @@ -36,6 +38,8 @@ Alchemy.Buttons = $button = $(this) $button.removeClass('disabled') $button.removeAttr('disabled') + $button.removeAttr('tabindex') $button.css("width", "") + $button.css("height", "") $button.html($button.data('content')) return true diff --git a/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee index 412108140d3..58f4201c88d 100644 --- a/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee @@ -21,7 +21,7 @@ class window.Alchemy.ConfirmDialog extends Alchemy.Dialog build_buttons: -> $btn_container = $('
    ') - @cancel_button = $("") + @cancel_button = $("") @ok_button = $("") $btn_container.append(@cancel_button) $btn_container.append(@ok_button) diff --git a/app/assets/javascripts/alchemy/alchemy.datepicker.js.coffee b/app/assets/javascripts/alchemy/alchemy.datepicker.js.coffee index d9d0307a26c..a62e1f3a46c 100644 --- a/app/assets/javascripts/alchemy/alchemy.datepicker.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.datepicker.js.coffee @@ -3,33 +3,27 @@ window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' $.extend Alchemy, Datepicker: (scope) -> - options = - format: Alchemy.t('formats.datetime') - formatDate: Alchemy.t('formats.date') - formatTime: Alchemy.t('formats.time') - dayOfWeekStart: Alchemy.t('formats.start_of_week') - onSelectDate: -> - Alchemy.setElementDirty $(this).closest(".element-editor") - - datepicker_options = $.extend {}, options, - format: options.formatDate - timepicker: false - - timepicker_options = $.extend {}, options, - format: options.formatTime - datepicker: false - - $.datetimepicker.setLocale(Alchemy.locale); - - # Initializes the datepickers and disables the browsers default Datepicker - # unless the browser is iOS. - $('input[type="date"], input.date', scope) - .datetimepicker(datepicker_options).prop "type", "text" unless Alchemy.isiOS - - $('input[type="time"], input.time', scope) - .datetimepicker(timepicker_options).prop "type", "text" unless Alchemy.isiOS - - $('input[type="datetime"], input.datetime', scope) - .datetimepicker(options).prop "type", "text" unless Alchemy.isiOS + $datepicker_inputs = $('input[data-datepicker-type]', scope) + + # Initializes the datepickers on the text inputs and sets the proper type + # to enable browsers default datepicker if the current OS is iOS. + if Alchemy.isiOS + $datepicker_inputs.prop "type", -> + return $(this).data('datepicker-type') + else + $datepicker_inputs.each -> + type = $(this).data('datepicker-type') + options = + # alchemy_i18n supports `zh_CN` etc., but flatpickr only has two-letter codes (`zh`) + locale: Alchemy.locale.slice(0, 2) + altInput: true + altFormat: Alchemy.t("formats.#{type}") + altInputClass: "" + enableTime: /time/.test(type) + noCalendar: type == "time" + time_24hr: Alchemy.t("formats.time_24hr") + onValueUpdate: (_selectedDates, _dateStr, instance) -> + Alchemy.setElementDirty $(instance.element).closest(".element-editor") + $(this).flatpickr(options) return diff --git a/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee index a55b9d50d57..8d6cd601a2d 100644 --- a/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee @@ -122,7 +122,7 @@ class window.Alchemy.Dialog @show_error(xhr, status) # Displays an error message - show_error: (xhr, status_message) -> + show_error: (xhr, status_message, $container = @dialog_body) -> error_type = "warning" switch xhr.status when 0 @@ -135,15 +135,15 @@ class window.Alchemy.Dialog error_type = "error" if status_message error_header = status_message - console.error eval(xhr.responseText) + console.error(xhr.responseText) else error_header = "#{xhr.statusText} (#{xhr.status})" error_body = "Please check log and try again." $errorDiv = $("
    ") - $errorDiv.append "" + $errorDiv.append Alchemy.messageIcon(error_type) $errorDiv.append "

    #{error_header}

    " $errorDiv.append "

    #{error_body}

    " - @dialog_body.html $errorDiv + $container.html $errorDiv # Binds close events on: # - Close button @@ -171,7 +171,7 @@ class window.Alchemy.Dialog @dialog_body = $('
    ') @dialog_header = $('
    ') @dialog_title = $('
    ') - @close_button = $('') + @close_button = $('') @dialog_title.text(@options.title) @dialog_header.append(@dialog_title) @dialog_header.append(@close_button) @@ -251,17 +251,19 @@ window.Alchemy.openDialog = (url, options) -> # See Alchemy.Dialog for further options you can add to the data attribute # window.Alchemy.watchForDialogs = (scope = '#alchemy') -> - $(scope).on 'click', '[data-alchemy-dialog]', (e) -> + $(scope).on 'click', '[data-alchemy-dialog]', (event) -> $this = $(this) url = $this.attr('href') options = $this.data('alchemy-dialog') Alchemy.openDialog(url, options) - false + event.preventDefault() + return $(scope).on 'click', '[data-alchemy-confirm-delete]', (event) -> $this = $(this) options = $this.data('alchemy-confirm-delete') Alchemy.confirmToDeleteDialog($this.attr('href'), options) - false + event.preventDefault() + return $(scope).on 'click', '[data-alchemy-confirm]', (event) -> options = $(this).data('alchemy-confirm') Alchemy.openConfirmDialog options.message, $.extend options, @@ -271,4 +273,15 @@ window.Alchemy.watchForDialogs = (scope = '#alchemy') -> Alchemy.pleaseWaitOverlay() @form.submit() return - false + event.preventDefault() + return + +# Returns a FontAwesome icon for given message type +# +window.Alchemy.messageIcon = (messageType) -> + icon_class = switch messageType + when "warning", "warn", "alert" then "exclamation" + when "notice" then "check" + when "error" then "bug" + else messageType + "" diff --git a/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee b/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee index b65e2778638..46360457ea8 100644 --- a/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee @@ -12,19 +12,14 @@ $.extend Alchemy, setElementDirty: (element) -> $element = $(element) $element.addClass('dirty') - $element - .find('> .element-header .icon[class*="element_"]') - .addClass('element_dirty') window.onbeforeunload = @pageUnload pageUnload: -> - Alchemy.pleaseWaitOverlay(false) Alchemy.t('page_dirty_notice') setElementClean: (element) -> $element = $(element) $element.removeClass('dirty') - $element.find('> .element-header .icon').removeClass('element_dirty') $element.find('> .element-content .dirty').removeClass('dirty') window.onbeforeunload = undefined @@ -41,10 +36,11 @@ $.extend Alchemy, $form = $("
    ") $form.append $(element).find("input") $form.appendTo "body" + Alchemy.pleaseWaitOverlay() $form.submit() else if $(element).is("a") callback = -> - window.location.href = element.pathname + Turbolinks.visit(element.pathname) if Alchemy.isPageDirty() Alchemy.openConfirmDialog Alchemy.t('page_dirty_notice'), title: Alchemy.t('warning') @@ -52,7 +48,6 @@ $.extend Alchemy, cancel_label: Alchemy.t('cancel') on_ok: -> window.onbeforeunload = undefined - Alchemy.pleaseWaitOverlay() callback() false else diff --git a/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee b/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee index aa0bf6d89d3..d509d3cc970 100644 --- a/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee @@ -1,11 +1,11 @@ -#= require jquery-ui/draggable -#= require jquery-ui/sortable +#= require jquery-ui/widgets/draggable +#= require jquery-ui/widgets/sortable # window.Alchemy = {} if typeof (window.Alchemy) is "undefined" $.extend Alchemy, - SortableElements: (page_id, form_token, selector = '#element_area .sortable_cell') -> + SortableElements: (page_id, form_token, selector = '#element_area .sortable-elements') -> Alchemy.initializedSortableElements = false $sortable_area = $(selector) @@ -25,7 +25,7 @@ $.extend Alchemy, opacity: 0.5 cursor: "move" containment: $('#element_area') - tolerance: "intersect" + tolerance: "pointer" update: (event, ui) -> # This callback is called twice for both elements, the source and the receiving # but, we only want to call ajax callback once on the receiving element. @@ -35,14 +35,11 @@ $.extend Alchemy, Alchemy.initializedSortableElements = true element_ids = $.map $this.children(), (child) -> $(child).attr("data-element-id") - cell_id = $this.data("cell-id") parent_element_id = ui.item.parent().closest("[data-element-id]").data('element-id') params = page_id: page_id authenticity_token: encodeURIComponent(form_token) element_ids: element_ids - if cell_id? - params['cell_id'] = cell_id if parent_element_id? params['parent_element_id'] = parent_element_id $(event.target).css("cursor", "progress") @@ -63,6 +60,10 @@ $.extend Alchemy, $this.sortable('option', 'connectWith', $dropzone) $this.sortable('refresh') $dropzone.css('minHeight', 36) + ui.item.addClass('dragged') + if ui.item.hasClass('compact') + ui.placeholder.addClass('compact').css + height: ui.item.outerHeight() Alchemy.Tinymce.remove(ids) return stop: (event, ui) -> @@ -70,6 +71,7 @@ $.extend Alchemy, name = ui.item.data('element-name') $dropzone = $("[data-droppable-elements~='#{name}']") $dropzone.css('minHeight', '') + ui.item.removeClass('dragged') Alchemy.Tinymce.init(ids) return @@ -77,25 +79,6 @@ $.extend Alchemy, $sortable_area.find('.nested-elements').sortable(sortable_options) return - SortableContents: (selector, token) -> - $(selector).sortable - items: "div.dragable_picture" - handle: "div.picture_handle" - opacity: 0.5 - cursor: "move" - tolerance: "pointer" - containment: "parent" - update: (event, ui) -> - ids = $.map $(this).children("div.dragable_picture"), (child) -> - child.id.replace /essence_picture_/, "" - $(event.originalTarget).css "cursor", "progress" - $.ajax - url: Alchemy.routes.order_admin_contents_path - type: "POST" - data: "authenticity_token=" + encodeURIComponent(token) + "&" + $.param(content_ids: ids) - complete: -> - $(event.originalTarget).css "cursor", "move" - DraggableTrashItems: -> $("#trash_items div.draggable").each -> $this = $(this) diff --git a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee index b7c2b44ccc4..80ae7b18be1 100644 --- a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee @@ -20,7 +20,9 @@ Alchemy.ElementEditors = # Binds click events on several DOM elements from element editors # Uses event delegation, so it is not necessary to rebind these events. bindEvents: -> - @element_area.on "click", ".element-header", (e) => + $('body').on 'click', (e) => + @onClickBody(e) + @element_area.on "click", ".element-editor", (e) => @onClickElement(e) @element_area.on "dblclick", ".element-header", (e) => @onDoubleClickElement(e) @@ -34,32 +36,36 @@ Alchemy.ElementEditors = # Binds the custom SaveElement event @element_area.on "SaveElement.Alchemy", '.element-editor', (e, data) => @onSaveElement(e, data) + # Listen to postMessage messages from the preview frame + window.addEventListener 'message', (e) => + if e.origin == window.location.origin + @onMessage(e.data) + else + console.warn 'Unsafe message origin!', e.origin + true return # Selects and scrolls to element with given id in the preview window. # - selectElementInPreview: (element_id) -> - $frame_elements = document - .getElementById("alchemy_preview_window") - .contentWindow - .jQuery("[data-alchemy-element]") - $selected_element = $frame_elements.closest("[data-alchemy-element='#{element_id}']") - $selected_element.trigger("SelectPreviewElement.Alchemy") + focusElementPreview: (element_id) -> + Alchemy.PreviewWindow.postMessage + message: 'Alchemy.focusElement' + element_id: element_id return # Selects element # Scrolls to element # Unfold if folded - # Also chooses the right cell, if necessary. + # Also chooses the right fixed elements tab, if necessary. # Can be triggered through custom event 'FocusElementEditor.Alchemy' # Used by the elements on click events in the preview frame. focusElement: ($element) -> element_id = $element.attr('id').replace(/\D/g, "") - @selectCellForElement($element) + Alchemy.ElementsWindow.show() + @selectTabForElement($element) # If we have folded parents we need to unfold each of them # and then finally scroll to or unfold ourself $folded_parents = $element.parents('.element-editor.folded') - @selectElement($element) if $folded_parents.length > 0 @unfoldParents $folded_parents, => @scrollToOrUnfold(element_id) @@ -68,20 +74,19 @@ Alchemy.ElementEditors = @scrollToOrUnfold(element_id) return - # Select cell for given element - selectCellForElement: ($element) -> - $cells = $("#cells .sortable_cell") - if $cells.size() > 0 - $cell = $element.closest(".sortable_cell") - $("#cells").tabs("option", "active", $cells.index($cell)) + # Selects tab for given element + selectTabForElement: ($element) -> + $tabs = $("#fixed-elements .sortable-elements") + if $tabs.size() > 0 + $tab = $element.closest(".sortable-elements") + $("#fixed-elements").tabs("option", "active", $tabs.index($tab)) # Marks an element as selected in the element window and scrolls to it. # - selectElement: ($element) -> - $elements = $("#element_area .element-editor") - $elements.removeClass("selected") + selectElement: ($element, scroll = false) -> + $("#element_area .element-editor").not($element[0]).removeClass("selected") $element.addClass("selected") - @scrollToElement($element) + @scrollToElement($element) if scroll return # Unfolds given parents until the last one is reached, then calls callback @@ -104,17 +109,19 @@ Alchemy.ElementEditors = # scrollToOrUnfold: (element_id, callback) -> $el = $("#element_#{element_id}") - @selectElement($el) if $el.hasClass("folded") @toggleFold(element_id, callback) + else + @selectElement($el, true) return # Scrolls the element window to given element editor dom element. # scrollToElement: (el) -> $("#element_area").scrollTo el, + axis: 'y', duration: 400 - offset: -10 + offset: -6 # Expands or folds a element editor # @@ -138,7 +145,7 @@ Alchemy.ElementEditors = toggleFold: (id, callback) -> spinner = new Alchemy.Spinner('small') spinner.spin("#element_#{id} > .element-header .ajax-folder") - $("#element_#{id}_folder").hide() + $("#element_#{id}_folder .icon").hide() $.post Alchemy.routes.fold_admin_element_path(id), => callback.call() if callback? return @@ -168,37 +175,45 @@ Alchemy.ElementEditors = # Event handlers - # Click event handler for element head. + onMessage: (data) -> + if data.message == 'Alchemy.focusElementEditor' + $element = $("#element_#{data.element_id}") + Alchemy.ElementEditors.focusElement($element) + + onClickBody: (e) -> + element = $(e.target).parents('.element-editor')[0] + $('#element_area .element-editor').not(element).removeClass('selected') + unless element + Alchemy.PreviewWindow.postMessage(message: 'Alchemy.blurElements') + return + + # Click event handler for element body. # # - Focuses the element - # - Triggers custom 'SelectPreviewElement.Alchemy' event on target element in preview frame. + # - Sends 'Alchemy.focusElement' message to preview frame. # onClickElement: (e) -> - $element = $(e.target).closest(".element-editor") + $target = $(e.target) + $element = $target.closest(".element-editor") element_id = $element.attr("id").replace(/\D/g, "") - $("#element_area .element-editor").removeClass("selected") - $element.addClass("selected") @selectElement($element) - @selectElementInPreview(element_id) - e.preventDefault() - e.stopPropagation() - false + @focusElementPreview(element_id) + return # Double click event handler for element head. onDoubleClickElement: (e) -> id = $(e.target).closest('.element-editor').attr('id').replace(/\D/g, '') @toggle(id) e.preventDefault() - e.stopPropagation() - false + return # Click event handler for element toggle icon. onClickToggle: (e) -> - id = $(e.target).data('element-toggle') + id = $(e.currentTarget).data('element-toggle') @toggle(id) e.preventDefault() e.stopPropagation() - false + return # Handles the custom 'FocusElementEditor.Alchemy' event. # diff --git a/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee b/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee index 1e797db5e21..cbf6a7650c9 100644 --- a/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee @@ -11,8 +11,8 @@ Alchemy.ToolbarButton = (options) -> $lnk.click (e) -> e.preventDefault() options.onClick(e) - false - $lnk.append "" + return + $lnk.append "" $btn.append $lnk $btn.append "
    " $btn @@ -21,6 +21,7 @@ Alchemy.ElementsWindow = init: (url, options, callback) -> @hidden = false + @$body = $('body') @element_window = $('
    ') @element_area = $('
    ') @url = url @@ -32,11 +33,11 @@ Alchemy.ElementsWindow = @button.click => @hide() false - height = @resize() window.requestAnimationFrame => spinner = new Alchemy.Spinner('medium') spinner.spin @element_area[0] $('#main_content').append(@element_window) + @show() @reload() createToolbar: (buttons) -> @@ -45,14 +46,6 @@ Alchemy.ElementsWindow = @toolbar.append Alchemy.ToolbarButton(btn) @toolbar - resize: -> - height = $(window).height() - 73 - @element_window.css - height: height - @element_area.css - height: height - 46 - height - reload: -> $.get @url, (data) => @element_area.html data @@ -60,19 +53,17 @@ Alchemy.ElementsWindow = if @callback @callback.call() .fail (xhr, status, error) => - Alchemy.AjaxErrorHandler @element_area, xhr.status, status, error + Alchemy.Dialog::show_error(xhr, error, @element_area) hide: -> - @element_window.css(right: -400) + @$body.removeClass('elements-window-visible'); @hidden = true @toggleButton() - Alchemy.PreviewWindow.resize() show: -> - @element_window.css(right: 0) + @$body.addClass('elements-window-visible'); @hidden = false @toggleButton() - Alchemy.PreviewWindow.resize() toggleButton: -> if @hidden diff --git a/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee b/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee index b273fc50f29..fff11850570 100644 --- a/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee @@ -9,7 +9,7 @@ Alchemy.FileProgress = (file) -> @$fileProgressElement = $('
    ') # Append Cancel Button - @$fileProgressCancel = $('') + @$fileProgressCancel = $('') @$fileProgressElement.append @$fileProgressCancel # Append Filename diff --git a/app/assets/javascripts/alchemy/alchemy.fixed_elements.js b/app/assets/javascripts/alchemy/alchemy.fixed_elements.js new file mode 100644 index 00000000000..23a8b34cf1e --- /dev/null +++ b/app/assets/javascripts/alchemy/alchemy.fixed_elements.js @@ -0,0 +1,38 @@ +window.Alchemy = Alchemy || {}; + +Alchemy.FixedElements = { + WRAPPER: '
    ', + TABS: '', + + // Builds fixed elements tabs + buildTabs: function(label) { + var $wrapper = $(this.WRAPPER), + $tabs = $(this.TABS.replace(/{{label}}/, label)); + + $('#main-content-elements').wrap($wrapper); + $('#fixed-elements').prepend($tabs).tabs('paging', { + follow: true, + followOnSelect: true + }); + }, + + // Creates a fixed element tab. + createTab: function(element_id, label) { + var $fixed_elements = $('#fixed-elements'), + $tab; + + $('> ul', $fixed_elements).append('
  • ' + label + '
  • '); + $tab = $('
    '); + $fixed_elements.append($tab); + $fixed_elements.tabs().tabs('refresh'); + $fixed_elements.tabs('option', 'active', $('#fixed-elements > div').index($tab)); + }, + + removeTab: function(element_id) { + var $fixed_elements = $('#fixed-elements'); + + $fixed_elements.find('a[href="#fixed-element-' + element_id + '"]').parent().remove(); + $fixed_elements.find('div#fixed-element-' + element_id).remove(); + $fixed_elements.tabs().tabs('refresh'); + } +}; diff --git a/app/assets/javascripts/alchemy/alchemy.growler.js.coffee b/app/assets/javascripts/alchemy/alchemy.growler.js.coffee index 96e3acac6ad..ad4283b0f80 100644 --- a/app/assets/javascripts/alchemy/alchemy.growler.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.growler.js.coffee @@ -4,24 +4,21 @@ Alchemy.Growler = build: (message, flash_type) -> $flash_container = $("
    ") - icon_class = (if flash_type is "notice" then "tick" else flash_type) - $flash_container.append "" + $flash_container.append Alchemy.messageIcon(flash_type) $flash_container.append message $("#flash_notices").append $flash_container $("#flash_notices").show() Alchemy.Growler.fade() fade: -> - $(".flash.notice, .flash.warning, .flash.warn, .flash.alert", "#flash_notices").delay(5000).hide "drop", - direction: "up" - , 400, -> - $(this).remove() + $(".flash:not(.error)", "#flash_notices").delay(5000).queue(-> Alchemy.Growler.dismiss(this)) + $(".flash", "#flash_notices").click((e) => @dismiss(e.currentTarget)) + return - $(".flash.error", "#flash_notices").click -> - $(this).hide "drop", - direction: "up" - , 400, -> - $(this).remove() + dismiss: (element) -> + $(element).on 'transitionend', => $(element).remove() + $(element).addClass('dismissed') + return Alchemy.growl = (message, style = "notice") -> Alchemy.Growler.build message, style diff --git a/app/assets/javascripts/alchemy/alchemy.gui.js.coffee b/app/assets/javascripts/alchemy/alchemy.gui.js.coffee index ca42375b361..c558623a5fd 100644 --- a/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.gui.js.coffee @@ -9,7 +9,10 @@ Alchemy.GUI = Alchemy.Datepicker(scope) Alchemy.Tooltips(scope) Alchemy.Buttons.observe(scope) - Alchemy.watchForDialogs(scope) + # Dialog links use event delegation and therefore do not + # need to be re-initialized after dom elements get replaced + unless scope + Alchemy.watchForDialogs() Alchemy.Hotkeys(scope) Alchemy.ListFilter(scope) Alchemy.Autocomplete.tags(scope) 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 92f3960bdf8..00000000000 --- 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 - Alchemy.debug "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.image_overlay.coffee b/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee index 2abf9b09595..e6635f33912 100644 --- a/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +++ b/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee @@ -44,7 +44,7 @@ class window.Alchemy.ImageOverlay extends Alchemy.Dialog @dialog = $('
    ') @dialog_body = $('
    ') @close_button = $(' - + ') @dialog.append(@close_button) @dialog.append(@dialog_body) diff --git a/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee b/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee index 24b2ac81862..d171c355ab5 100644 --- a/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.initializer.js.coffee @@ -15,11 +15,9 @@ Alchemy.Initializer = -> Alchemy.Growler.fade() # Add observer for please wait overlay. - $('.please_wait, #main_navi a, .button_with_label form :submit, .locked_page a, .pagination a') - .not('*[data-alchemy-confirm], .locked_page button') - .click -> - unless Alchemy.isPageDirty() - Alchemy.pleaseWaitOverlay() + $('.please_wait, .button_with_label form :submit') + .not('*[data-alchemy-confirm]') + .click Alchemy.pleaseWaitOverlay # Hack for enabling tab focus for 's styled as button. $('a.button').attr({tabindex: 0}) @@ -28,17 +26,16 @@ Alchemy.Initializer = -> $('select#change_locale').on 'change', (e) -> url = window.location.pathname delimiter = if url.match(/\?/) then '&' else '?' - window.location.href = "#{url}#{delimiter}admin_locale=#{$(this).val()}" + Turbolinks.visit "#{url}#{delimiter}admin_locale=#{$(this).val()}" # Site select handler $('select#change_site').on 'change', (e) -> url = window.location.pathname delimiter = if url.match(/\?/) then '&' else '?' - window.location.href = "#{url}#{delimiter}site_id=#{$(this).val()}" + Turbolinks.visit "#{url}#{delimiter}site_id=#{$(this).val()}" # Submit forms of selects with `data-autosubmit="true"` $('select[data-auto-submit="true"]').on 'change', (e) -> - Alchemy.pleaseWaitOverlay() $(this.form).submit() # Attaches the image loader on all images @@ -49,12 +46,6 @@ Alchemy.Initializer = -> tagName = (event.target || event.srcElement).tagName key.isPressed('esc') || !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') - # Sticky table headers - $('table.list').floatThead - useAbsolutePositioning: false, - scrollingTop: 122, - zIndex: 1 - # Enabling the Turbolinks Progress Bar for v2.5 Turbolinks.enableProgressBar() if Turbolinks.enableProgressBar diff --git a/app/assets/javascripts/alchemy/alchemy.jquery_loader.js b/app/assets/javascripts/alchemy/alchemy.jquery_loader.js deleted file mode 100644 index 55be25bb800..00000000000 --- a/app/assets/javascripts/alchemy/alchemy.jquery_loader.js +++ /dev/null @@ -1,42 +0,0 @@ -if (typeof(Alchemy) === 'undefined') { - var Alchemy = {}; -} - -// Load jQuery on demand. Use this if jQuery is not present. -// Found on http://css-tricks.com/snippets/jquery/load-jquery-only-if-not-present/ -Alchemy.loadjQuery = function(callback) { - - var thisPageUsingOtherJSLibrary = false; - - if (typeof($) === 'function') { - thisPageUsingOtherJSLibrary = true; - } - - function getScript(url, success) { - var script = document.createElement('script'); - var head = document.getElementsByTagName('head')[0], - done = false; - script.src = url; - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function() { - if (!done && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) { - done = true; - // callback function provided as param - success(); - script.onload = script.onreadystatechange = null; - head.removeChild(script); - }; - }; - head.appendChild(script); - } - - getScript('//code.jquery.com/jquery.min.js', function() { - if (typeof(jQuery) !== 'undefined') { - if (thisPageUsingOtherJSLibrary) { - jQuery.noConflict(); - } - callback(jQuery); - } - }); - -} diff --git a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee index 580f4515d19..cbc7e463b3d 100644 --- a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee @@ -18,18 +18,19 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog # attach events we handle @attachEvents() # Store some jQuery objects for further reference - @$page_anchor = $('#page_anchor', @dialog_body) - @$internal_urlname = $('#internal_urlname', @dialog_body) - @$internal_anchor = $('#internal_anchor', @dialog_body) + @$page_urlname = $('#page_urlname', @dialog_body) + @$element_anchor = $('#element_anchor', @dialog_body) + @$anchor_link = $('#anchor_link', @dialog_body) @$external_url = $('#external_url', @dialog_body) @$public_filename = $('#public_filename', @dialog_body) @$overlay_tabs = $('#overlay_tabs', @dialog_body) @$page_container = $('#page_selector_container') - @initInternalAnchors() # if we edit an existing link if @link_object # we select the correct tab @selectTab() + @initPageSelect() + @initAnchorLinks() # Attaches click events to several buttons in the link dialog. attachEvents: -> @@ -42,8 +43,12 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog url = @$external_url.val() when 'file' url = @$public_filename.val() + when 'anchor' + url = @$anchor_link.val() else - url = $("##{@link_type}_urlname").val() + url = @$page_urlname.val() + if @$element_anchor.val() != '' + url += "##{@$element_anchor.val()}" # Create the link @createLink url: url @@ -51,38 +56,67 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog target: $("##{@link_type}_link_target").val() false - # Attaches click events to buttons in the link dialog that appear after the - # page tree has finished loading. - attachTreeEvents: -> - # The select page and show elements links. - $('a.sitemap_pagename_link, a.show_elements_to_link', @dialog_body).click (e) => - $this = $(e.currentTarget) - page_id = $this.data('page-id') - url = $this.data('url') - # Select page in page tree - @selectPage(page_id) - # if the show elements link was clicked - if $this.hasClass('show_elements_to_link') - # we open the elements select for that page - @showElementsSelect($this.attr('href'), url) + # Initializes the select2 based Page select + initPageSelect: -> + pageTemplate = HandlebarsTemplates.page + element_anchor_placeholder = @$element_anchor.attr('placeholder') + @$page_urlname.select2 + placeholder: Alchemy.t('Search page') + allowClear: true + minimumInputLength: 3 + ajax: + url: Alchemy.routes.api_pages_path + datatype: 'json' + quietMillis: 300 + data: (term, page) -> + q: + name_cont: term + page: page + results: (data) -> + meta = data.meta + results: + data.pages.map (page) -> + id: page.url_path + name: page.name + 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(/^\/([a-z]{2}(-[A-Z]{2})?\/)?/, '') + page: 1 + per_page: 1, + (data) => + page = data.pages[0] + if page + @initElementSelect(page.id) + callback + id: page.url_path + name: page.name + url_path: page.url_path + page_id: page.id + formatSelection: (page) -> + page.name + formatResult: (page) -> + pageTemplate(page: page) + .on 'change', (event) => + if event.val == '' + @$element_anchor.val(element_anchor_placeholder) + @$element_anchor.select2('destroy').prop('disabled', true) else - # store url - @$internal_urlname.val('/' + url) - false - # Select the current page in the tree - @selectInternalLinkTab() - - # Sets the page selected and scrolls it in the viewport. - selectPage: (page_id) -> - # deselect any selected page from page tree - @deselectPage() - # reset the internal anchor select - @$internal_anchor.select2('val', '') - $('#sitemap_sitename_' + page_id).addClass('selected_page') - @$page_container.scrollTo("#sitemap_sitename_#{page_id}", {duration: 400, offset: -10}) - - deselectPage: -> - $('#sitemap .selected_page', @dialog_body).removeClass('selected_page') + @$element_anchor.val('') + @initElementSelect(event.added.page_id) + + # Initializes the select2 based elements select + # reveals after a page has been selected + initElementSelect: (page_id) -> + $.get Alchemy.routes.api_elements_path, page_id: page_id, (data) => + @$element_anchor.prop('disabled', false).removeAttr('placeholder').select2 + data: [ id: '', text: Alchemy.t('None') ].concat data.elements.map (element) -> + id: element.dom_id + text: element.display_name # Creates a link if no validation errors are present. # Otherwise shows an error notice. @@ -124,7 +158,7 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog $("#contents_#{content_id}_link_class_name").val(@link_type) $("#contents_#{content_id}_link_target").val(target) @$link_object.addClass('linked') - @$link_object.next().addClass('linked').removeClass('disabled') + @$link_object.next().addClass('linked').removeClass('disabled').removeAttr('tabindex') # Selects the correct tab for link type and fills all fields. selectTab: -> @@ -149,57 +183,24 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog # Handles a file link. tab = $('#overlay_tab_file_link') @$public_filename.select2('val', @$link[0].pathname + @$link[0].search) + else if @$link.attr('href').match(/^#/) + # Handles an anchor link. + tab = $('#overlay_tab_anchor_link') + @$anchor_link.select2('val', @$link.attr('href')) else # Handles an internal link. tab = $('#overlay_tab_internal_link') + @initInternalLinkTab() # activate the tab jquery ui 1.10 style o.O @$overlay_tabs.tabs('option', 'active', $('#overlay_tabs > div').index(tab)) # Handles actions for internal link tab. - selectInternalLinkTab: -> - return unless @$link + initInternalLinkTab: -> url = @$link.attr('href').split('#') - urlname = url[0] - anchor = url[1] - if anchor - # store the anchor - @$page_anchor.val("##{anchor}") - # and update the url field - @$internal_urlname.val("#{urlname}##{anchor}") - # if we linked an internal anchor - if @$internal_urlname.val().match(/^#/) - # we select the correct value from anchors select - value = @$internal_urlname.val() - @$internal_anchor.select2 'val', value.replace(/^#/, '') - else - @$internal_urlname.val(urlname) - $sitemap_line = $('.sitemap_sitename').closest('[name="'+urlname+'"]') - if ($sitemap_line.length > 0) - # Select the line where the link was detected in. - $sitemap_line.addClass('selected_page') - @$page_container.scrollTo($sitemap_line.closest('li'), {duration: 400, offset: -10}) - - # Opens a new Dialog that shows the elements from given page_id in a selectbox. - # The value is stored as anchor and the url gets updated so it includes the anchor link - showElementsSelect: (show_elements_url, urlname) -> - dialog = new Alchemy.Dialog show_elements_url, - size: '400x165' - ready: => - $element_select = $('.elements_from_page_selector') - # check if the urlname is the same as stored, - current_urlname = @$internal_urlname.val().split('#')[0] - if "/#{urlname}" == current_urlname - # then we can select the current anchor in the selectbox - $element_select.select2 'val', @$page_anchor.val() - $element_select.change => - @$page_anchor.val $element_select.select2('val') - $('button', dialog.dialog_body).click => - @$internal_urlname.val("/#{urlname}#{@$page_anchor.val()}") - dialog.close() - false - return - dialog.open() - return + # update the url field + @$page_urlname.val(url[0]) + # store the anchor + @$element_anchor.val(url[1]) # Creates a temporay $('a') object that holds all values on it. createTempLink: -> @@ -225,20 +226,16 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog $('#errors', @dialog_body).show() # Populates the internal anchors select - initInternalAnchors: -> + initAnchorLinks: -> frame = document.getElementById('alchemy_preview_window') elements = frame.contentDocument.getElementsByTagName('*') if elements.length > 0 for element in elements if element.id - @$internal_anchor.append("") + @$anchor_link.append("") else - @$internal_anchor.html("") - @$internal_anchor.change (e) => - # deselect any selected page from page tree - @deselectPage() - # store the internal anchor as urlname - $("#internal_urlname").val("##{e.target.value}") + @$anchor_link.html("") + return # Public class methods @@ -251,6 +248,7 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog $("#contents_#{content_id}_link_target").val('') if $link.hasClass('linked') Alchemy.setElementDirty $(link).closest('.element-editor') - $link.removeClass('linked').addClass('disabled') + $link.removeClass('linked').addClass('disabled').attr('tabindex', '-1') + $link.blur() $('#edit_link_' + content_id).removeClass('linked') false diff --git a/app/assets/javascripts/alchemy/alchemy.page_sorter.js b/app/assets/javascripts/alchemy/alchemy.page_sorter.js index df57ce4be16..9c4039c97d0 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.preview.js.coffee b/app/assets/javascripts/alchemy/alchemy.preview.js.coffee index e7f0b39aec2..542b8e1d2ad 100644 --- a/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.preview.js.coffee @@ -1,112 +1,99 @@ -#= require alchemy/alchemy.jquery_loader -#= require alchemy/alchemy.browser -#= require alchemy/alchemy.i18n +window.Alchemy = Alchemy || {} -window.Alchemy = {} if typeof(Alchemy) is 'undefined' - -Alchemy.initAlchemyPreviewMode = ($) -> - - # Setting jQueryUIs global animation duration - $.fx.speeds._default = 400 +Alchemy.initAlchemyPreviewMode = -> # The Alchemy JavaScript Object contains all Functions - $.extend Alchemy, + Object.assign Alchemy, ElementSelector: - # defaults - scrollOffset: 20 - styles: reset: outline: "" "outline-offset": "" - "-moz-outline-radius": "" - default_hover: - outline: "3px solid #F0B437" + cursor: "" + hover: + outline: "2px dashed #f0b437" "outline-offset": "4px" cursor: "pointer" - webkit_hover: - outline: "4px auto #F0B437" - moz_hover: - "-moz-outline-radius": "3px" - default_selected: - outline: "3px solid #90B9D0" + selected: + outline: "2px dashed #90b9d0" "outline-offset": "4px" - webkit_selected: - outline: "4px auto #90B9D0" - moz_selected: - "-moz-outline-radius": "3px" init: -> - $elements = $("[data-alchemy-element]") - @$previewElements = $elements - $elements.mouseover (e) => - $el = $(e.delegateTarget) - $el.attr("title", Alchemy.t('click_to_edit')) - $el.css(@getStyle("hover")) unless $el.hasClass("selected") + window.addEventListener "message", (event) => + if event.origin != window.location.origin + console.warn 'Unsafe message origin!', event.origin + return + switch event.data.message + when "Alchemy.blurElements" then @blurElements() + when "Alchemy.focusElement" then @focusElement(event.data) + else console.info("Received unknown message!", event.data) return - $elements.mouseout (e) => - $el = $(e.delegateTarget) - $el.removeAttr("title") - $el.css(@getStyle("reset")) unless $el.hasClass("selected") - return - $elements.on "SelectPreviewElement.Alchemy", (e) => - $el = $(e.delegateTarget) - # Stop the event from bubbling up to parent elements - e.stopPropagation() - @selectElement($el) - return - $elements.click (e) => - $el = $(e.delegateTarget) - # Stop the event from bubbling up to parent elements - e.stopPropagation() - # Stop default click events from running - e.preventDefault() - # Mark current preview element as selected - @selectElement($el) - # Focus the element editor - @focusElementEditor($el) + @elements = Array.from document.querySelectorAll("[data-alchemy-element]") + @elements.forEach (element) => + element.addEventListener 'mouseover', => + unless element.classList.contains('selected') + Object.assign element.style, @getStyle('hover') + return + element.addEventListener 'mouseout', => + unless element.classList.contains('selected') + Object.assign element.style, @getStyle('reset') + return + element.addEventListener 'click', (e) => + e.stopPropagation() + e.preventDefault() + @selectElement(element) + @focusElementEditor(element) + return return return # Mark element in preview frame as selected and scrolls to it. - selectElement: ($el) -> - offset = $el.offset() - @$previewElements.removeClass("selected").css(@getStyle("reset")) - $el.addClass("selected").css(@getStyle("selected")) - $("html, body").animate - scrollTop: offset.top - @scrollOffset - scrollLeft: offset.left - @scrollOffset - , 400 + selectElement: (element) -> + @blurElements() + element.classList.add('selected') + Object.assign element.style, @getStyle('selected') + element.scrollIntoView + behavior: 'smooth' + block: 'start' return + # Blur all elements in preview frame. + blurElements: -> + @elements.forEach (element) => + element.classList.remove('selected') + Object.assign element.style, @getStyle('reset') + return + return + + # Focus the element in the Alchemy preview window. + focusElement: (data) -> + element = @getElement(data.element_id) + if element + @selectElement(element) + else + console.warn('Could not focus element with id', data.element_id) + + getElement: (element_id) -> + @elements.find (element) -> + element.dataset.alchemyElement == element_id.toString() + # Focus the element editor in the Alchemy element window. - focusElementEditor: ($el) -> - alchemy_window = window.parent - alchemy_$ = alchemy_window.jQuery - target_id = $el.data("alchemy-element") - $element_editor = alchemy_$("#element_#{target_id}") - elements_window = alchemy_window.Alchemy.ElementsWindow - $element_editor.trigger("FocusElementEditor.Alchemy", target_id) - elements_window.show() if elements_window.hidden + focusElementEditor: (element) -> + element_id = element.getAttribute('data-alchemy-element') + window.parent.postMessage + message: 'Alchemy.focusElementEditor' + element_id: element_id + , window.location.origin return getStyle: (state) -> if state == "reset" @styles["reset"] else - default_state_style = @styles["default_#{state}"] - browser = "webkit" if Alchemy.Browser.isWebKit - browser = "moz" if Alchemy.Browser.isFirefox - if browser - $.extend(default_state_style, @styles["#{browser}_#{state}"]) - else - default_state_style + @styles[state] Alchemy.ElementSelector.init() -if typeof(jQuery) is 'undefined' - Alchemy.loadjQuery(Alchemy.initAlchemyPreviewMode) -else - Alchemy.initAlchemyPreviewMode(jQuery) +Alchemy.initAlchemyPreviewMode() diff --git a/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee b/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee index fa7fb3707d1..92405a5b144 100644 --- a/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee @@ -13,16 +13,12 @@ Alchemy.PreviewWindow = $('body').append($iframe) @currentWindow = $iframe @_bindReloadButton() - @resize() - resize: -> - width = @_calculateWidth() - height = $(window).height() - @HEIGHT + resize: (width) -> width = @MIN_WIDTH if width < @MIN_WIDTH @currentWidth = width @currentWindow.css width: width - height: height refresh: (callback) -> $iframe = $('#alchemy_preview_window') @@ -36,6 +32,10 @@ Alchemy.PreviewWindow = $iframe.attr 'src', $iframe.attr('src') true + postMessage: (data) -> + frameWindow = @currentWindow[0].contentWindow + frameWindow.postMessage(data, window.location.origin) + _showSpinner: -> @reload = $('#reload_preview_button') @spinner = new Alchemy.Spinner('small') @@ -43,20 +43,15 @@ Alchemy.PreviewWindow = _hideSpinner: -> @spinner.stop() - @reload.html('') + @reload.html('') _bindReloadButton: -> $reload = $('#reload_preview_button') key 'alt+r', => @refresh() - $reload.click => + $reload.click (e) => + e.preventDefault() @refresh() - _calculateWidth: -> - width = $(window).width() - $('#left_menu').width() - unless Alchemy.ElementsWindow.hidden - width -= $('#alchemy_elements_window').width() - return width - Alchemy.reloadPreview = -> Alchemy.PreviewWindow.refresh() diff --git a/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee b/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee index 106654689d3..27e3c0744e8 100644 --- a/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee @@ -1,5 +1,3 @@ -#= require date-formatter - window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' # The admin sitemap Alchemy module @@ -8,7 +6,7 @@ Alchemy.Sitemap = # Storing some objects. init: (options) -> @search_field = $(".search_input_field") - @filter_field_clear = $('.js_filter_field_clear') + @filter_field_clear = $('.search_field_clear') @display = $('#page_filter_result') @sitemap_wrapper = $('#sitemap-wrapper p.loading') @template = Handlebars.compile($('#sitemap-template').html()) @@ -57,7 +55,7 @@ Alchemy.Sitemap = self.options.ready() request.fail (jqXHR, status) -> - Alchemy.debug("Request failed: " + status) + console.warn("Request failed: " + status) # Filters the sitemap filter: (term) -> @@ -96,6 +94,7 @@ Alchemy.Sitemap = @filter_field_clear.click => @search_field.val('') filter('') + false # Handles the page publication date fields watchPagePublicationState: -> @@ -110,7 +109,7 @@ Alchemy.Sitemap = now = new Date() if $checkbox.is(':checked') $publication_date_fields.removeClass('hidden') - $public_on_field.val Date.format(now, format) + $public_on_field[0]._flatpickr.setDate(now) else $publication_date_fields.addClass('hidden') $public_on_field.val('') diff --git a/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee b/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee index 6f9bbeef298..4d6ab955281 100644 --- a/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee @@ -51,7 +51,7 @@ $.extend Alchemy.Tinymce, # remove editor instance, if already initialized editor.remove() if editor if textarea.length == 0 - Alchemy.log_error "Could not initialize TinyMCE for textarea#tinymce_#{id}!" + console.warn "Could not initialize TinyMCE for textarea#tinymce_#{id}!" return config = @getConfig(id, textarea[0].classList[1]) if config @@ -59,16 +59,22 @@ $.extend Alchemy.Tinymce, textarea.closest('.tinymce_container').prepend spinner.spin().el tinymce.init(config) else - Alchemy.debug('No tinymce configuration found for', id) + console.warn('No tinymce configuration found for', id) # Gets called after an editor instance gets intialized # - initInstanceCallback: (inst) -> - $this = $("##{inst.id}") - parent = $this.closest('.element-editor') - parent.find('.spinner').remove() - inst.on 'dirty', (e) -> - Alchemy.setElementDirty(parent) + initInstanceCallback: (editor) -> + $this = $("##{editor.id}") + element = $this.closest('.element-editor') + element.find('.spinner').remove() + editor.on 'dirty', -> + Alchemy.setElementDirty(element) + return + editor.on 'click', (event) -> + event.target = element[0] + Alchemy.ElementEditors.onClickElement(event) + return + return # Removes the TinyMCE editor from given dom ids. # 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 e78a21434e9..00000000000 --- a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +++ /dev/null @@ -1,187 +0,0 @@ -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -# Holds translations for javascripts -# -Alchemy.translations = - - # German - de: - allowed_chars: 'von %{count} Zeichen' - cancel: 'Abbrechen' - cancelled: 'Abgebrochen' - click_to_edit: 'Klicken zum Bearbeiten' - complete: 'Abgeschlossen' - element_dirty_notice: 'Dieses Element hat nicht gespeicherte Änderungen. Möchten Sie es wirklich einklappen?' - help: 'Hilfe' - ok: 'Ok' - page_dirty_notice: 'Sie haben ungesicherte Änderungen auf der Seite. Diese gehen verloren, wenn Sie fortfahren.' - page_found: 'Seite gefunden' - pages_found: 'Seiten gefunden' - url_validation_failed: 'Die URL hat kein gültiges Format.' - warning: 'Achtung!' - 'File is too large': 'Datei ist zu groß.' - 'File is too small': 'Datei ist zu klein.' - 'File type not allowed': 'Dateityp nicht erlaubt.' - 'Maximum number of files exceeded': 'Maximale Anzahl gleichzeitig erlaubter Datei-Uploads erreicht.' - 'Uploaded bytes exceed file size': 'Maximale Größe der erlaubten Dateigröße erreicht.' - formats: - datetime: "d.m.Y H:i" - date: "d.m.Y" - time: "H:i" - start_of_week: 1 - - # English - 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" - start_of_week: 0 - - # Dutch - nl: - allowed_chars: 'of %{count} chars' - cancel: 'Annuleren' - cancelled: 'Afgebroken' - click_to_edit: 'klik om aan te passen' - complete: 'Klaar' - element_dirty_notice: 'Dit element bevat niet opgeslagen wijzigingen. Wilt u dit echt inklappen?' - help: 'Hulp' - ok: 'Ok' - page_found: 'Page found' - pages_found: 'Pages found' - page_dirty_notice: 'Er zijn wijzigingen op deze pagina aangebracht. Deze gaan verloren als u doorgaat.' - url_validation_failed: 'De url heeft geen geldig formaat.' - warning: 'Waarschuwing!' - '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': 'Uploadlimiet bereikt.' - 'Uploaded bytes exceed file size': 'Uploaded bytes exceed file size' - formats: - datetime: "Y-m-d H:i" - date: "Y-m-d" - time: "H:i" - start_of_week: 1 - - # French - fr: - allowed_chars: 'von %{count} signes' - cancel: 'abandonner' - cancelled: 'annulé' - click_to_edit: 'Cliquez pour modifier' - complete: 'terminé' - element_dirty_notice: 'Cet élément a des modifications non enregistrées. Souhaitez-vous vraiment plier?' - help: 'Aide' - ok: 'Ok' - page_dirty_notice: 'Vous avez des modifications non enregistrées sur la page. Ce sera perdue si vous continuez.' - page_found: 'page trouvée' - pages_found: 'pages trouvées' - url_validation_failed: "L'URL n'est pas correctement formatée." - warning: 'Attention!' - 'File is too large': 'Le fichier est trop grand.' - 'File is too small': 'Le fichier est trop petit.' - 'File type not allowed': 'Type de document non autorisé.' - 'Maximum number of files exceeded': 'Le nombre maximum de fichiers est atteint.' - 'Uploaded bytes exceed file size': 'Taille de fichier maximale autorisée atteint.' - formats: - datetime: "d.m.Y H:i" - date: "d.m.Y" - time: "H:i" - start_of_week: 1 - - # Russian - ru: - allowed_chars: '%{count} знаков' - cancel: 'Отмена' - cancelled: 'Отменено' - click_to_edit: 'нажмите, чтобы редактировать' - complete: 'Завершено' - element_dirty_notice: 'Для этого элемента есть несохраненные изменения. Вы действительно хотите их сбросить?' - help: 'Помощь' - ok: 'Ок' - page_dirty_notice: 'На странице есть несохраненные изменения. Если вы продолжите, то они пропадут.' - page_found: 'Страница найдена' - pages_found: 'Страницы найдены' - url_validation_failed: 'Адрес (URL) имеет неверный формат.' - warning: 'Внимание!' - 'File is too large': 'Файл слишком большой' - 'File is too small': 'Файл слишком маленький' - 'File type not allowed': 'Этот тип файла не разрешен' - 'Maximum number of files exceeded': 'Исчерпано максимальное количество файлов.' - 'Uploaded bytes exceed file size': 'Превышен максимальный размер файла' - formats: - datetime: "d.m.Y H:i" - date: "d.m.Y" - time: "H:i" - start_of_week: 1 - - # Spanish - es: - allowed_chars: 'de %{count} caracteres' - cancel: 'Cancelar' - cancelled: 'Cancelado' - click_to_edit: 'Pulsar para editar' - complete: 'Completado' - element_dirty_notice: 'Este elemento tiene cambios sin guardar. ¿Realmente quieres plegarlo?' - help: 'Ayuda' - ok: 'Aceptar' - page_dirty_notice: 'Tienes cambios sin guardar en esta página. Si continúas se perderán.' - page_found: 'Página encontrada' - pages_found: 'Páginas encontradas' - url_validation_failed: 'La url no tiene un formato válido.' - warning: '¡Atención!' - 'File is too large': 'El archivo es demasiado grande' - 'File is too small': 'El archivo es demasiado pequeño' - 'File type not allowed': 'El tipo de archivo no está permitido' - 'Maximum number of files exceeded': 'Número máximo de archivos excedido.' - 'Uploaded bytes exceed file size': 'Los bytes subidos exceden el tamaño del archivo' - formats: - datetime: "d/m/Y H:i" - date: "d/m/Y" - time: "H:i" - start_of_week: 1 - - # Italian - it: - allowed_chars: 'di %{count} caratteri' - cancel: 'Annulla' - cancelled: 'Annullato' - click_to_edit: 'clicca per modificare' - complete: 'Completato' - element_dirty_notice: 'Questo elemento contiene delle modifiche non salvate. Vuoi veramenter chiuderlo?' - help: 'Aiuto' - ok: 'Ok' - page_dirty_notice: 'Su questa pagina hai delle modifiche non salvate. Verranno perse se decidi di continuare.' - page_found: 'Pagina trovata' - pages_found: 'Pagine trovate' - url_validation_failed: 'Formato URL non valido.' - warning: 'Attenzione!' - 'File is too large': 'Il file è troppo grande' - 'File is too small': 'Il file è troppo piccolo' - 'File type not allowed': 'Tipo di file non consentito' - 'Maximum number of files exceeded': 'Numero massimo di file raggiunto.' - 'Uploaded bytes exceed file size': 'Il numero di byte caricati eccede la dimensione del file' - formats: - datetime: "Y-m-d H:i" - date: "Y-m-d" - time: "H:i" - start_of_week: 1 diff --git a/app/assets/javascripts/alchemy/node_select.js b/app/assets/javascripts/alchemy/node_select.js new file mode 100644 index 00000000000..a3587039054 --- /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/page_select.js b/app/assets/javascripts/alchemy/page_select.js new file mode 100644 index 00000000000..e51d9e6f9d7 --- /dev/null +++ b/app/assets/javascripts/alchemy/page_select.js @@ -0,0 +1,41 @@ +$.fn.alchemyPageSelect = function(options) { + var pageTemplate = HandlebarsTemplates.page + + 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: function (term, page) { + return { + q: $.extend({ + name_cont: term + }, options.query_params), + page: page + } + }, + results: function (data) { + var meta = data.meta + + return { + results: data.pages, + more: meta.page * meta.per_page < meta.total_count + } + } + }, + formatSelection: function (page) { + return page.text || page.name + }, + formatResult: function(page) { + return pageTemplate({page: page}) + } + }) +} diff --git a/app/assets/javascripts/alchemy/templates/index.js b/app/assets/javascripts/alchemy/templates/index.js index 7827d0ee40c..acbaf8b3dbc 100644 --- a/app/assets/javascripts/alchemy/templates/index.js +++ b/app/assets/javascripts/alchemy/templates/index.js @@ -1 +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 00000000000..50d487d9126 --- /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/node_folder.hbs b/app/assets/javascripts/alchemy/templates/node_folder.hbs new file mode 100644 index 00000000000..d4c80680f9b --- /dev/null +++ b/app/assets/javascripts/alchemy/templates/node_folder.hbs @@ -0,0 +1,3 @@ +
    + + diff --git a/app/assets/javascripts/alchemy/templates/page.hbs b/app/assets/javascripts/alchemy/templates/page.hbs new file mode 100644 index 00000000000..81748efe202 --- /dev/null +++ b/app/assets/javascripts/alchemy/templates/page.hbs @@ -0,0 +1,9 @@ +
    + + + {{ page.name }} + + + {{ page.url_path }} + +
    diff --git a/app/assets/stylesheets/alchemy/_defaults.scss b/app/assets/stylesheets/alchemy/_defaults.scss index 63d135e9847..f801d007b54 100644 --- a/app/assets/stylesheets/alchemy/_defaults.scss +++ b/app/assets/stylesheets/alchemy/_defaults.scss @@ -1,5 +1,3 @@ -@import "bourbon"; @import "alchemy/variables"; @import "alchemy/mixins"; @import "alchemy/extends"; -@import "alchemy/icon-font"; diff --git a/app/assets/stylesheets/alchemy/_extends.scss b/app/assets/stylesheets/alchemy/_extends.scss index 6a3e7c91fd0..3dda65c11e3 100644 --- a/app/assets/stylesheets/alchemy/_extends.scss +++ b/app/assets/stylesheets/alchemy/_extends.scss @@ -1,7 +1,3 @@ -%rounded-border { - border-radius: $default-border-radius; -} - %text-overflow { white-space: nowrap; overflow: hidden; @@ -11,56 +7,37 @@ -moz-text-overflow: ellipsis; } -%thumbnail-background { - position: relative; - display: table-cell; - background-color: $dark-gray; - text-align: center; - vertical-align: middle; - padding: 0; - line-height: 0; -} - %field-with-error { border-color: $error_border_color; color: $error_text_color; background-color: lighten($error_background_color, 8%); - box-shadow: inset 1px 1px 4px rgba($error_border_color, 0.5); -} - -%blue-focus-style { - @include default-focus-style; + box-shadow: inset 0px 0 1px rgba($error_border_color, 0.5); } %default-input-style { - box-sizing: border-box; - @extend %rounded-border; - box-shadow: inset 0px 1px 1px $medium-gray; + border-radius: $default-border-radius; + box-shadow: $form-field-box-shadow; font: $default-font-style; - padding: 1px 2*$default-padding; - margin: $default-form-field-margin; - background: rgba(#fff, 0.9); - border-top: 1px solid $default-border-color; - border-left: 1px solid $default-border-color; - border-bottom: 1px solid $border-inset-color; - border-right: 1px solid $border-inset-color; + color: $form-field-text-color; + padding: $form-field-padding; + margin: $form-field-margin; + background: $form-field-background-color; + border-width: $form-field-border-width; + border-style: $form-field-border-style; + border-color: $form-field-border-color; + height: $form-field-height; width: 100%; - font-size: 12px; - line-height: 12px; - height: $default-form-field-height; - @include transition( - box-shadow 0.25s ease-in-out, - background-color 0.25s ease-in-out, - border 0.25s ease-in-out - ); + font-size: $form-field-font-size; + line-height: $form-field-line-height; + transition: $transition-duration; &:focus { - @extend %blue-focus-style; + @include default-focus-style($box-shadow: 0 0 0 1px $focus-color); } &[disabled], .disabled { - color: #888; - background-color: rgba(#fff, 0.5); + color: $form-field-disabled-text-color; + background-color: $form-field-disabled-bg-color; cursor: not-allowed; } } @@ -68,5 +45,5 @@ %gradiated-toolbar { background: $toolbar-bg-color; padding: 8px 4px; - height: $default-form-field-height; + height: $toolbar-height; } diff --git a/app/assets/stylesheets/alchemy/_mixins.scss b/app/assets/stylesheets/alchemy/_mixins.scss index 41ac6ea864d..2251b133b68 100644 --- a/app/assets/stylesheets/alchemy/_mixins.scss +++ b/app/assets/stylesheets/alchemy/_mixins.scss @@ -1,6 +1,10 @@ -@mixin default-focus-style { - border-color: $blue; - box-shadow: 0 0 1px 1px $blue; +@mixin default-focus-style( + $border-color: $focus-color, + $box-shadow: 0 0 0 2px $focus-color, + $border-radius: $default-border-radius) { + border-color: $border-color; + border-radius: $border-radius; + box-shadow: $box-shadow; outline: none; &::-moz-focus-inner { @@ -10,49 +14,56 @@ } @mixin button-defaults( - $first-color: $button-gradient-first-color, - $second-color: $button-gradient-second-color, - $border-radius: $default-border-radius, + $background-color: $button-bg-color, + $hover-color: $button-hover-bg-color, + $hover-border-color: $button-hover-border-color, + $border-radius: $button-border-radius, + $border: $button-border-width solid $button-border-color, + $box-shadow: $button-box-shadow, $padding: $button-padding, - $margin: $button-margin) { + $margin: $button-margin, + $color: $button-text-color, + $line-height: $form-field-line-height, + $focus-border-color: $button-focus-border-color, + $focus-box-shadow: $button-focus-box-shadow) { display: inline-block; - font-size: 12px; + font-family: $default-font-family; + font-size: $default-font-size; + font-weight: $button-font-weight; + line-height: $line-height; padding: $padding; cursor: pointer; border-radius: $border-radius; - background-color: $button-bg-color; - background-image: linear-gradient($first-color, $second-color); - box-shadow: white 0 1px 0 inset; - border: 1px solid $button-border-color; - color: $text-color; - text-shadow: $button-text-shadow; + background-color: $background-color; + box-shadow: $box-shadow; + border: $border; + color: $color; margin: $margin; - @include appearance(none); + -webkit-appearance: none; + transition: all $transition-duration; + @include antialiased-font-smoothing; &:hover { text-decoration: none; - background-color: $button-hover-bg-color; - background-image: linear-gradient( - darken($first-color, 5%), - darken($second-color, 5%) - ); - border-color: $button-hover-border-color; + background-color: $hover-color; + border-color: $hover-border-color; } - &:active, &:active:focus { - box-shadow: #c4c4c4 0 1px 2px inset; - border-color: $button-border-color; + &:active, &.active { + box-shadow: inset $button-box-shadow; } &:focus { - @include default-focus-style; + @include default-focus-style( + $border-color: $focus-border-color, + $box-shadow: $focus-box-shadow + ); } &.disabled, &[disabled], &.disabled:active, &[disabled]:active, &.disabled:hover, &[disabled]:hover { - opacity: 0.6; - background: $second-color; + background-color: transparentize($button-bg-color, 0.5); cursor: not-allowed; box-shadow: none; } @@ -63,60 +74,38 @@ } } -@mixin default-label-style { - font-size: 10px; - text-shadow: #fff 0 1px 2px; - margin-top: $default-margin; - display: block; - line-height: 100%; - @extend .disable-user-select; - clear: both; -} - @mixin form-label { - @extend .border-box-sizing; width: $form-left-width; - padding-right: 2*$default-padding; - padding-top: $default-padding + 1; - margin-top: 4px; + padding-right: 2 * $default-padding; + padding-top: 0.6em; + margin-top: $default-margin; vertical-align: top; - line-height: 19px; word-break: normal; float: left; text-align: right; -} - -@mixin form-heading { - border-bottom: 1px solid #CECECE; - padding-right: 8px; - margin-top: 8px; - text-align: right; - font-weight: normal; - color: #585858; - text-transform: uppercase; + font-size: $default-font-size; } @mixin form-value-display { float: right; width: $form-right-width; - margin: $default-form-field-margin; + margin: $form-field-margin; padding: $default-padding 2*$default-padding; line-height: 21px; - min-height: $default-form-field-height; + min-height: $form-field-height; background: white; - @extend %rounded-border; - @extend .border-box-sizing; + border-radius: $default-border-radius; } @mixin form-hint($background-color: $light_yellow, $border-color: $medium-gray) { - font-size: 10px; + font-size: $small-font-size; line-height: 1.5em; padding: $default-padding; background-color: $background-color; border: 1px solid $border-color; display: block; clear: both; - @extend %rounded-border; + border-radius: $default-border-radius; } @mixin animate-left { @@ -146,36 +135,50 @@ box-shadow: $shadow; } -@mixin box-sizing($box-sizing-type: content-box) { - -webkit-box-sizing: $box-sizing-type; - -moz-box-sizing: $box-sizing-type; - box-sizing: $box-sizing-type; -} - -@mixin tag-base($margin: 2*$default-margin 0, $padding: $default-padding 4*$default-padding $default-padding 6*$default-padding) { +@mixin label-base($margin: 2*$default-margin 0, $padding: $default-padding 3*$default-padding) { + display: inline-block; + vertical-align: middle; padding: $padding; margin: $margin; background-color: $medium-gray; overflow: hidden; position: relative; - @extend %rounded-border; + border-radius: $default-border-radius; + color: $text-color; +} + +@mixin tag-base($margin: 2*$default-margin 0, $padding: $default-padding 3*$default-padding) { + @include label-base( + $margin: $margin, + $padding: $padding + ); &:before { - position: absolute; - left: 8px; - top: 8px; - font-family: 'Alchemy Icons'; - @extend .icon-tag:before; - line-height: 8px; + content: fa-content($fa-var-tag); + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + font-size: 0.8em; + padding-right: 2*$default-padding; + } + + &:focus { + @include default-focus-style($box-shadow: inset 0 0 0 2px $focus-color); } } -@mixin linked-button($border-radius: $default-border-radius) { +@mixin linked-button( + $border-radius: $default-border-radius, + $line-height: $form-field-line-height, + $padding: 3px) { @include button-defaults( - $first-color: $linked-color, - $second-color: darken($linked-color, 10%), + $line-height: $line-height, + $background-color: $linked-button-color, + $hover-color: darken($linked-button-color, 10%), + $border: 1px solid $linked-border-color, + $hover-border-color: darken($linked-border-color, 10%), $border-radius: $border-radius, - $padding: 3px, + $box-shadow: none, + $padding: $padding, $margin: 0 ); } @@ -188,11 +191,12 @@ cursor: zoom-in; } -@mixin truncate($max-width, $display: inline-block) { +@mixin truncate($max-width, $display: inline-block, $wrap: normal) { display: $display; max-width: $max-width; overflow: hidden; text-overflow: ellipsis; + white-space: $wrap; } @mixin hint-hover-style { @@ -201,3 +205,36 @@ opacity: 1; transition-delay: .2s; } + +@mixin clearfix { + &::after { + clear: both; + content: ""; + display: table; + } +} + +@mixin border-top-radius($radius) { + border-top-left-radius: $radius; + border-top-right-radius: $radius; +} + +@mixin border-bottom-radius($radius) { + border-bottom-left-radius: $radius; + border-bottom-right-radius: $radius; +} + +@mixin border-left-radius($radius) { + border-bottom-left-radius: $radius; + border-top-left-radius: $radius; +} + +@mixin border-right-radius($radius) { + border-bottom-right-radius: $radius; + border-top-right-radius: $radius; +} + +@mixin antialiased-font-smoothing { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/app/assets/stylesheets/alchemy/_variables.scss b/app/assets/stylesheets/alchemy/_variables.scss index c219baa0ccd..e4f7b26e1fd 100644 --- a/app/assets/stylesheets/alchemy/_variables.scss +++ b/app/assets/stylesheets/alchemy/_variables.scss @@ -1,76 +1,165 @@ -$text-color: #333333; -$muted-text-color: rgba(0, 0, 0, 0.5); -$light-blue: #cddce5; // #c6dbe7 -$blue: darken($light-blue, 15%); +$light-blue: #cddce5; $very-light-blue: lighten($light-blue, 12%); -$light-gray: #ededed; -$medium-gray: #e3e3e3; +$blue: #2d5a8d; +$dark-blue: darken($blue, 10%); +$light-gray: #f7f7f7; +$medium-gray: #efefef; $dark-gray: #666666; -$light_yellow: #ffffdc; -$linked-color: #ffd77a; -$white: #f2f2f2; - -$default-font-face: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Verdana, Tahoma, sans-serif; -$mono-font-face: Menlo, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; -$default-font-size: 12px; -$default-font-style: $default-font-size $default-font-face; - -$dialog-header-height: 36px; -$header-color: $light-blue; -$header-text-color: $text-color; -$header-font-size: $default-font-size; - -$default-padding: 4px; -$default-margin: $default-padding; - -$default-border-color: #afafaf; -$border-inset-color: lighten($default-border-color, 20%); -$default-border-width: 1px; -$default-border-style: solid; -$default-border: $default-border-width $default-border-style $default-border-color; -$default-border-radius: 3px; -$default-form-field-margin: $default-margin 0; -$default-form-field-height: 29px; - -$button-bg-color: #f7f7f7; -$button-hover-bg-color: #e6f0f5; -$button-border-color: #9a9a9a; -$button-hover-border-color: #888888; -$button-text-shadow: $white 1px 1px 0; -$button-gradient-first-color: $white !default; -$button-gradient-second-color: darken($button-gradient-first-color, 10%) !default; -$button-padding: 0.5em 1.5em !default; -$button-margin: $default-form-field-margin !default; -$linked-button-color: #ffd77a; - -$success_border_color: #9cc4a1; -$success_text_color: #2e5934; -$success_background_color: #e2efd3; - -$info_border_color: #8392b5; -$info_text_color: #233772; -$info_background_color: #cadbf3; - -$warning_border_color: #c4c19c; -$warning_text_color: #726d23; -$warning_background_color: #f3f0c1; - -$error_border_color: #c49c9c; -$error_text_color: #a23434; -$error_background_color: #efd3d3; - -$form-left-width: 35%; -$form-right-width: 65%; -$sitemap-line-height: 32px; +$very-dark-gray: #333; +$dark-gray-blue: rgb(51, 59, 81); +$light_yellow: #fbfbd8; +$orange: #ffd77a; +$dark-orange: #eca96e; +$very-dark-orange: darken($dark-orange, 20%); +$white: #fff; + +$text-color: rgba($dark-gray-blue, 0.8) !default; +$muted-text-color: rgba($dark-gray-blue, 0.5) !default; +$icon-color: rgba($dark-gray-blue, 0.75) !default; +$linked-color: $orange !default; +$anchor-color: $very-dark-orange !default; + +$body-background-color: $light-gray !default; + +$default-font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Verdana, Tahoma, sans-serif !default; +$mono-font-family: Menlo, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace !default; +$base-font-size: 12px !default; +$default-font-size: 1em !default; +$small-font-size: 0.9em !default; +$default-font-style: $default-font-size $default-font-family !default; + +$dialog-header-height: 36px !default; +$dialog-header-color: $dark-blue !default; +$dialog-header-text-color: $white !default; +$dialog-header-font-size: $default-font-size !default; +$dialog-box-shadow: 0 8px 16px rgba(35, 35, 35, 0.5) !default; +$dialog-overlay-color: rgb(100, 100, 100) !default; +$dialog-overlay-opacity: 0.4 !default; +$dialog-background-color: $light-gray !default; + +$default-padding: 4px !default; +$default-margin: $default-padding !default; + +$default-border-color: rgba(#afafaf, 0.5) !default; +$border-inset-color: lighten($default-border-color, 10%) !default; +$default-border-width: 1px !default; +$default-border-style: solid !default; +$default-border: $default-border-width $default-border-style $default-border-color !default; +$default-border-radius: 3px !default; + +$form-field-margin: $default-margin 0 !default; +$form-field-height: 31px !default; +$form-field-background-color: $white !default; +$form-field-border-width: $default-border-width !default; +$form-field-border-style: $default-border-style !default; +$form-field-border-color: $default-border-color $default-border-color $border-inset-color $border-inset-color !default; +$form-field-box-shadow: inset 0px 0 1px $medium-gray !default; +$form-field-padding: 0.6em 0.75em !default; +$form-field-font-size: $default-font-size !default; +$form-field-line-height: 1.25 !default; +$form-field-text-color: $text-color !default; +$form-field-disabled-text-color: #888 !default; +$form-field-disabled-bg-color: rgba($white, 0.5) !default; + +$focus-color: rgba($dark-orange, 0.75) !default; + +$button-bg-color: $white !default; +$button-hover-bg-color: transparent !default; +$button-border-color: $default-border-color !default; +$button-border-width: 1px !default; +$button-border-radius: $default-border-radius !default; +$button-text-color: $text-color !default; +$button-hover-border-color: $default-border-color !default; +$button-focus-border-color: $dark-orange !default; +$button-font-weight: 700 !default; +$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 0.8em !default; +$button-margin: $form-field-margin !default; + +$secondary-button-bg-color: transparent !default; +$secondary-button-text-color: $text-color !default; +$secondary-button-border-color: $default-border-color !default; + +$linked-button-color: rgba($linked-color, 0.75) !default; +$linked-border-color: darken($linked-color, 10%) !default; + +$success_border_color: #9cc4a1 !default; +$success_text_color: #2e5934 !default; +$success_background_color: #e2efd3 !default; + +$info_border_color: #8392b5 !default; +$info_text_color: #233772 !default; +$info_background_color: #cadbf3 !default; + +$warning_border_color: #c4c19c !default; +$warning_text_color: #726d23 !default; +$warning_background_color: #f3f0c1 !default; + +$error_border_color: #c49c9c !default; +$error_text_color: #a23434 !default; +$error_background_color: #efd3d3 !default; + +$form-left-width: 35% !default; +$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($white, 0.5) !default; +$sitemap-highlight-color: rgba(#fffba5, 0.5) !default; $main-menu-width: 150px !default; $collapsed-main-menu-width: 48px !default; -$main-menu-bg-color: #baced9 !default; -$main-menu-active-bg-color: $light-blue !default; -$main-menu-text-color: #222 !default; -$main-menu-entry-max-width: 100px !default; +$collapsed-main-menu-entry-max-width: 300px !default; +$main-menu-bg-color: $dark-blue !default; +$main-menu-active-bg-color: $dark-orange !default; +$main-menu-active-text-color: $dark-blue !default; +$main-menu-text-color: $white !default; +$main-menu-icon-color: $white !default; +$main-menu-entry-max-width: 110px !default; $toolbar-bg-color: $medium-gray !default; +$toolbar-height: 46px !default; +$element-toolbar-height: 37px !default; $header-height: 29px !default; +$header-background: #d8d8d8 !default; $text-shadow-light: rgba($white, 0.5) 0 0 4px !default; -$transition-duration: 200ms !default; +$transition-duration: 250ms !default; $transition-easing: linear !default; + +$tooltip-background-color: $dark-blue !default; + +$addon-icon-opacity: 0.85 !default; + +$table-row-even-background-color: $white !default; +$table-row-odd-background-color: rgba($white, 0.5) !default; +$table-row-hover-color: rgba($light_yellow, 0.5) !default; + +$elements-window-width: 22.5vw !default; +$elements-window-min-width: 400px !default; +$element-header-bg-color: $medium-gray !default; +$top-menu-height: 75px !default; + +$tabs-height: 31px !default; +$pagination-height: 52px !default; + +$datepicker_current_bg: $blue !default; +$datepicker_current_color: $white !default; +$datepicker_current_box_shadow: none !default; +$datepicker_hover_bg: $light_yellow !default; +$datepicker_hover_color: $text-color !default; +$datepicker_today_bg: $medium-gray !default; +$datepicker_today_color: $blue !default; +$datepicker_header_color: $text-color !default; +$datepicker_day_bg: $light-gray !default; +$datepicker_day_border: 1px solid $medium-gray !default; +$datepicker_day_color: $text-color !default; + +$select-hover-bg-color: $dark-blue !default; +$select-hover-text-color: $white !default; + +$thumbnail-background-color: opacify($default-border-color, 1) !default; +$medium-screen-break-point: 700px; +$large-screen-break-point: 1000px; diff --git a/app/assets/stylesheets/alchemy/admin.scss b/app/assets/stylesheets/alchemy/admin.scss index 35c4ed2d475..e657f50327e 100644 --- a/app/assets/stylesheets/alchemy/admin.scss +++ b/app/assets/stylesheets/alchemy/admin.scss @@ -4,6 +4,9 @@ *= require_self */ +@import "fontawesome/fontawesome"; +@import "fontawesome/regular"; +@import "fontawesome/solid"; @import "alchemy/defaults"; @import "alchemy/archive"; @import "alchemy/navigation"; @@ -12,27 +15,37 @@ @import "alchemy/buttons"; @import "alchemy/dialogs"; @import "alchemy/toolbar"; +@import "alchemy/clipboard"; @import "alchemy/dashboard"; @import "alchemy/elements"; @import "alchemy/errors"; +@import "alchemy/filter_field"; @import "alchemy/flash"; +@import "alchemy/flatpickr"; +@import "alchemy/fonts"; @import "alchemy/forms"; @import "alchemy/form_fields"; @import "alchemy/frame"; @import "alchemy/hints"; @import "alchemy/icons"; @import "alchemy/image_library"; -@import "alchemy/modules"; +@import "alchemy/labels"; +@import "alchemy/nodes"; +@import "alchemy/node-select"; @import "alchemy/notices"; +@import "alchemy/page-select"; @import "alchemy/pagination"; +@import "alchemy/preview_window"; @import "alchemy/resource_info"; @import "alchemy/search"; @import "alchemy/selects"; @import "alchemy/sitemap"; @import "alchemy/spinner"; @import "alchemy/tables"; +@import "alchemy/tags"; +@import "alchemy/trash"; +@import "alchemy/typography"; @import "alchemy/lists"; @import "alchemy/upload"; @import "alchemy/jquery-ui"; @import "jquery.Jcrop.min"; -@import "jquery.datetimepicker"; diff --git a/app/assets/stylesheets/alchemy/archive.scss b/app/assets/stylesheets/alchemy/archive.scss index c504e3b57ea..cc696824c27 100644 --- a/app/assets/stylesheets/alchemy/archive.scss +++ b/app/assets/stylesheets/alchemy/archive.scss @@ -1,28 +1,46 @@ +.resources-header { + padding: 2 * $default-padding; +} + +.resources-table-wrapper { + padding-bottom: 60px; + + &.with_tag_filter { + padding-right: 242px; + } +} + div#image_assign_filter_and_image_sizing { width: 100%; height: 40px; } #picture_archive { - .selected_item_tools { - margin: -2*$default-padding; + margin: -2 * $default-padding; border-bottom: 1px solid $default-border-color; - margin-bottom: 4*$default-padding; - padding: 4*$default-padding; + margin-bottom: 4 * $default-padding; + padding: 4 * $default-padding; display: none; } } +.thumbnail_background { + position: relative; + display: flex; + align-items: center; + justify-content: center; + background-color: $thumbnail-background-color; + width: 100%; + height: 120px; +} + .picture_thumbnail { - padding: $default-padding; - margin: 2px 1px 2px 2px; + margin: 2 * $default-margin; background-color: #fff; - float: none; - display: inline-block; - border: 1px solid #c0c0c0; position: relative; - @extend %rounded-border; + box-shadow: 0 0 0 1px $default-border-color; + width: 160px; img { width: auto; @@ -31,90 +49,53 @@ div#image_assign_filter_and_image_sizing { max-height: 100%; } - .thumbnail_background { - @extend %thumbnail-background; - width: 160px; - height: 120px; - } - .picture_name { - height: 14px; display: block; + width: 100%; text-align: center; white-space: nowrap; overflow: hidden; - position: absolute; - bottom: 1px; + bottom: 0; left: 0; - font-family: "Courier New", Courier, mono; - line-height: 11px; - padding: 2px 0; - margin-right: 4px; - margin-left: 4px; + line-height: 2; + padding: 0 $default-padding; + text-overflow: ellipsis; } &.small { width: 80px; - height: 80px; - - .picture_name { - width: 80px; - } .thumbnail_background { - width: 80px; height: 60px; } } - &.medium { - width: 160px; - height: 140px; - - .picture_name { - width: 160px; - } - - .thumbnail_background { - width: 160px; - height: 120px; - } - } - &.large { width: 240px; - height: 200px; - - .picture_name { - width: 240px; - } .thumbnail_background { - width: 240px; height: 180px; } } &:hover { - - .picture_tool { - display: block; + .picture_tool, + .picture_tags { + visibility: visible; + opacity: 1; } } } -#pictures { - margin-right: 232px; - padding-bottom: 60px; - - .picture_thumbnail { - margin: 0 8px 8px 0; - display: inline-block; - float: none; +#pictures, +#overlay_picture_list { + display: flex; + flex-wrap: wrap; +} - .thumbnail_background { - @include zoom-in; - } +#pictures { + .thumbnail_background { + @include zoom-in; } } @@ -129,25 +110,29 @@ div.assign_image_list_image { } .picture_tool { - width: 16px; - height: 16px; + width: 20px; + height: 20px; position: absolute; background-color: white; - top: 3px; - padding: 1px; + top: $default-padding; + padding: $default-padding / 2; z-index: 10; - display: none; + text-align: center; + border-radius: $default-border-radius; + box-shadow: 0 0 1px $dark-gray; &.visible { - display: block; + visibility: visible; + opacity: 1; } &.hidden { - display: none; + // Overwrite the `.hidden { display: none }` style + display: block; } &.select { - left: 4px; + left: $default-padding; input { margin: 0; @@ -156,11 +141,7 @@ div.assign_image_list_image { } &.delete { - right: 4px; - - a { - background: image-url('alchemy/icons.png') no-repeat -63px -72px; - } + right: $default-padding; } a { @@ -171,21 +152,45 @@ div.assign_image_list_image { } } +.picture_tags { + overflow: hidden; + position: absolute; + bottom: 22px; + left: 0; + width: 100%; + max-height: 80%; + padding: $default-padding; + pointer-events: none; +} + +.picture_tags, +.picture_tool { + visibility: hidden; + opacity: 0; + transition: opacity $transition-duration; +} + div#library_sidebar { - box-sizing: border-box; + display: flex; + flex-direction: column; position: fixed; border-left: 1px solid $default-border-color; top: 0; right: 0; width: 232px; - padding-top: 96px; - padding: 84px 4*$default-padding 0 4*$default-padding; + + padding: $top-menu-height + 2 * $default-padding 4 * $default-padding + $pagination-height + 2 * $default-padding 4 * $default-padding; height: 100%; z-index: 3; background-color: $light-gray; h2 { - margin-top: 4*$default-margin; + margin-top: 4 * $default-margin; + } + + h3 { + margin: 2em 0 1em; } } @@ -194,121 +199,28 @@ div#library_sidebar { } div#filter_bar { - .selectboxit { width: 194px; } } -.tag-list { - box-sizing: border-box; - height: 100%; - padding-bottom: 138px; - - &.filtered { - padding-bottom: 164px; - } - - &.with_filter_bar { - padding-bottom: 218px; - - &.filtered { - padding-bottom: 244px; - } - } - - .js_filter_field_box { - position: absolute; - right: auto; - top: auto; - width: 200px; - z-index: 1100; - - input { - width: 200px; - background-color: #f8f8f8; - } - - label { display: none } - } - - .js_filter_field_clear { - right: 4px; - } - - ul { - box-sizing: border-box; - list-style-type: none; - padding: 0; - margin: 40px 0 4px 0; - height: 100%; - width: 200px; - overflow-x: hidden; - overflow-y: auto; - - li { - @include tag-base; - display: block; - - &:first-child { margin-top: 0 } - - a { - text-decoration: none; - display: block; - } - - &:hover { background-color: $very-light-blue } - - &.active { - background-color: $dark-gray; - - a, &:before { color: $light-gray } - } - } - } -} - .alchemy-dialog-body { - #library_sidebar { - padding: 0 4*$default-padding; + padding: 0 4 * $default-padding; } } -#assign_image_list, #assign_file_list { +#assign_image_list, +#assign_file_list { position: relative; - height: 519px; + height: 548px; } #assign_image_list { padding-right: 244px; - - .tag-list ul { - height: 316px; - } - - &.filtered .tag-list ul { - height: 292px; - } -} - -#assign_file_list { - - .tag-list ul { - height: 396px; - } - - &.filtered .tag-list ul { - height: 372px; - } } #overlay_file_list { - - &.with_tag_list { - padding-right: 234px; - } - .assign_file_file { display: block; margin-bottom: 4px; @@ -328,7 +240,7 @@ div#filter_bar { display: block; line-height: 25px; text-decoration: none; - padding: 0px 4px 2px 4px; + padding: $default-padding 2 * $default-padding; } .icon { @@ -338,45 +250,6 @@ div#filter_bar { } } -.picture_tags { - box-sizing: border-box; - width: 50%; - max-height: 100%; - position: absolute; - top: 16px; - left: 0; - padding: $default-padding; - pointer-events: none; - display: none; -} - -.picture_thumbnail:hover .picture_tags { - display: block; -} - -.tag { - display: inline-block; - vertical-align: middle; - @extend %rounded-border; - padding: $default-padding 2*$default-padding $default-padding 4*$default-padding; - margin: $default-margin/2 0; - background-color: rgba($medium-gray, 0.85); - color: $text-color; - overflow: hidden; - position: relative; - pointer-events: none; - font-size: 10px; - - &:before { - position: absolute; - left: $default-padding; - top: 2*$default-padding; - font-family: 'Alchemy Icons'; - @extend .icon-tag:before; - line-height: 6px; - } -} - -.tags .list .tag { - padding: 0; +#all_files td.name a { + @include zoom-in; } diff --git a/app/assets/stylesheets/alchemy/base.scss b/app/assets/stylesheets/alchemy/base.scss index 46d60f8cbaa..f266206dd08 100644 --- a/app/assets/stylesheets/alchemy/base.scss +++ b/app/assets/stylesheets/alchemy/base.scss @@ -1,13 +1,21 @@ html { + box-sizing: border-box; height: 100%; + font-size: $base-font-size; &.turbolinks-progress-bar::before, .turbolinks-progress-bar { - background-color: #fff !important; + background-color: $blue !important; z-index: 400001; } } +*, +*::before, +*::after { + box-sizing: inherit; +} + noscript { position: fixed; top: 0; @@ -30,7 +38,7 @@ body { padding: 0; color: $text-color; font: $default-font-style; - background-color: $light-gray; + background-color: $body-background-color; height: 100%; cursor: default; @@ -42,74 +50,26 @@ body { } } -h1 { - padding: 0; - margin: 8px; - font-weight: bold; - font-size: $default-font-size; - - &.resources-header { - position: fixed; - width: 100%; - top: 75px; - left: $main-menu-width + 9px; - z-index: 2; - margin: 0 -8px; - padding: 16px; - background-color: $light-gray; - } -} - -h2 { - margin: $default-margin 0; - padding-bottom: 5px; - clear: both; - font-weight: bold; - font-size: $default-font-size; -} - -h3 { - margin: $default-margin 0; - padding-bottom: 5px; - font-size: $default-font-size; - font-weight: bold; -} - -p { - margin: 0 0 2*$default-margin 0; - white-space: normal; -} - a { - color: $text-color; + color: inherit; text-decoration: none; + transition: $transition-duration; &:hover { text-decoration: underline; } - &:focus { - @extend %blue-focus-style; - color: black; - } - img { border: none; } } -form { - margin-bottom: 0; +a:focus, [tabindex]:focus { + @include default-focus-style; } -ul { - padding-left: 16px; - margin-top: 1em; - margin-bottom: 1em; - - li { - margin-bottom: 0.5em; - } +form { + margin-bottom: 0; } hr { @@ -118,6 +78,15 @@ hr { opacity: 0.5; } +.panel { + width: 80%; + max-width: 500px; + margin: 2em auto; + padding: 4*$default-padding; + background: $medium-gray; + border-radius: $default-border-radius; +} + .float_right { float: right } .float_left { float: left } @@ -126,16 +95,6 @@ hr { text-align: center; } -kbd { - font-family: Courier, monospaced; - background: $medium-gray; - border: $default-border; - padding: 0.25em 0.5em; - vertical-align: top; - margin-right: 1ex; - @extend %rounded-border; -} - .top-rounded-border { @include border-top-radius($default-border-radius); } @@ -157,380 +116,25 @@ kbd { } .disable-user-select { - @include user-select(none); -} - -.border-box-sizing { - box-sizing: border-box; -} - -span.updated_at { - float: right; -} - -a.drop_down_select img { - float: left; - position: relative; - top: 2px; - margin-right: 4px; -} - -#language_select { - margin-bottom: 4px; -} - -div.source_editor textarea { - width: 100%; - height: 358px; -} - -div.edit_source_buttons { - height: 18px; - margin-top: 8px; - float: right; - clear: both; -} - -div#page { - height: auto !important; - height: 100%; - min-height: 100%; - position: relative; -/* needed for footer positioning*/ -} - -#archive_all .edit_images_bottom span { - width: 19px; - height: 19px; - float: left; - margin-right: 4px; -} - -#archive_all .edit_images_bottom span a { - display: block; - height: 19px; - width: 19px; - background-repeat: no-repeat; - text-decoration: none; - background-position: left; -} - -#archive_all .edit_images_bottom span a:hover { - background-position: right; -} - -#back_to_site { - float: right; - margin-right: 8px; -} - -div#new_page_from_clipboard { - @extend .hidden; -} - -div#new_page_from_clipboard input.button { - float: right; -} - -div#new_page_from_clipboard span { - float: left; - line-height: 25px; -} - -div#page_status_options { - float: left; -} - -div#page_editing_menu { - overflow: auto; - padding-left: 1px; - padding-right: 1px; -} - -#page_sorting_panel p { - margin-top: 1em; + user-select: none; } .handle { cursor: move; } -.headline_text_editor { - overflow: auto; - width: 374px; -} - -.short_content_text { - width: 374px; - background-color: white; - margin-bottom: 10px; - padding-bottom: 2px; - padding-top: 2px; - padding-left: 2px; - float: none; -} - -.short_content_text a { - color: #000000; - text-decoration: none; - font-weight: bold; -} - -.short_content_text a:hover { - color: #000000; - text-decoration: underline; -} - -.picture_tool_assign_box { - float: right; - width: 105px; - overflow: auto; - padding: 1px; -} - -/* @end */ - -.short_picture { - float: left -} - -.short_element { - margin-left: 20px; - float: left; -} - -.short_element input { - width: 190px; - float: left; - font: 11px $default-font-face; -} - -.dashed_border { - border: 1px dashed; -} - -ul#all_files { - list-style-type: none; - display: block; - margin: 0; - padding: 0; - width: 100%; -} - -img.image_in_text { - position: relative; - top: 3px; -} - -div.right_checkbox { - float: left; -} - -div.all_rights { - float: left; - clear: both; - margin-top: 10px; -} - -.date_select_without_time #date_date_4i { - @extend .hidden; -} - -.date_select_without_time #date_date_5i { - @extend .hidden; -} - .with_padding { padding: 2*$default-padding; } .with_margin { - margin: 2*$default-padding; -} - -div#images { - margin-top: 16px; - margin-bottom: 8px; - overflow: visible; -} - -.foot_note, form .input .hint { - @include form-hint; -} - -#all_files td.name a { - @include zoom-in; -} - -#sitemap .placeholder { - background-color: $medium-gray; - margin-bottom: 0px; - margin-left: 22px; -} - -#page_selector_scroll_container { - position: relative; - width: 100%; -} - -#page_selector_container { - height: 230px; - background-color: $light-gray; - margin-bottom: 4px; - border: 1px inset #e8e8e8; - overflow: auto; - padding: 4px 0 0 4px; - @extend %rounded-border; -} - -#alchemyConfirmation .alchemy_window_buttons { - display: block; - text-align: right; - margin-top: 8px; - padding-right: 8px; -} - -#alchemyConfirmation_content div.alchemy_window_message { - padding: 2*$default-padding; -} - -input.with_border { - margin: 0; - padding: 1px; - font-size: 12px; - border: 1px solid silver; -} - -#image_edit div { - height: 25px; -} - -#image_edit div span { - float: left; - padding: 0; - margin-right: 6px; - height: 17px; - line-height: 19px; + margin: 2*$default-margin; } a img { border-style: none; } -#sitemap_external_links form { - margin-left: 5px; - margin-bottom: 4px; -} - -input#link_to_text { - border-width: 1px; - font-size: 11px; - line-height: 11px; - width: 229px; -} - -.link_window_tab_body { - background-color: $light-gray; - padding: 2*$default-padding; - margin: 0; -} - -.bordertop_white { - border-top: 4px solid white; - padding-top: 8px; - padding-bottom: 8px; - overflow: auto; - float: none; -} - -#external_link_table .padding_right { - padding-right: 8px; -} - -#external_link_table .external_address_input { - width: 195px; -} - -table.window_form td.checkbox { - text-align: left; -} - -select#url_protocol.medium { - margin: 0; -} - -#clipboard_items { - - ul { - list-style-type: none; - margin: 0 0 8px; - padding: 0; - height: 220px; - overflow-y: auto; - overflow-x: hidden; - - li { - padding: 2*$default-padding; - border: $default-border; - background-color: white; - @extend %rounded-border; - - &.elements { - background-color: $medium-gray; - } - - .icon { - vertical-align: text-bottom; - margin-right: $default-padding; - } - - .float_right .icon { - margin-right: 0; - } - } - } -} - -#trash_items { - height: 310px; - overflow-x: hidden; - overflow-y: auto; - margin-bottom: 1em; -} - -.mceEditor table { - border-spacing: 0 !important; -} - -div.browse { - margin-bottom: 1em; -} - -#alchemy_preview_window { - box-sizing: border-box; - position: absolute; - left: $main-menu-width; - top: 75px; - width: auto; - height: auto; - border: 0 none; - background: #fff; - border-right: $default-border; - @include transition(200ms ease-in-out); - - .collapsed-menu & { - left: $collapsed-main-menu-width; - } -} - -#error_trace { - border: $default-border; - width: 80%; - padding: $default-padding; - overflow: auto; - @extend %rounded-border; -} - .full_width { width: 100%; } diff --git a/app/assets/stylesheets/alchemy/buttons.scss b/app/assets/stylesheets/alchemy/buttons.scss index a0871ef134e..9bbb25da898 100644 --- a/app/assets/stylesheets/alchemy/buttons.scss +++ b/app/assets/stylesheets/alchemy/buttons.scss @@ -2,48 +2,48 @@ button, input[type="submit"], a.button, input.button { @include button-defaults; position: relative; + &.secondary { + background-color: $secondary-button-bg-color; + color: $secondary-button-text-color; + border-color: $secondary-button-border-color; + + &:focus { + @include default-focus-style( + $border-color: $button-focus-border-color, + $box-shadow: $button-focus-box-shadow + ); + } + + &:active, &:active:focus { + border-color: $button-hover-border-color; + box-shadow: none; + } + } + &.small { - padding: $default-padding 3*$default-padding; + padding: $small-button-padding; vertical-align: inherit; line-height: 4*$default-padding; font-size: inherit; - - &.with_icon { - padding: $default-padding 3*$default-padding $default-padding 6px; - } } - &.with_icon { - padding: 6px 18px 6px 6px; - - span.icon { - margin-top: -5px; - padding-right: 0.25em; - height: 15px; - } + &.with_icon .icon { + position: relative; + top: -1px; + margin-right: 2 * $default-margin; + color: inherit; } - &.icon_only { - margin: 0; - padding: 0; - border: 0 none; - box-shadow: none; - @include background(none); - border-radius: 0; + &[disabled] { + cursor: not-allowed; - .icon { - margin: 0; + & + label { + display: none; } } - - &.disabled { - line-height: 13px; - height: 29px; - } } .icon_button { - box-sizing: border-box; width: 27px; height: 27px; line-height: 21px; @@ -51,23 +51,8 @@ button, input[type="submit"], a.button, input.button { padding: 3px; overflow: hidden; text-align: center; - - .icon { - display: inline-block; - vertical-align: middle; - margin-top: -2px; - } - - .old_icon { - position: relative; - background-repeat: no-repeat; - width: 22px; - height: 20px; - display: inline-block; - vertical-align: middle; - top: -1px; - left: -1px; - } + text-shadow: none; + border-radius: $default-border-radius; img { position: absolute; @@ -86,47 +71,34 @@ button, input[type="submit"], a.button, input.button { top: 0px; background: none; border: none; - box-shadow: none; .icon { position: absolute; top: 50%; - height: 8px; - @include transform(translateX(-50%) translateY(-50%)); - - &:before { - line-height: 10px; - } - - &.delete-small { - background-position: -4px -76px; - } + transform: translateX(-50%) translateY(-50%); + transition: color 250ms; } &:hover { - border: $default-border-width $default-border-style $button-hover-border-color; - border-radius: $default-border-radius; - background: $button-hover-bg-color; - } - } - - &.old_icon { - width: 20px; - height: 19px; + border: $default-border-width $default-border-style $icon-color; + color: $icon-color; - img { - top: 0; - left: 1px; + .icon { color: inherit } } } - &.disabled { + &.disabled, + &[disabled] { span { opacity: 0.3; cursor: not-allowed; } + i { + opacity: 0.3; + } + &:hover { cursor: not-allowed; } @@ -137,14 +109,13 @@ button, input[type="submit"], a.button, input.button { } &:focus { - @extend %blue-focus-style; + @include default-focus-style; } } button.icon_button { background: transparent; border: 0 none; - border-radius: 0; box-shadow: none; &:disabled, &.disabled { @@ -159,30 +130,13 @@ button.icon_button { .button_with_label { position: relative; display: inline-block; - vertical-align: top; text-align: center; margin: 0 2*$default-margin; - line-height: 0; - &.active, &:active { - - .icon_button { - background-color: darken($white, 10%); - border: $default-border; - padding: 2px; - @extend %rounded-border; - } - } - - &:hover .icon_button { - border: $default-border; - padding: 2px; - @extend %rounded-border; - cursor: pointer; - - &:focus { - border: 0; - padding: 3px; + &.active, &:active, &:hover { + .icon_button:not([disabled]) { + background-color: $default-border-color; + cursor: pointer; } } @@ -212,20 +166,20 @@ button.icon_button { .icon_button { width: 29px; height: 29px; - line-height: 23px; + line-height: 24px; } label { - @extend %rounded-border; + border-radius: $default-border-radius; position: absolute; visibility: hidden; top: -20px; left: 50%; margin-left: -13px; z-index: 30; - background: $dark-gray; - color: #fff; - padding: $default-padding 2*$default-padding; + background-color: $tooltip-background-color; + color: $white; + padding: $default-padding 2*$default-padding 1.5*$default-padding; line-height: 1; box-shadow: 0 0 4px $default-border-color; white-space: nowrap; @@ -239,7 +193,7 @@ button.icon_button { bottom: -10px; left: 8px; border: 5px transparent solid; - border-top-color: $dark-gray; + border-top-color: $tooltip-background-color; } &.center { diff --git a/app/assets/stylesheets/alchemy/clipboard.scss b/app/assets/stylesheets/alchemy/clipboard.scss new file mode 100644 index 00000000000..b8369aeba58 --- /dev/null +++ b/app/assets/stylesheets/alchemy/clipboard.scss @@ -0,0 +1,26 @@ +#clipboard_items { + + ul { + list-style-type: none; + margin: 0 0 8px; + padding: 0; + height: 220px; + overflow-y: auto; + overflow-x: hidden; + + li { + padding: 2*$default-padding; + border: $default-border; + background-color: white; + border-radius: $default-border-radius; + + &.elements { + background-color: $medium-gray; + } + + > .icon { + margin-right: $default-padding; + } + } + } +} diff --git a/app/assets/stylesheets/alchemy/dashboard.scss b/app/assets/stylesheets/alchemy/dashboard.scss index 8b776cda1ef..3d562c86d42 100644 --- a/app/assets/stylesheets/alchemy/dashboard.scss +++ b/app/assets/stylesheets/alchemy/dashboard.scss @@ -1,34 +1,26 @@ #dashboard { overflow: auto; - padding: 4*$default-padding 2*$default-padding; + padding: 4*$default-padding 0; width: 100%; position: relative; - box-sizing: border-box; - * { box-sizing: border-box } h1 { - font-size: 2em; - margin: 0 0 8px 0; + font-size: 1.5em; + margin: 0 0 8px 8px; } - h2 { - padding: 0; - margin-bottom: 16px; + p { + margin: 0 0 16px 8px; } - div.column { + .column { width: 50%; float: left; padding-right: 16px; } - div.widget { - width: 100%; + .widget { margin-bottom: 16px; - background-color: $medium-gray; - border: $default-border; - @extend .top-rounded-border; - float: left; h2 { padding: 2 * $default-padding; @@ -40,45 +32,20 @@ } table { - width: 100%; - padding: 0; - border-spacing: 0; - td { - padding: 2 * $default-padding; - margin-bottom: 0; position: relative; - color: #aaa; &:first-child { width: 40% } form { float: right } - span { color: #333 } } } } } -div#sideboard { - right: 16px; - top: 82px; - position: absolute; - width: 240px; - padding: 16px; - - div.sideboard_widget img { - margin-bottom: 16px; - float: none; - } -} - -div#license { - overflow: auto; - height: 24em; - background-color: white; -} - #update_check { + position: relative; height: 16px; + margin-bottom: 2em; img#load_info { width: 16px; diff --git a/app/assets/stylesheets/alchemy/dialogs.scss b/app/assets/stylesheets/alchemy/dialogs.scss index 6913c3eb5d0..0d29bfa9308 100644 --- a/app/assets/stylesheets/alchemy/dialogs.scss +++ b/app/assets/stylesheets/alchemy/dialogs.scss @@ -1,3 +1,5 @@ +$dialog-transition-duration: 150ms; + .alchemy-image-overlay-container, .alchemy-dialog-container { position: fixed; @@ -10,7 +12,10 @@ text-align: center; white-space: nowrap; opacity: 0; - @include transition(opacity 200ms linear); + transform: scale(0.98); + transition-property: transform, opacity; + transition-duration: $dialog-transition-duration; + transition-timing-function: ease-in; // vertical aligment &:before { @@ -24,6 +29,7 @@ &.open { opacity: 1; + transform: scale(1); } } @@ -36,7 +42,7 @@ width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.0); - @include transition(background-color 200ms linear); + transition: background-color $dialog-transition-duration linear; &.closable { cursor: pointer; @@ -46,14 +52,14 @@ .alchemy-dialog-overlay { &.open { - background-color: rgba(0, 0, 0, 0.4); + background-color: rgba($dialog-overlay-color, $dialog-overlay-opacity); } } .alchemy-dialog, .alchemy-image-overlay-dialog { position: relative; - max-width: 100%; + max-width: calc(100vw - 16px); display: inline-block; vertical-align: middle; text-align: left; @@ -61,29 +67,30 @@ .alchemy-dialog { white-space: normal; - background-color: $light-gray; margin: 2*$default-margin; @include border-top-radius($default-border-radius); - box-shadow: 0 0 2px rgba(#000,0.8); - @include transition(min-height 200ms ease-in-out); - - &.modal { - box-shadow: 0 0 4px #666; - } + box-shadow: $dialog-box-shadow; + background-color: $dialog-background-color; + transition: min-height $dialog-transition-duration ease-in-out; div#jscropper { padding: 2 * $default-padding; .thumbnail_background { - @extend %thumbnail-background; width: 800px; height: 600px; position: relative; } + form, + .info.message { + position: absolute; + right: 2 * $default-padding; + width: 256px; + } + .info.message { - float: right; - width: 134px; + top: 2 * $default-padding; margin: 0 0 0 8px; padding: 0 8px 0 32px; } @@ -99,35 +106,30 @@ form { padding: 0; - position: absolute; bottom: 2 * $default-padding; - right: 2 * $default-padding; - width: 176px; margin-left: 8px; - .reset_mask, button { - display: block; + button { margin-bottom: 0; - text-align: center; + width: calc(50% - #{$default-padding}); } - .reset_mask { padding-top: 4px } - - button { width: 100% } + button[type="submit"] { + float: right; + } } } } .alchemy-dialog-header { - box-sizing: border-box; position: relative; height: $dialog-header-height; padding: 0 2*$default-padding; line-height: $dialog-header-height; - background: $header-color; - color: $header-text-color; + background: $dialog-header-color; + color: $dialog-header-text-color; font-weight: normal; - font-size: $header-font-size; + font-size: $dialog-header-font-size; @include border-top-radius($default-border-radius); .ui-draggable & { @@ -142,6 +144,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + @include antialiased-font-smoothing; } .alchemy-image-overlay-close, @@ -149,11 +152,6 @@ position: absolute; z-index: 1; cursor: pointer; - - .icon.close.small { - line-height: 1; - vertical-align: text-top; - } } .alchemy-dialog-close { @@ -162,13 +160,12 @@ padding: 0 $default-padding; color: inherit; - .icon.close.small { - font-size: 16px; + .icon { + color: inherit; } } .alchemy-dialog-body { - box-sizing: border-box; position: relative; color: $text-color; @@ -184,18 +181,6 @@ } } - ul#sitemap ul li span.sitemap_sitename { - - &.selected_page a { - font-weight: bold; - padding-left: 4px; - } - - a:hover { - font-weight: bold; - } - } - .buttons { text-align: right; @@ -207,11 +192,7 @@ } .alchemy-dialog-buttons { - box-sizing: border-box; - float: left; - clear: both; - width: 100%; - padding: 4*$default-padding 0; + padding-top: 4*$default-padding; text-align: right; button:first-child { @@ -225,4 +206,8 @@ .ui-tabs-panel { padding: 4*$default-padding; } + + .ui-tabs.ui-corner-all { + border-radius: 0; + } } diff --git a/app/assets/stylesheets/alchemy/elements.scss b/app/assets/stylesheets/alchemy/elements.scss index 1ac5d306310..e293627ac15 100644 --- a/app/assets/stylesheets/alchemy/elements.scss +++ b/app/assets/stylesheets/alchemy/elements.scss @@ -1,22 +1,38 @@ #alchemy_elements_window { - box-sizing: border-box; position: absolute; right: 0; - top: 73px; - width: 400px; - height: auto; + top: $top-menu-height; + z-index: 20; + width: calc(100vw - #{$collapsed-main-menu-width}); + height: calc(100vh - #{$top-menu-height}); border-left: $default-border; - @include transition(200ms ease-in-out); + background-color: $light-gray; + transition: $transition-duration ease-in-out; + transform: translate3d(100%, 0, 0); + + .elements-window-visible & { + transform: translate3d(0, 0, 0); + } + + // Fix for Tinymce fullscreen window positioning issues (GH#1511) + .mce-fullscreen & { + width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width}); + } + + @media screen and (min-width: $large-screen-break-point) { + width: $elements-window-width; + min-width: $elements-window-min-width; + } } #element_area { - box-sizing: border-box; + height: calc(100vh - #{$top-menu-height + $toolbar-height}); overflow-x: hidden; overflow-y: auto; + -webkit-overflow-scrolling: touch; - .sortable_cell { + .sortable-elements { min-height: 100px; - padding: 2*$default-padding $default-padding 2px; } textarea { @@ -27,6 +43,15 @@ height: 140px !important; } } + + > .message { + margin: 2*$default-margin + } +} + +#main-content-elements, +.element-editor.is-fixed .nestable-elements { + padding: 2*$default-padding $default-padding 2px; } .element-title { @@ -41,13 +66,13 @@ } .preview_text_quote { - font-size: 10px; + font-size: $small-font-size; line-height: 15px; } } .preview_text_element_name { - font-size: 10px; + font-size: $small-font-size; text-shadow: #efefef 1px 1px 1px; font-weight: bold; line-height: 15px; @@ -66,18 +91,8 @@ .element.icon { position: absolute; - top: 0; - left: 0; - } - - .icon { - position: absolute; - right: 0; - bottom: 0; - - &.element_dirty { - background-position: -160px -166px; - } + top: 3px; + left: 1px; } } @@ -90,7 +105,7 @@ width: 16px; height: 16px; right: 8px; - top: 8px; + top: 10px; .error_icon { float: left; @@ -103,48 +118,18 @@ } } -.add_picture { - float: left; - margin: 1px; - width: 119px; - border: 1px solid #c0c0c0; - @extend %rounded-border; - height: 123px; - - a { - display: block; - height: 100%; - width: 100%; - position: relative; - text-decoration: none; - overflow: hidden; - text-align: center; - font-weight: bold; - line-height: 104px; - @extend %rounded-border; - font-size: 40px; - background-color: $light-gray; - - &:hover { - background-color: $medium-gray; - } - - .icon { - font-size: 30px; - width: 30px; - height: 30px; - position: absolute; - top: 48px; - left: 44px; - } - } -} - .element-editor { + position: relative; border: 1px solid $default-border-color; - @extend %rounded-border; + border-radius: $default-border-radius; background-color: $light-gray; margin-bottom: 2*$default-margin; + transition: box-shadow $transition-duration; + + &.is-fixed { + border-width: 0; + border-radius: 0; + } &.not-draggable { opacity: 0.5; @@ -153,14 +138,14 @@ &.dirty { border-color: #d0c83d; - > .element-header { + >.element-header { background-color: #fff8df; } } - &.selected { - border-color: $blue; - box-shadow: 0 0 4px $blue; + &.selected:not(.is-fixed) { + border-color: #c3c3c3; + box-shadow: 0 2px 8px rgba(#9b9b9b, 0.75); } &.folded { @@ -170,30 +155,119 @@ } } + &.expanded { + &.not-fixed { + .nestable-elements { + box-shadow: inset 0 4px 8px -2px darken($medium-gray, 15%); + background-color: $medium-gray; + padding: 8px 4px 4px; + + .add-nestable-element-button { + width: calc(50% - 8px); + margin: 4px; + text-align: center; + } + } + } + } + &.dragged { border-style: dotted; - height: 36px; + overflow: hidden; - * { display: none } + &:not(.compact) { + height: 36px !important; + transition: height 0.2s; + } } - &.with-contents, &.without-contents.not-nestable { + &.compact { + .element-toolbar { + visibility: hidden; + position: absolute; + height: 35px; + padding: 2px 0; + opacity: 0; + z-index: 1; + border-bottom: $default-border; + background-color: $light-gray; + transition: all $transition-duration; + } + + .element-header:hover+.element-toolbar, + .element-toolbar:hover { + visibility: visible; + opacity: 1; + } + + .element-footer { + padding-top: 0; + border-top: 0; + + .button { + padding: $small-button-padding; + } + } + + .element-title { + max-width: 75%; + } + + &:not(.folded) .ajax-folder { + pointer-events: none; + + i:before { + content: fa-content($fa-var-ellipsis-v); + } + } + + .element_tools { + display: flex; + width: 100%; + justify-content: space-between; + margin-left: 0; + } .element-content { - padding: 2*$default-padding 2*$default-padding 0; - border-top: 1px solid $medium-gray; + margin: 4px 8px; + } + + .button_with_label { + margin: 0 4px; + } + + .content_editor, + .picture_thumbnail { + width: 100%; + } + + .picture_thumbnail { + margin: 0; + } + + .thumbnail_background { + height: 115px; + } + + textarea, + input[type="url"], + input[type="text"], + input[type="email"], + input[type="password"] { + padding: 0.5em; + height: auto; } } - form { - margin: 0; + .element-content { + margin: 2*$default-padding; } .validation_notice { float: left; margin-top: 8px; margin-bottom: 4px; - font-size: 10px; + font-size: $small-font-size; text-align: left; } @@ -205,27 +279,11 @@ margin-bottom: 1em; } - .content_editor_error { - border: 1px solid #f5b04e; - padding: 4px 8px; - line-height: 21px; - background-color: #f5dea9; - margin-top: 4px; - @extend %rounded-border; - - .icon.warning { - position: relative; - top: 2px; - margin-right: 8px; - vertical-align: top; - } - } - .autocomplete_tag_list { padding: $default-padding 0; label { - font-size: 10px; + font-size: $small-font-size; } .select2-container { @@ -251,16 +309,16 @@ .element-header { position: relative; - height: 20px; padding: 2*$default-padding; - background-color: $medium-gray; + background-color: $element-header-bg-color; @extend .disable-user-select; cursor: pointer; + @include border-top-radius($default-border-radius); - > .hint-with-icon { + >.hint-with-icon { position: absolute; right: 32px; - top: 8px; + top: 10px; .hint-bubble { left: auto; @@ -277,20 +335,18 @@ } .element-toolbar { - padding: 6px 0; - height: 6*$default-padding; - background-color: #f0f0f0; + padding: $default-padding 0; + height: $element-toolbar-height; + border-bottom: 1px solid $medium-gray; .element_tools { float: left; - margin-top: -3px; - margin-left: -4px; + margin-left: -$default-margin; } } .element-footer { border-top: 1px solid $medium-gray; - margin: 8px 0 0 0; padding: 2*$default-padding; text-align: right; @@ -299,25 +355,12 @@ } } -.picture_gallery_images { - overflow: hidden; - margin: $default-margin 0; - border: 1px solid $default-border-color; - @extend %rounded-border; - background-color: $medium-gray; - padding: 1px; - - .essence_picture_editor { - margin: 1px; - float: left; - height: 126px; - - .picture_thumbnail { margin: 0 } - } +.element-content-editors { + display: flex; + flex-wrap: wrap; } .picture_thumbnail .picture_image { - height: 93px; overflow: hidden; img.img_paddingtop { @@ -325,65 +368,63 @@ } } -#cells { +#fixed-elements { min-height: 100px; } .linkable_essence_tools { + display: flex; position: absolute; - bottom: 6px; + bottom: 2 * $default-margin; right: 0; .icon_button { - @include button-defaults; + @include button-defaults($background-color: $medium-gray, + $hover-color: darken($medium-gray, 5%), + $border: 1px solid #c0c0c0, + $box-shadow: none, + $hover-border-color: #c0c0c0, + $line-height: 30px, + $padding: 0, + $margin: 0, + $border-radius: 0, + $focus-border-color: $focus-color, + $focus-box-shadow: 0 0 0 2px $focus-color); width: 29px; - height: 28px; - margin: 0 0 0 4px; - padding: 2px; - position: absolute; - bottom: 3px; - right: 28px; - border-radius: 0; + height: $form-field-height; - &.link-essence, - &.unlink-essence { - border: 1px solid #c0c0c0; + &.disabled, + &.disabled:hover { + background-color: $form-field-disabled-bg-color; } &.linked.link-essence { - @include linked-button($border-radius: 0 0 0 0); + @include linked-button($border-radius: 0, + $line-height: 30px, + $padding: 0); + + &:focus { + border-radius: 0; + } } &.linked.unlink-essence { - @include linked-button($border-radius: 0 $default-border-radius $default-border-radius 0); + @include linked-button($border-radius: 0 $default-border-radius $default-border-radius 0, + $line-height: 30px, + $padding: 0); + + &:focus { + border-radius: 0 $default-border-radius $default-border-radius 0; + } } &.unlink-essence { - right: 0; + border-left-width: 0; @extend .right-rounded-border; } } } -.expand_element, -.fold_element { - background-image: image-url('alchemy/icons.png'); - background-repeat: no-repeat; - height: 16px; - width: 16px; - text-decoration: none; - display: block; - margin: 1px; -} - -.expand_element { - background-position: -160px -72px; -} - -.fold_element { - background-position: -192px -72px; -} - .droppable_element_placeholder { height: 36px; border: 1px dotted $button-border-color; @@ -393,45 +434,33 @@ } .edit_images_bottom { - height: 19px; position: absolute; + display: flex; + justify-content: space-between; + left: 0; + bottom: 0; z-index: 0; - left: 5px; - bottom: 2px; - width: 111px; - - > a, > span.icon { - float: left; - margin-left: 2px; - margin-right: 2px; - width: 18px; - height: 18px; - } + width: 100%; + padding: $default-padding $default-padding/2; a { + margin: 0 $default-padding/2; + padding: $default-padding/2; + cursor: pointer; &.linked { - position: relative; - @extend %rounded-border; + border-radius: $default-border-radius; background-color: $linked-color; - border: 1px solid $button-border-color; - bottom: 1px; - margin-right: 2px; - width: 18px; - height: 18px; - margin-left: 0; - - .icon { margin-left: 1px } } - } - a.disabled, > span.icon { - opacity: 0.3; - cursor: default; + &.disabled { + opacity: 0.3; + cursor: default; + } } } -.essence_picture_editor { +.essence_picture { position: relative; .picture_tool_delete { @@ -447,24 +476,18 @@ } .picture_thumbnail { - width: 111px; - height: 115px; + display: inline-block; + width: 160px; margin: $default-margin 0; - - .thumbnail_background { - width: 111px; - height: 93px; - } + padding-bottom: 28px; } &.validation_failed .picture_thumbnail { border-color: $error_border_color } - .thumbnail_background.missing:before { - font-family: 'Alchemy Icons'; - @extend .icon-pictures:before; - font-size: 48px; + .thumbnail_background .icon { + font-size: 4em; color: $medium-gray; vertical-align: top; } @@ -477,99 +500,58 @@ background-color: white; background-color: rgba(255, 254, 255, 0.7); padding: 4px 8px; - font-size: 10px; + font-size: $small-font-size; text-align: right; height: 12px; overflow: hidden; } } -.dragable_picture { - float: left; - - .picture_handle { - cursor: move; - } -} - -.picture_thumbnail .picture_handle { - position: absolute; - z-index: 1; - width: 110px; - height: 93px; - top: 4px; - left: 4px; - background-color: transparent; - @extend .disable-user-select; -} - -div.file { - padding: 0; - margin-top: 8px; - margin-bottom: 4px; - border: 1px solid #c0c0c0; - background-color: #f3f3f3; - @extend %rounded-border; - height: 24px; - - .validation_failed & { - color: $error_text_color; - border-color: $error_border_color; - } -} +.content_editor.essence_file { -div.file_icon { - text-align: center; - float: left; - height: 24px; - width: 24px; + .file { + margin: 6px 0 $default-margin; + border: $default-border; + background-color: $white; + border-radius: $default-border-radius; + height: $form-field-height; - span.icon { - margin: $default-margin; + .validation_failed & { + color: $error_text_color; + border-color: $error_border_color; + } } - a.assign_file { - display: block; - height: 16px; - background: image-url('alchemy/icons.png') no-repeat -480px -40px; - width: 16px; - margin: $default-margin; + .file_icon { + display: inline-block; + text-align: center; + float: left; + width: 24px; + padding: 6px 4px; } -} - -.content_editor.essence_file { .file_name { white-space: nowrap; overflow: hidden; float: left; max-width: 80%; - line-height: 25px; - font-size: 10px; + line-height: 28px; + font-size: $small-font-size; text-overflow: ellipsis; } } .essence_file_tools { - height: 24px; + height: 27px; float: right; background-color: white; width: 48px; + border-radius: 0 $default-border-radius $default-border-radius 0; a { text-decoration: none; - width: 16px; - height: 16px; - margin: $default-margin; + padding: 6px $default-margin; float: left; - - &.assign_file { - background: image-url('alchemy/icons.png') -512px -40px; - } - - &.edit_file { - background: image-url('alchemy/icons.png') -160px -167px; - } } } @@ -594,10 +576,12 @@ select.long { } .content_editor { + width: 100%; padding: $default-padding 0; position: relative; - .thin_border, input[type="text"] { + .thin_border, + input[type="text"] { width: 100%; } @@ -613,12 +597,6 @@ select.long { width: 70%; } - .icon.warning { - vertical-align: text-bottom; - margin-left: 2px; - margin-right: 6px; - } - .button { float: right; margin: 0; @@ -632,15 +610,22 @@ select.long { margin-right: 4px; vertical-align: top; - .thin_border, input[type="text"] { + .thin_border, + input[type="text"] { width: 170px; } } &.validation_failed { - label { color: $error_text_color } - input { @extend %field-with-error } + label { + color: $error_text_color + } + + input { + @extend %field-with-error + } + .tinymce_container { outline: 1px solid $error_border_color; } @@ -648,9 +633,8 @@ select.long { label { display: block; - margin-bottom: 2px; - font-size: 10px; - text-shadow: #fff5e1 1px 1px 0; + margin: $default-margin 0; + font-size: $small-font-size; line-height: 15px; text-indent: 1px; @@ -665,29 +649,13 @@ select.long { min-width: 90px; margin-right: 4px; } - - a.icon_button.small { - width: 10px; - height: 10px; - display: none; - - .icon { - top: 1px; - right: 1px; - } - - &:hover { - - .icon { - right: 2px; - } - } - } } &.essence_select { - label { margin-bottom: 4px } + label { + margin-bottom: 2*$default-margin + } .select2-container { width: 100%; @@ -698,16 +666,11 @@ select.long { } } - &:hover label a.icon_button.small { - display: inline-block; - vertical-align: middle; - } - select.essence_editor_select { - @extend %rounded-border; + border-radius: $default-border-radius; background: white; border: 1px solid $button-border-color; - font-size: 11px; + font-size: $default-font-size; height: 21px; line-height: 21px; padding: 2px; @@ -730,11 +693,15 @@ select.long { } } - &.essence_picture_editor { - float: none; - height: auto; - display: inline-block; - vertical-align: middle; + &.essence_picture { + width: 50%; + padding-left: 1px; // Compensate the box shadow + padding-right: $default-padding; + + +.essence_picture { + padding-left: $default-padding; + padding-right: 1px; // Compensate the box shadow + } } } @@ -751,10 +718,10 @@ textarea.has_tinymce { visibility: hidden; } -.essence_html_editor textarea { - font-family: $mono-font-face; +.essence_html textarea { + font-family: $mono-font-family; resize: vertical; - font-size: 11px; + font-size: $form-field-font-size; } .text_short_float_left { @@ -771,7 +738,7 @@ textarea.has_tinymce { background-color: $error_background_color; padding: 2*$default-padding; list-style-type: none; - @extend %rounded-border; + border-radius: $default-border-radius; color: $error_text_color; border: 1px solid $error_border_color; @@ -783,10 +750,10 @@ textarea.has_tinymce { .content_editor .hint-with-icon, .content_editor .with-hint, -.element-handle .warning { - margin-left: 0; +.element-handle .hint-with-icon { + margin: 0; - > .hint-bubble { + >.hint-bubble { left: -7px; transform: none; @@ -796,37 +763,58 @@ textarea.has_tinymce { } } -.content_editor .hint-with-icon { +.element-handle .hint-with-icon { top: -1px; } -.nestable-elements { - box-shadow: inset 0 4px 8px -2px darken($medium-gray, 15%); - background-color: $medium-gray; +.is-fixed { + &.with-contents { + >.element-footer { + border-top: 0; + border-bottom: 1px solid $medium-gray; + } + } - .expanded.element-editor > & { - padding: 16px 8px 8px; + >.nestable-elements .add-nestable-element-button { + width: 100%; + text-align: center; + } +} + +.element-editor, +.droppable_element_placeholder { + width: 100%; + + .not-fixed & { + width: calc(100% - 8px); + margin: 4px; + } + + &.compact { + width: calc(50% - 8px); } } .nested-elements { + display: flex; + flex-wrap: wrap; + + .element-editor { + position: relative; + } .element-header { background-color: transparent; } .element-toolbar { - background-color: transparent; + width: 100%; border-top: 1px solid $medium-gray; } } -.add-nestable-element-button { - margin-top: 0; -} - .essence_date--label { position: absolute; - right: 5px; - top: 31px; + right: 7px; + top: 37px; } diff --git a/app/assets/stylesheets/alchemy/errors.scss b/app/assets/stylesheets/alchemy/errors.scss index f6fc00f7a90..1a60c25dd63 100644 --- a/app/assets/stylesheets/alchemy/errors.scss +++ b/app/assets/stylesheets/alchemy/errors.scss @@ -5,7 +5,7 @@ margin-bottom: 8px; border: 1px solid #d08f91; color: #690001; - @extend %rounded-border; + border-radius: $default-border-radius; h2 { font-size: 1.2em; @@ -21,7 +21,7 @@ div#errors, div.errors { padding: 8px 8px 4px 28px; border-width: 1px; border-style: solid; - @extend %rounded-border; + border-radius: $default-border-radius; border-color: $error_border_color; color: $error_text_color; background-color: $error_background_color; @@ -40,7 +40,7 @@ div#errors, div.errors { body.error { .error.message { - max-height: 95%; + max-height: 100%; overflow-y: scroll; } } diff --git a/app/assets/stylesheets/alchemy/filter_field.scss b/app/assets/stylesheets/alchemy/filter_field.scss new file mode 100644 index 00000000000..7a9ba47de56 --- /dev/null +++ b/app/assets/stylesheets/alchemy/filter_field.scss @@ -0,0 +1,30 @@ +.js_filter_field_box { + position: relative; + margin: $form-field-margin; + + > .icon { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + } + + .js_filter_field { + width: 100%; + padding-left: 28px; + padding-right: 24px; + margin: 0; + } + + .js_filter_field_clear { + display: none; + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + width: 16px; + height: 16px; + line-height: 14px; + text-align: center; + } +} diff --git a/app/assets/stylesheets/alchemy/flash.scss b/app/assets/stylesheets/alchemy/flash.scss index 338bf71a513..d6fecfd3279 100644 --- a/app/assets/stylesheets/alchemy/flash.scss +++ b/app/assets/stylesheets/alchemy/flash.scss @@ -2,7 +2,7 @@ div#flash_notices { position: fixed; right: 0; z-index: 400000; - width: 348px; + width: 400px; top: 0; .flash.error { @@ -10,33 +10,42 @@ div#flash_notices { padding-right: 24px; &:before { + content: fa-content($fa-var-times); position: absolute; right: 2*$default-padding; - top: 2*$default-padding; - @extend .icon-cross:before; - font-size: 14px; - font-family: 'Alchemy Icons'; + top: 11px; + font-size: $small-font-size; + font-family: 'Font Awesome 5 Free'; } } } div.flash { - @extend %rounded-border; + border-radius: $default-border-radius; opacity: 0.95; padding: 8px 8px 8px 30px; font-weight: bold; border-width: 1px; border-style: solid; z-index: 1000; - margin: $default-margin; + margin: $default-margin $default-margin 2*$default-margin; position: relative; - min-height: 1.3em; + min-height: 2.6em; word-break: break-all; + transition-property: opacity, transform; + transition-duration: 0.4s; - span.icon { + .icon { position: absolute; - top: 8px; + top: 10px; left: 8px; + color: inherit; + } + + &.dismissed { + display: block; + opacity: 0; + transform: translate3d(0, -100%, 0); } &.notice { diff --git a/app/assets/stylesheets/alchemy/flatpickr.scss b/app/assets/stylesheets/alchemy/flatpickr.scss new file mode 100644 index 00000000000..2d36966cd4a --- /dev/null +++ b/app/assets/stylesheets/alchemy/flatpickr.scss @@ -0,0 +1,839 @@ +.flatpickr-calendar { + background: transparent; + opacity: 0; + display: none; + text-align: center; + visibility: hidden; + padding: 0; + -webkit-animation: none; + animation: none; + direction: ltr; + border: 0; + font-size: $default-font-size; + line-height: 24px; + border-radius: 5px; + position: absolute; + width: 307.875px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + -ms-touch-action: manipulation; + touch-action: manipulation; + background: $white; + -webkit-box-shadow: 1px 0 0 $medium-gray, -1px 0 0 $medium-gray, 0 1px 0 $medium-gray, 0 -1px 0 $medium-gray, 0 3px 13px rgba(0, 0, 0, 0.08); + box-shadow: 1px 0 0 $medium-gray, -1px 0 0 $medium-gray, 0 1px 0 $medium-gray, 0 -1px 0 $medium-gray, 0 3px 13px rgba(0, 0, 0, 0.08); +} + +.flatpickr-calendar.open, +.flatpickr-calendar.inline { + opacity: 1; + max-height: 640px; + visibility: visible +} + +.flatpickr-calendar.open { + display: inline-block; + z-index: 99999 +} + +.flatpickr-calendar.animate.open { + -webkit-animation: fpFadeInDown 300ms cubic-bezier(.23, 1, .32, 1); + animation: fpFadeInDown 300ms cubic-bezier(.23, 1, .32, 1) +} + +.flatpickr-calendar.inline { + display: block; + position: relative; + top: 2px +} + +.flatpickr-calendar.static { + position: absolute; + top: calc(100% + 2px); +} + +.flatpickr-calendar.static.open { + z-index: 999; + display: block +} + +.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) { + -webkit-box-shadow: none !important; + box-shadow: none !important +} + +.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) { + -webkit-box-shadow: -2px 0 0 $medium-gray, 5px 0 0 $medium-gray; + box-shadow: -2px 0 0 $medium-gray, 5px 0 0 $medium-gray +} + +.flatpickr-calendar .hasWeeks .dayContainer, +.flatpickr-calendar .hasTime .dayContainer { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.flatpickr-calendar .hasWeeks .dayContainer { + border-left: 0 +} + +.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time { + height: 40px; + border-top: 1px solid $medium-gray +} + +.flatpickr-calendar.noCalendar.hasTime .flatpickr-time { + height: auto +} + +.flatpickr-calendar:before, +.flatpickr-calendar:after { + position: absolute; + display: block; + pointer-events: none; + border: solid transparent; + content: ''; + height: 0; + width: 0; + left: 22px +} + +.flatpickr-calendar.rightMost:before, +.flatpickr-calendar.rightMost:after { + left: auto; + right: 22px +} + +.flatpickr-calendar:before { + border-width: 5px; + margin: 0 -5px +} + +.flatpickr-calendar:after { + border-width: 4px; + margin: 0 -4px +} + +.flatpickr-calendar.arrowTop:before, +.flatpickr-calendar.arrowTop:after { + bottom: 100% +} + +.flatpickr-calendar.arrowTop:before { + border-bottom-color: $medium-gray +} + +.flatpickr-calendar.arrowTop:after { + border-bottom-color: $white +} + +.flatpickr-calendar.arrowBottom:before, +.flatpickr-calendar.arrowBottom:after { + top: 100% +} + +.flatpickr-calendar.arrowBottom:before { + border-top-color: $medium-gray +} + +.flatpickr-calendar.arrowBottom:after { + border-top-color: $white +} + +.flatpickr-calendar:focus { + outline: 0 +} + +.flatpickr-wrapper { + position: relative; + display: inline-block +} + +.flatpickr-months { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.flatpickr-months .flatpickr-month { + background: transparent; + color: rgba(0, 0, 0, 0.9); + fill: rgba(0, 0, 0, 0.9); + height: $form-field-height; + line-height: 1; + text-align: center; + position: relative; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + overflow: hidden; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1 +} + +.flatpickr-months .flatpickr-prev-month, +.flatpickr-months .flatpickr-next-month { + text-decoration: none; + cursor: pointer; + position: absolute; + top: 0; + line-height: 16px; + height: 28px; + padding: 10px; + z-index: 3; + color: rgba(0, 0, 0, 0.9); + fill: rgba(0, 0, 0, 0.9); +} + +.flatpickr-months .flatpickr-prev-month.disabled, +.flatpickr-months .flatpickr-next-month.disabled { + display: none +} + +.flatpickr-months .flatpickr-prev-month i, +.flatpickr-months .flatpickr-next-month i { + position: relative +} + +.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month, +.flatpickr-months .flatpickr-next-month.flatpickr-prev-month { + /* + /*rtl:begin:ignore*/ + left: 0; + /* + /*rtl:end:ignore*/ +} + +/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month.flatpickr-next-month, +.flatpickr-months .flatpickr-next-month.flatpickr-next-month { + /* + /*rtl:begin:ignore*/ + right: 0; + /* + /*rtl:end:ignore*/ +} + +/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month:hover, +.flatpickr-months .flatpickr-next-month:hover { + color: $dark-gray; +} + +.flatpickr-months .flatpickr-prev-month:hover svg, +.flatpickr-months .flatpickr-next-month:hover svg { + fill: $dark-orange; +} + +.flatpickr-months .flatpickr-prev-month svg, +.flatpickr-months .flatpickr-next-month svg { + width: 14px; + height: 14px; +} + +.flatpickr-months .flatpickr-prev-month svg path, +.flatpickr-months .flatpickr-next-month svg path { + -webkit-transition: fill .1s; + transition: fill .1s; + fill: inherit +} + +.numInputWrapper { + position: relative; + height: auto; +} + +.numInputWrapper input, +.numInputWrapper span { + display: inline-block +} + +.numInputWrapper input { + width: 100%; +} + +.numInputWrapper input::-ms-clear { + display: none +} + +.numInputWrapper span { + position: absolute; + right: 0; + width: 14px; + padding: 0 4px 0 2px; + height: 50%; + line-height: 50%; + opacity: 0; + cursor: pointer; + border: 1px solid rgba(57, 57, 57, 0.15); + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.numInputWrapper span:hover { + background: rgba(0, 0, 0, 0.1) +} + +.numInputWrapper span:active { + background: rgba(0, 0, 0, 0.2) +} + +.numInputWrapper span:after { + display: block; + content: ""; + position: absolute +} + +.numInputWrapper span.arrowUp { + top: 0; + border-bottom: 0; +} + +.numInputWrapper span.arrowUp:after { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid rgba(57, 57, 57, 0.6); + top: 26% +} + +.numInputWrapper span.arrowDown { + top: 50%; +} + +.numInputWrapper span.arrowDown:after { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid rgba(57, 57, 57, 0.6); + top: 40% +} + +.numInputWrapper span svg { + width: inherit; + height: auto; +} + +.numInputWrapper span svg path { + fill: rgba(0, 0, 0, 0.5) +} + +.numInputWrapper:hover { + background: rgba(0, 0, 0, 0.05); +} + +.numInputWrapper:hover span { + opacity: 1 +} + +.flatpickr-current-month { + font-size: 1.25rem; + line-height: inherit; + font-weight: 300; + color: inherit; + position: absolute; + width: 75%; + left: 12.5%; + padding: 6.16px 0 0 0; + line-height: 1; + height: 28px; + display: inline-block; + text-align: center; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} + +.flatpickr-current-month span.cur-month { + font-family: inherit; + font-weight: 700; + color: inherit; + display: inline-block; + margin-left: .5ch; + padding: 0; +} + +.flatpickr-current-month span.cur-month:hover { + background: rgba(0, 0, 0, 0.05) +} + +.flatpickr-current-month .numInputWrapper { + width: 6ch; + width: 7ch\0; + display: inline-block; +} + +.flatpickr-current-month .numInputWrapper span.arrowUp:after { + border-bottom-color: rgba(0, 0, 0, 0.9) +} + +.flatpickr-current-month .numInputWrapper span.arrowDown:after { + border-top-color: rgba(0, 0, 0, 0.9) +} + +.flatpickr-current-month input.cur-year { + background: transparent; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: inherit; + cursor: text; + padding: 0 0 0 .5ch; + margin: 0; + display: inline-block; + font-size: $form-field-font-size; + font-family: inherit; + font-weight: 300; + line-height: inherit; + height: auto; + border: 0; + border-radius: 0; + vertical-align: initial; +} + +.flatpickr-current-month input.cur-year:focus { + outline: 0 +} + +.flatpickr-current-month input.cur-year[disabled], +.flatpickr-current-month input.cur-year[disabled]:hover { + color: rgba(0, 0, 0, 0.5); + background: transparent; + pointer-events: none +} + +.flatpickr-weekdays { + background: transparent; + text-align: center; + overflow: hidden; + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + height: 28px; +} + +.flatpickr-weekdays .flatpickr-weekdaycontainer { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1 +} + +span.flatpickr-weekday { + cursor: default; + background: transparent; + color: rgba(0, 0, 0, 0.54); + line-height: 1; + margin: 0; + text-align: center; + display: block; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + font-weight: bolder +} + +.dayContainer, +.flatpickr-weeks { + padding: 1px 0 0 0 +} + +.flatpickr-days { + position: relative; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -webkit-align-items: flex-start; + -ms-flex-align: start; + align-items: flex-start; + width: 307.875px; +} + +.flatpickr-days:focus { + outline: 0 +} + +.dayContainer { + padding: 0; + outline: 0; + text-align: left; + width: 307.875px; + min-width: 307.875px; + max-width: 307.875px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: inline-block; + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-wrap: wrap; + -ms-flex-pack: justify; + -webkit-justify-content: space-around; + justify-content: space-around; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + opacity: 1; +} + +.dayContainer+.dayContainer { + -webkit-box-shadow: -1px 0 0 $medium-gray; + box-shadow: -1px 0 0 $medium-gray +} + +.flatpickr-day { + background: none; + border: 1px solid transparent; + border-radius: $default-border-radius; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #393939; + cursor: pointer; + font-weight: 400; + width: 14.2857143%; + -webkit-flex-basis: 14.2857143%; + -ms-flex-preferred-size: 14.2857143%; + flex-basis: 14.2857143%; + max-width: 39px; + height: 39px; + line-height: 39px; + margin: 0; + display: inline-block; + position: relative; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; +} + +.flatpickr-day.inRange, +.flatpickr-day.prevMonthDay.inRange, +.flatpickr-day.nextMonthDay.inRange, +.flatpickr-day.today.inRange, +.flatpickr-day.prevMonthDay.today.inRange, +.flatpickr-day.nextMonthDay.today.inRange, +.flatpickr-day:hover, +.flatpickr-day.prevMonthDay:hover, +.flatpickr-day.nextMonthDay:hover, +.flatpickr-day:focus, +.flatpickr-day.prevMonthDay:focus, +.flatpickr-day.nextMonthDay:focus { + cursor: pointer; + outline: 0; + background: $medium-gray; + border-color: $medium-gray +} + +.flatpickr-day.today { + border-color: $dark-gray; +} + +.flatpickr-day.today:hover, +.flatpickr-day.today:focus { + border-color: $dark-gray; + background: $dark-gray; + color: $white +} + +.flatpickr-day.selected, +.flatpickr-day.startRange, +.flatpickr-day.endRange, +.flatpickr-day.selected.inRange, +.flatpickr-day.startRange.inRange, +.flatpickr-day.endRange.inRange, +.flatpickr-day.selected:focus, +.flatpickr-day.startRange:focus, +.flatpickr-day.endRange:focus, +.flatpickr-day.selected:hover, +.flatpickr-day.startRange:hover, +.flatpickr-day.endRange:hover, +.flatpickr-day.selected.prevMonthDay, +.flatpickr-day.startRange.prevMonthDay, +.flatpickr-day.endRange.prevMonthDay, +.flatpickr-day.selected.nextMonthDay, +.flatpickr-day.startRange.nextMonthDay, +.flatpickr-day.endRange.nextMonthDay { + background: $blue; + -webkit-box-shadow: none; + box-shadow: none; + color: $white; + border-color: $blue +} + +.flatpickr-day.selected.startRange, +.flatpickr-day.startRange.startRange, +.flatpickr-day.endRange.startRange { + border-radius: 50px 0 0 50px +} + +.flatpickr-day.selected.endRange, +.flatpickr-day.startRange.endRange, +.flatpickr-day.endRange.endRange { + border-radius: 0 50px 50px 0 +} + +.flatpickr-day.selected.startRange+.endRange:not(:nth-child(7n+1)), +.flatpickr-day.startRange.startRange+.endRange:not(:nth-child(7n+1)), +.flatpickr-day.endRange.startRange+.endRange:not(:nth-child(7n+1)) { + -webkit-box-shadow: -10px 0 0 $blue; + box-shadow: -10px 0 0 $blue +} + +.flatpickr-day.selected.startRange.endRange, +.flatpickr-day.startRange.startRange.endRange, +.flatpickr-day.endRange.startRange.endRange { + border-radius: 50px +} + +.flatpickr-day.inRange { + border-radius: 0; + -webkit-box-shadow: -5px 0 0 $medium-gray, 5px 0 0 $medium-gray; + box-shadow: -5px 0 0 $medium-gray, 5px 0 0 $medium-gray +} + +.flatpickr-day.disabled, +.flatpickr-day.disabled:hover, +.flatpickr-day.prevMonthDay, +.flatpickr-day.nextMonthDay, +.flatpickr-day.notAllowed, +.flatpickr-day.notAllowed.prevMonthDay, +.flatpickr-day.notAllowed.nextMonthDay { + color: rgba(57, 57, 57, 0.3); + background: transparent; + border-color: transparent; + cursor: default +} + +.flatpickr-day.disabled, +.flatpickr-day.disabled:hover { + cursor: not-allowed; + color: rgba(57, 57, 57, 0.1) +} + +.flatpickr-day.week.selected { + border-radius: 0; + -webkit-box-shadow: -5px 0 0 $blue, 5px 0 0 $blue; + box-shadow: -5px 0 0 $blue, 5px 0 0 $blue +} + +.flatpickr-day.hidden { + visibility: hidden +} + +.rangeMode .flatpickr-day { + margin-top: 1px +} + +.flatpickr-weekwrapper { + display: inline-block; + float: left; +} + +.flatpickr-weekwrapper .flatpickr-weeks { + padding: 0 12px; + -webkit-box-shadow: 1px 0 0 $medium-gray; + box-shadow: 1px 0 0 $medium-gray +} + +.flatpickr-weekwrapper .flatpickr-weekday { + float: none; + width: 100%; + line-height: 28px +} + +.flatpickr-weekwrapper span.flatpickr-day, +.flatpickr-weekwrapper span.flatpickr-day:hover { + display: block; + width: 100%; + max-width: none; + color: rgba(57, 57, 57, 0.3); + background: transparent; + cursor: default; + border: none +} + +.flatpickr-innerContainer { + display: block; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; +} + +.flatpickr-rContainer { + display: inline-block; + padding: 0; + -webkit-box-sizing: border-box; + box-sizing: border-box +} + +.flatpickr-time { + text-align: center; + outline: 0; + display: block; + height: 0; + line-height: 40px; + max-height: 40px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.flatpickr-time:after { + content: ""; + display: table; + clear: both +} + +.flatpickr-time .numInputWrapper { + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: 40%; + height: 40px; + float: left; +} + +.flatpickr-time .numInputWrapper span.arrowUp:after { + border-bottom-color: #393939 +} + +.flatpickr-time .numInputWrapper span.arrowDown:after { + border-top-color: #393939 +} + +.flatpickr-time.hasSeconds .numInputWrapper { + width: 26% +} + +.flatpickr-time.time24hr .numInputWrapper { + width: 49% +} + +.flatpickr-time input { + background: transparent; + -webkit-box-shadow: none; + box-shadow: none; + border: 0; + border-radius: 0; + text-align: center; + margin: 0; + padding: 0; + height: inherit; + line-height: inherit; + color: #393939; + font-size: $form-field-font-size; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.flatpickr-time input.flatpickr-hour { + font-weight: bold +} + +.flatpickr-time input.flatpickr-minute, +.flatpickr-time input.flatpickr-second { + font-weight: 400 +} + +.flatpickr-time input:focus { + outline: 0; + border: 0 +} + +.flatpickr-time .flatpickr-time-separator, +.flatpickr-time .flatpickr-am-pm { + height: inherit; + display: inline-block; + float: left; + line-height: inherit; + color: #393939; + font-weight: bold; + width: 2%; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-align-self: center; + -ms-flex-item-align: center; + align-self: center +} + +.flatpickr-time .flatpickr-am-pm { + outline: 0; + width: 18%; + cursor: pointer; + text-align: center; + font-weight: 400 +} + +.flatpickr-time input:hover, +.flatpickr-time .flatpickr-am-pm:hover, +.flatpickr-time input:focus, +.flatpickr-time .flatpickr-am-pm:focus { + background: $light-gray +} + +.flatpickr-input[readonly] { + cursor: pointer +} + +@-webkit-keyframes fpFadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0) + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +@keyframes fpFadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0) + } + + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} diff --git a/app/assets/stylesheets/alchemy/fonts.scss b/app/assets/stylesheets/alchemy/fonts.scss new file mode 100755 index 00000000000..540b9ae376f --- /dev/null +++ b/app/assets/stylesheets/alchemy/fonts.scss @@ -0,0 +1,13 @@ +@font-face { + font-family: 'Open Sans'; + src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Open Sans'; + src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); + font-weight: 700; + font-style: normal; +} diff --git a/app/assets/stylesheets/alchemy/form_fields.scss b/app/assets/stylesheets/alchemy/form_fields.scss index 55af0ec98be..becce5b4cff 100644 --- a/app/assets/stylesheets/alchemy/form_fields.scss +++ b/app/assets/stylesheets/alchemy/form_fields.scss @@ -1,5 +1,6 @@ textarea, input[type="url"], +input[type="number"], input[type="text"], input[type="email"], input[type="password"], @@ -13,6 +14,17 @@ input[type="search"], } } +input[type="checkbox"] { + transition: $transition-duration; + + &:focus { + @include default-focus-style($box-shadow: ( + inset 0 0 0 1px $focus-color, + 0 0 0 1px $focus-color + )); + } +} + input[type="search"] { -webkit-appearance: textfield; } @@ -26,8 +38,6 @@ input[type="search"]::-webkit-search-results-decoration { textarea { height: auto; - padding: 4px 8px; - line-height: 19px; } .alchemy-char-counter { diff --git a/app/assets/stylesheets/alchemy/forms.scss b/app/assets/stylesheets/alchemy/forms.scss index 95d7ab87b28..9dbd648dc41 100644 --- a/app/assets/stylesheets/alchemy/forms.scss +++ b/app/assets/stylesheets/alchemy/forms.scss @@ -16,10 +16,6 @@ form { height: 67px; } - > h2 { - @include form-heading; - } - .control-label { @include form-label; } @@ -29,6 +25,7 @@ form { @include clearfix; > input[type="url"], + > input[type="number"], > input[type="text"], > input[type="email"], > input[type="password"], @@ -41,6 +38,10 @@ form { float: right; } + .input > .select2-container { + width: 100%; + } + > .autocomplete_tag_list { .select2-container, .select2-choices { @@ -52,11 +53,8 @@ form { line-height: 16px; } - &.select, &.grouped_select { - - .select2-container { - margin: 4px 0; - } + .select2-container { + margin: 4px 0; } &.boolean { @@ -136,11 +134,9 @@ form { .inline-input { @include clearfix; margin: 0 -1*$default-margin; - box-sizing: border-box; .left-column, .right-column { padding: 0 $default-padding; - box-sizing: border-box; } .left-column { @@ -166,12 +162,22 @@ form { .control_group { width: $form-right-width; - padding-top: 6px; + padding-top: $default-margin; float: right; } - .foot_note, .input .hint { + .check_boxes .control_group { + padding-top: 0.6em; + } + + .input .hint { + @include form-hint; margin-left: $form-left-width; + + a[href] { + color: $very-dark-gray; + text-decoration: underline; + } } .submit { @@ -180,7 +186,6 @@ form { } .input-column { - box-sizing: border-box; float: left; padding: 0 $default-padding; width: 50%; @@ -208,6 +213,10 @@ textarea#essence_picture_caption { // styles for link overlay selects .window_form { + td.checkbox { + text-align: left; + } + .select2-container { width: 100%; } diff --git a/app/assets/stylesheets/alchemy/frame.scss b/app/assets/stylesheets/alchemy/frame.scss index 197b1d8da71..c10a3ae1e36 100644 --- a/app/assets/stylesheets/alchemy/frame.scss +++ b/app/assets/stylesheets/alchemy/frame.scss @@ -12,7 +12,7 @@ div#overlay { div#overlay_text_box { color: #333333; - font-size: 18px; + font-size: 1.5em; left: 50%; line-height: 120px; margin-left: -100px; @@ -29,57 +29,28 @@ div#overlay_text_box { } #logout { - box-sizing: border-box; position: absolute; z-index: 1; bottom: 0; left: 0; - width: $main-menu-width; - background-color: $main-menu-bg-color; - border-right: $default-border; - transition: background-color $transition-duration $transition-easing; - - &:hover { - background-color: $main-menu-active-bg-color; - } - - .collapsed-menu & { - width: $collapsed-main-menu-width; - } - - a { - display: block; - padding-left: 3 * $default-padding; - line-height: 45px; - - &:hover { - text-decoration: none; - } - - label { - padding-left: 2 * $default-padding; - } - } + width: 100%; + margin-bottom: 0; - img { - margin-bottom: 4px; + &:not(:hover) { + background-color: $main-menu-bg-color; } } #header { + display: flex; height: $header-height; line-height: $header-height; - background: $light-blue; + background: $header-background; border-bottom: $default-border; position: relative; - a { - display: inline-block; - vertical-align: middle; - - &:hover { - text-decoration: none; - } + a:hover { + text-decoration: none; } .page_status_and_name { @@ -90,119 +61,68 @@ div#overlay_text_box { @extend .locked_page; cursor: default; border-bottom-color: $toolbar-bg-color; + + .hint-bubble { + left: -40px; + transform: none; + + &:before { + left: 47px; + } + } } .page_name { + display: inline-block; + max-width: 100px; margin-right: $default-margin; line-height: $header-height; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } #main_content { - box-sizing: border-box; - background-color: $light-gray; - padding: 84px 8px 8px $main-menu-width + 10px; + padding-top: 84px; + padding-right: 8px; + padding-bottom: 8px; + padding-left: $collapsed-main-menu-width + 8px; z-index: 0; width: 100%; height: 100%; -} - -#archive_all { - box-sizing: border-box; - height: 100%; - padding-bottom: 60px; - - &.with_tag_filter { - - table { - padding-right: 230px; - } - > .info { - margin-right: 230px; - } - } - - &.resources-table-wrapper { - padding-top: 47px; - - > table { - padding-bottom: 60px; - } + @media screen and (min-width: $large-screen-break-point) { + padding-left: $main-menu-width + 8px; } } #user_info { - position: absolute; - top: 0; - right: 0; height: $header-height; - font-size: 11px; + font-size: $small-font-size; padding-left: 2*$default-padding; - background-color: $light-blue; + margin-left: auto; border-bottom: $default-border; + background-color: $header-background; - .select2-container { + .select2-container, select { + height: $form-field-height; margin-top: -3px; - padding-right: 2*$default-padding; } - .select2-choice { - padding-right: 0; + .select2-choice, select { background: transparent; - box-shadow: none; border: none; border-radius: 0; border-left: $default-border; - height: $header-height + 1; - line-height: $header-height; - text-shadow: $text-shadow-light; - - .select2-arrow { - width: 14px; - border-left: none; - - b { - color: $muted-text-color; - - &:before { - position: absolute; - top: 50%; - transform: translateY(-50%); - } - } - } + height: $header-height; } - .select2-dropdown-open { - background-color: $main-menu-bg-color; - - .select2-choice .select2-arrow b { - background-position: -20px -5px; - } + .select2-choice { + line-height: 2; } .current-user-name { padding-right: 2*$default-padding; } } - -#locked_pages { - - label { - float: left; - font-size: 10px; - line-height: 27px; - height: 27px; - margin-left: 8px; - margin-right: 4px; - } - - img { - float: left; - } - - .subnavi_tab > a { - padding-right: 0; - } -} diff --git a/app/assets/stylesheets/alchemy/hints.scss b/app/assets/stylesheets/alchemy/hints.scss index f99dbc8943a..b1707583025 100644 --- a/app/assets/stylesheets/alchemy/hints.scss +++ b/app/assets/stylesheets/alchemy/hints.scss @@ -28,7 +28,7 @@ > .hint-bubble { visibility: hidden; background: $light_yellow; - @extend %rounded-border; + border-radius: $default-border-radius; box-shadow: 0px 4px 8px rgba(0, 0, 0, .3); position: absolute; left: 50%; @@ -41,7 +41,7 @@ max-width: 300px; white-space: normal; text-align: left; - font-size: 12px; + font-size: $default-font-size; opacity: 0; transition: .3s; pointer-events: none; diff --git a/app/assets/stylesheets/alchemy/icon-font.scss b/app/assets/stylesheets/alchemy/icon-font.scss deleted file mode 100644 index 95b463d9b77..00000000000 --- a/app/assets/stylesheets/alchemy/icon-font.scss +++ /dev/null @@ -1,75 +0,0 @@ -@font-face { - font-family: 'Alchemy Icons'; - src: font-url('alchemy/icons.eot?sgh5oh'); - src: font-url('alchemy/icons.eot?#iefixsgh5oh') format('embedded-opentype'), - font-url('alchemy/icons.ttf?sgh5oh') format('truetype'), - font-url('alchemy/icons.woff?sgh5oh') format('woff'), - font-url('alchemy/icons.svg?sgh5oh#Alchemy-Icons') format('svg'); - font-weight: normal; - font-style: normal; -} - -.icon-angle-double-right, -.icon-angle-left, -.icon-angle-right, -.icon-plus, -.icon-minus, -.icon-cross, -.icon-plus-2, -.icon-minus-2, -.icon-pencil, -.icon-tag, -.icon-info, -.icon-warning, -.icon-checkmark, -.icon-ccw, -.icon-trash, -.icon-cross-2, -.icon-pictures, -.icon-menu, -.icon-magnifying-glass, -.icon-fullscreen-exit, -.icon-help, -.icon-fullscreen, -.icon-cog { - font-family: 'Alchemy Icons'; - speak: none; - font-size: 12px; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - text-decoration: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - width: 16px; - color: #666; - text-align: center; - line-height: 1; - display: inline-block; - vertical-align: middle; -} - -.icon-angle-double-right:before { content: "\f101" } -.icon-angle-left:before { content: "\f104" } -.icon-angle-right:before { content: "\f105" } -.icon-plus:before { content: "\e000" } -.icon-minus:before { content: "\e001" } -.icon-cross:before { content: "\e004" } -.icon-plus-2:before { content: "\e005" } -.icon-minus-2:before { content: "\e006" } -.icon-pencil:before { content: "\e007" } -.icon-tag:before { content: "\e034" } -.icon-info:before { content: "\e008" } -.icon-warning:before { content: "\e009" } -.icon-checkmark:before { content: "\e00a" } -.icon-ccw:before { content: "\e00b" } -.icon-trash:before { content: "\f014" } -.icon-cross-2:before { content: "\e00e" } -.icon-pictures:before { content: "\e00f" } -.icon-menu:before { content: "\e010" } -.icon-magnifying-glass:before { content: "\e011" } -.icon-fullscreen-exit:before { content: "\e01e" } -.icon-help:before { content: "\e00c" } -.icon-fullscreen:before { content: "\e01d" } -.icon-cog:before { content: "\f013" } diff --git a/app/assets/stylesheets/alchemy/icons.scss b/app/assets/stylesheets/alchemy/icons.scss index c2bae850730..87118f92912 100644 --- a/app/assets/stylesheets/alchemy/icons.scss +++ b/app/assets/stylesheets/alchemy/icons.scss @@ -1,345 +1,11 @@ .icon { - display: inline-block; - vertical-align: middle; - background: image-url('alchemy/icons.png') no-repeat; - width: 16px; - height: 16px; + color: $icon-color; - &.add { - background: none; - @extend .icon-plus-2; - line-height: 1em; - font-size: 19px; - } - - &.add_page { - background-position: -96px -40px; - } - - &.archive { - background-position: -416px -136px; - } - - &.audio { - background-position: -192px -136px; - } - - &.back { - background: none; - line-height: 14px; - font-size: 20px; - font-weight: bold; - color: #666; - - &:before { - content: '\00ab'; - } + &.disabled { + opacity: 0.3; } &.blank { background: transparent; } - - &.clipboard { - background-position: -288px -104px; - - &.full { - background-position: -320px -104px; - } - } - - &.close { - background-position: -416px -39px; - - &.small { - background: none; - @extend .icon-cross; - line-height: 1; - } - } - - &.collapse.small { - background-position: -512px -106px; - } - - &.configure_page { - background-position: -320px -40px; - } - - &.copy_page { - background-position: -544px -40px; - } - - &.create { - background-position: -191px -168px; - } - - &.crop { - background-position: -256px -72px; - } - - &.delete_page { - background-position: -64px -40px; - } - - &.delete-small { - background-position: -1px -72px; - } - - &.destroy { - background-position: -224px -169px; - } - - &.edit { - background-position: -160px -168px; - } - - &.element, &.clipboard_elements { - background-position: -224px -72px; - } - - &.element_draft { - background-position: 1px -70px; - } - - &.element-hide { - background-position: -416px -168px; - } - - &.element_public { - background-position: -94px -166px; - } - - &.element_window { - background-position: -512px -72px; - } - - &.element_copy { - background-position: -128px -72px; - } - - &.cut { - background-position: -97px -72px; - } - - &.error { - background-position: -32px -168px; - } - - &.excel { - background-position: -353px -136px; - } - - &.expand.small { - background-position: -498px -106px; - } - - &.false { - background-image: none; - } - - &.file { - background-position: 0 -40px; - } - - &.file_delete { - background-position: -64px -40px; - } - - &.file_download, &.download { - background-position: -480px -136px; - } - - &.file_edit { - background-position: -32px -40px; - } - - &.file_change { - background-position: -512px -40px; - } - - &.flash { - background-position: -256px -136px; - } - - &.flush { - background-position: -352px -39px; - } - - &.questionmark, &.hint { - background-position: -447px -105px; - } - - &.image { - background-position: -224px -136px; - } - - &.info { - background-position: -64px -168px; - } - - &.language { - background-position: 0 -120px; - } - - &.language_add { - background-position: -47px -120px; - } - - &.language_delete { - background-position: -16px -120px; - } - - &.language_edit { - background-position: -32px -120px; - } - - &.link { - background-position: -320px -72px; - } - - &.new_element { - background-position: -384px -40px; - } - - &.offline { - background-position: -255px -105px; - } - - &.online { - background-position: -224px -105px; - } - - &.page, &.clipboard_pages { - background-position: 0 -40px; - } - - &.pdf { - background-position: -161px -136px; - } - - &.preview_window { - background-position: -448px -40px; - } - - &.psd { - background-position: -128px -136px; - } - - &.publish { - background-position: -64px -136px; - } - - &.remove { - background-position: -63px -72px; - } - - &.rtf { - background-position: -320px -136px; - } - - &.search { - background: none; - padding-top: 2px; - @extend .icon-magnifying-glass; - } - - &.select_all { - background-position: -256px -168px; - } - - &.settings { - background-position: -320px -40px; - } - - &.sort { - background-position: -128px -168px; - } - - &.swap_picture { - background-position: -288px -72px; - } - - &.sync, &.reload { - background-position: -96px -136px; - } - - &.tag_add { - background-position: -384px -168px; - } - - &.tag { - background-position: -288px -168px; - } - - &.tag_label { - background: none; - @extend .icon-tag; - } - - &.tag_edit { - background-position: -352px -168px; - } - - &.tag_delete { - background-position: -320px -168px; - } - - &.text { - background-position: -448px -136px; - } - - &.tick, &.true, &.notice, &.ok { - background-position: -32px -73px; - } - - &.trash { - background-position: -352px -104px; - - &.full { - background-position: -384px -104px; - } - } - - &.unlink { - background-position: -352px -72px; - } - - &.upload { - background-position: 0 -136px; - } - - &.vcard { - background-position: -416px -104px; - } - - &.video { - background-position: -288px -136px; - } - - &.visit_page { - background-position: -31px -135px; - } - - &.warning, &.warn, &.alert { - background-position: 0 -168px; - - &.inline { vertical-align: text-bottom; } - } - - &.word { - background-position: -385px -136px; - } - - &.zoom-equal { - background-position: -448px -72px; - } - - &.zoom-in { - background-position: -480px -72px; - } - - &.zoom-out { - background-position: -416px -72px; - } } diff --git a/app/assets/stylesheets/alchemy/image_library.scss b/app/assets/stylesheets/alchemy/image_library.scss index 17627a965b9..23b79343a61 100644 --- a/app/assets/stylesheets/alchemy/image_library.scss +++ b/app/assets/stylesheets/alchemy/image_library.scss @@ -1,6 +1,6 @@ -$picture-overlay-handle-width: 18px; +$picture-overlay-handle-width: 24px; $image-overlay-form-width: 350px - $picture-overlay-handle-width; -$image-overlay-transition-duration: 200ms; +$image-overlay-transition-duration: $transition-duration; $image-overlay-transition-easing: ease-in; .alchemy-image-overlay { @@ -41,8 +41,8 @@ $image-overlay-transition-easing: ease-in; .picture-overlay-handle { right: 0; - .icon-angle-double-right { - @include transform(rotate(180deg) translate(50%, 50%)); + .icon { + transform: rotate(180deg) translate(50%, 50%); } } } @@ -64,13 +64,13 @@ $image-overlay-transition-easing: ease-in; top: 2*$default-padding; right: 2*$default-padding + $picture-overlay-handle-width + $image-overlay-form-width; cursor: pointer; - @include transition(right $image-overlay-transition-duration $image-overlay-transition-easing); + transition: right $image-overlay-transition-duration $image-overlay-transition-easing; - .icon.close.small { - font-size: 32px; + .icon { + font-size: 2em; color: $medium-gray; text-shadow: 0 0 $default-padding $text-color; - @include transition(color $image-overlay-transition-duration linear); + transition: color $image-overlay-transition-duration linear; &:hover { color: $white; @@ -84,11 +84,10 @@ $image-overlay-transition-easing: ease-in; top: 0; background-color: $medium-gray; box-shadow: -2px 0 $default-padding -2px $text-color; - @include transition(right $image-overlay-transition-duration $image-overlay-transition-easing); + transition: right $image-overlay-transition-duration $image-overlay-transition-easing; } .picture-details-overlay { - box-sizing: border-box; right: 0; width: $image-overlay-form-width; height: 100%; @@ -97,12 +96,7 @@ $image-overlay-transition-easing: ease-in; form .control-label, .resource_info .value label { - width: 100%; text-align: left; - float: none; - display: block; - padding: 0; - margin: $default-margin 0 0; } form .input .hint { @@ -122,14 +116,14 @@ $image-overlay-transition-easing: ease-in; right: $image-overlay-form-width; cursor: pointer; - .icon-angle-double-right { + .icon { position: absolute; top: 50%; left: 50%; - @include transform(translate(-50%, -50%)); - font-size: 14px; + transform: translate(-50%, -50%); + font-size: 1.2em; color: $text-color; - @include transition(transform $image-overlay-transition-duration $image-overlay-transition-easing); + transition: transform $image-overlay-transition-duration $image-overlay-transition-easing; } &:hover { @@ -138,7 +132,6 @@ $image-overlay-transition-easing: ease-in; } .zoomed-picture-background { - box-sizing: border-box; width: 100%; height: 100%; padding-top: 2*$default-padding; @@ -148,7 +141,7 @@ $image-overlay-transition-easing: ease-in; margin: 0 auto; text-align: center; cursor: pointer; - @include transition(padding-right $image-overlay-transition-duration $image-overlay-transition-easing); + transition: padding-right $image-overlay-transition-duration $image-overlay-transition-easing; &:before { content: ''; @@ -175,34 +168,44 @@ $image-overlay-transition-easing: ease-in; padding: 2*$default-padding 0; .message { - box-sizing: border-box; margin: 2*$default-padding 0; } } #pictures_page_list { - h3 { - padding: $default-padding 0 0; + display: flex; + padding: 4px 8px 0; margin: 0; } - ol { + ul { padding-left: 0; - list-style-position: inside; + list-style-type: none; } .list { margin: 2*$default-margin 0; li { + display: flex; padding: 2*$default-padding; border-radius: $default-border-radius; white-space: normal; - - &.even { background: #F7F7F7 } + margin-bottom: 1em; } } + + .icon { + padding-top: 3px; + text-align: left; + width: 7%; + } + + p { + width: 93%; + margin: 0; + } } .picture-overlay-navigation { @@ -222,25 +225,24 @@ $image-overlay-transition-easing: ease-in; cursor: pointer; text-align: center; text-decoration: none; - @include transition(background-color $image-overlay-transition-duration linear); + transition: background-color $image-overlay-transition-duration linear; &:hover { background-color: rgba(0,0,0, 0.3); - .icon-angle-left, - .icon-angle-right { + .icon { color: $white; text-shadow: none; } } } - .icon-angle-left, - .icon-angle-right { - font-size: 48px; + .icon { + font-size: 4em; color: $medium-gray; text-shadow: 0 0 $default-padding $text-color; - @include transition(all $image-overlay-transition-duration linear); + transition: all $image-overlay-transition-duration linear; + vertical-align: middle; } .previous-picture { @@ -251,6 +253,6 @@ $image-overlay-transition-easing: ease-in; .next-picture { right: $image-overlay-form-width + $picture-overlay-handle-width; @include border-left-radius($default-border-radius); - @include transition(right $image-overlay-transition-duration $image-overlay-transition-easing); + transition: right $image-overlay-transition-duration $image-overlay-transition-easing; } } diff --git a/app/assets/stylesheets/alchemy/jquery-ui.scss b/app/assets/stylesheets/alchemy/jquery-ui.scss index d20d3035586..ae3dd4b79a8 100644 --- a/app/assets/stylesheets/alchemy/jquery-ui.scss +++ b/app/assets/stylesheets/alchemy/jquery-ui.scss @@ -74,18 +74,6 @@ cursor: default !important; } -/* Icons -----------------------------------*/ - -/* states and images */ - -.ui-icon { - display: block; - text-indent: -99999px; - overflow: hidden; - background-repeat: no-repeat; -} - /* Misc visuals ----------------------------------*/ @@ -116,7 +104,7 @@ ----------------------------------*/ .ui-widget { - font-family: $default-font-face; + font-family: $default-font-family; font-size: $default-font-size; } @@ -130,12 +118,6 @@ color: $text-color; } -.ui-widget-content a { - color: $text-color; - - &:hover { color: #000 } -} - .ui-widget-header { border: 1px none #afafaf; background: $light-blue; @@ -149,10 +131,6 @@ border-bottom-right-radius: 0; } -.ui-widget-header a { - color: $text-color; -} - /* Interaction states ----------------------------------*/ @@ -161,7 +139,6 @@ background-color: #ededed; font-weight: normal; color: $text-color; - text-shadow: $button-text-shadow; } .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { @@ -253,790 +230,6 @@ background-image: none; } -/* Icons -----------------------------------*/ - -/* states and images */ - -.ui-icon { - width: 16px; - height: 16px; - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-widget-content .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-widget-header .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-state-default .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-state-active .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-state-highlight .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon { - background-image: image-url('alchemy/ui-icons_666666_256x240.png'); -} - -/* positioning */ - -.ui-icon-carat-1-n { - background-position: 0 0; -} - -.ui-icon-carat-1-ne { - background-position: -16px 0; -} - -.ui-icon-carat-1-e { - background-position: -32px 0; -} - -.ui-icon-carat-1-se { - background-position: -48px 0; -} - -.ui-icon-carat-1-s { - background-position: -64px 0; -} - -.ui-icon-carat-1-sw { - background-position: -80px 0; -} - -.ui-icon-carat-1-w { - background-position: -96px 0; -} - -.ui-icon-carat-1-nw { - background-position: -112px 0; -} - -.ui-icon-carat-2-n-s { - background-position: -128px 0; -} - -.ui-icon-carat-2-e-w { - background-position: -144px 0; -} - -.ui-icon-triangle-1-n { - background-position: 0 -16px; -} - -.ui-icon-triangle-1-ne { - background-position: -16px -16px; -} - -.ui-icon-triangle-1-e { - background-position: -32px -16px; -} - -.ui-icon-triangle-1-se { - background-position: -48px -16px; -} - -.ui-icon-triangle-1-s { - background-position: -64px -16px; -} - -.ui-icon-triangle-1-sw { - background-position: -80px -16px; -} - -.ui-icon-triangle-1-w { - background-position: -96px -16px; -} - -.ui-icon-triangle-1-nw { - background-position: -112px -16px; -} - -.ui-icon-triangle-2-n-s { - background-position: -128px -16px; -} - -.ui-icon-triangle-2-e-w { - background-position: -144px -16px; -} - -.ui-icon-arrow-1-n { - background-position: 0 -32px; -} - -.ui-icon-arrow-1-ne { - background-position: -16px -32px; -} - -.ui-icon-arrow-1-e { - background-position: -32px -32px; -} - -.ui-icon-arrow-1-se { - background-position: -48px -32px; -} - -.ui-icon-arrow-1-s { - background-position: -64px -32px; -} - -.ui-icon-arrow-1-sw { - background-position: -80px -32px; -} - -.ui-icon-arrow-1-w { - background-position: -96px -32px; -} - -.ui-icon-arrow-1-nw { - background-position: -112px -32px; -} - -.ui-icon-arrow-2-n-s { - background-position: -128px -32px; -} - -.ui-icon-arrow-2-ne-sw { - background-position: -144px -32px; -} - -.ui-icon-arrow-2-e-w { - background-position: -160px -32px; -} - -.ui-icon-arrow-2-se-nw { - background-position: -176px -32px; -} - -.ui-icon-arrowstop-1-n { - background-position: -192px -32px; -} - -.ui-icon-arrowstop-1-e { - background-position: -208px -32px; -} - -.ui-icon-arrowstop-1-s { - background-position: -224px -32px; -} - -.ui-icon-arrowstop-1-w { - background-position: -240px -32px; -} - -.ui-icon-arrowthick-1-n { - background-position: 0 -48px; -} - -.ui-icon-arrowthick-1-ne { - background-position: -16px -48px; -} - -.ui-icon-arrowthick-1-e { - background-position: -32px -48px; -} - -.ui-icon-arrowthick-1-se { - background-position: -48px -48px; -} - -.ui-icon-arrowthick-1-s { - background-position: -64px -48px; -} - -.ui-icon-arrowthick-1-sw { - background-position: -80px -48px; -} - -.ui-icon-arrowthick-1-w { - background-position: -96px -48px; -} - -.ui-icon-arrowthick-1-nw { - background-position: -112px -48px; -} - -.ui-icon-arrowthick-2-n-s { - background-position: -128px -48px; -} - -.ui-icon-arrowthick-2-ne-sw { - background-position: -144px -48px; -} - -.ui-icon-arrowthick-2-e-w { - background-position: -160px -48px; -} - -.ui-icon-arrowthick-2-se-nw { - background-position: -176px -48px; -} - -.ui-icon-arrowthickstop-1-n { - background-position: -192px -48px; -} - -.ui-icon-arrowthickstop-1-e { - background-position: -208px -48px; -} - -.ui-icon-arrowthickstop-1-s { - background-position: -224px -48px; -} - -.ui-icon-arrowthickstop-1-w { - background-position: -240px -48px; -} - -.ui-icon-arrowreturnthick-1-w { - background-position: 0 -64px; -} - -.ui-icon-arrowreturnthick-1-n { - background-position: -16px -64px; -} - -.ui-icon-arrowreturnthick-1-e { - background-position: -32px -64px; -} - -.ui-icon-arrowreturnthick-1-s { - background-position: -48px -64px; -} - -.ui-icon-arrowreturn-1-w { - background-position: -64px -64px; -} - -.ui-icon-arrowreturn-1-n { - background-position: -80px -64px; -} - -.ui-icon-arrowreturn-1-e { - background-position: -96px -64px; -} - -.ui-icon-arrowreturn-1-s { - background-position: -112px -64px; -} - -.ui-icon-arrowrefresh-1-w { - background-position: -128px -64px; -} - -.ui-icon-arrowrefresh-1-n { - background-position: -144px -64px; -} - -.ui-icon-arrowrefresh-1-e { - background-position: -160px -64px; -} - -.ui-icon-arrowrefresh-1-s { - background-position: -176px -64px; -} - -.ui-icon-arrow-4 { - background-position: 0 -80px; -} - -.ui-icon-arrow-4-diag { - background-position: -16px -80px; -} - -.ui-icon-extlink { - background-position: -32px -80px; -} - -.ui-icon-newwin { - background-position: -48px -80px; -} - -.ui-icon-refresh { - background: none !important; - @extend .icon-ccw; - - &:before { - text-indent: 0; - position: absolute; - right: 4px; - top: 3px; - } -} - -.ui-icon-fullscreen { - background: none !important; - position: relative; - @extend .icon-fullscreen; - - &:before { - text-indent: 0; - position: absolute; - right: 2px; - top: 2px; - font-size: 10px; - } -} - -.ui-icon-exit-fullscreen { - background: none !important; - position: relative; - @extend .icon-fullscreen-exit; - - &:before { - text-indent: 0; - position: absolute; - right: 1px; - top: 2px; - } -} - -.ui-icon-shuffle { - background-position: -80px -80px; -} - -.ui-icon-transfer-e-w { - background-position: -96px -80px; -} - -.ui-icon-transferthick-e-w { - background-position: -112px -80px; -} - -.ui-icon-folder-collapsed { - background-position: 0 -96px; -} - -.ui-icon-folder-open { - background-position: -16px -96px; -} - -.ui-icon-document { - background-position: -32px -96px; -} - -.ui-icon-document-b { - background-position: -48px -96px; -} - -.ui-icon-note { - background-position: -64px -96px; -} - -.ui-icon-mail-closed { - background-position: -80px -96px; -} - -.ui-icon-mail-open { - background-position: -96px -96px; -} - -.ui-icon-suitcase { - background-position: -112px -96px; -} - -.ui-icon-comment { - background-position: -128px -96px; -} - -.ui-icon-person { - background-position: -144px -96px; -} - -.ui-icon-print { - background-position: -160px -96px; -} - -.ui-icon-trash { - background-position: -176px -96px; -} - -.ui-icon-locked { - background-position: -192px -96px; -} - -.ui-icon-unlocked { - background-position: -208px -96px; -} - -.ui-icon-bookmark { - background-position: -224px -96px; -} - -.ui-icon-tag { - background-position: -240px -96px; -} - -.ui-icon-home { - background-position: 0 -112px; -} - -.ui-icon-flag { - background-position: -16px -112px; -} - -.ui-icon-calendar { - background-position: -32px -112px; -} - -.ui-icon-cart { - background-position: -48px -112px; -} - -.ui-icon-pencil { - background-position: -64px -112px; -} - -.ui-icon-clock { - background-position: -80px -112px; -} - -.ui-icon-disk { - background-position: -96px -112px; -} - -.ui-icon-calculator { - background-position: -112px -112px; -} - -.ui-icon-zoomin { - background-position: -128px -112px; -} - -.ui-icon-zoomout { - background-position: -144px -112px; -} - -.ui-icon-search { - background-position: -160px -112px; -} - -.ui-icon-wrench { - background-position: -176px -112px; -} - -.ui-icon-gear { - background-position: -192px -112px; -} - -.ui-icon-heart { - background-position: -208px -112px; -} - -.ui-icon-star { - background-position: -224px -112px; -} - -.ui-icon-link { - background-position: -240px -112px; -} - -.ui-icon-cancel { - background-position: 0 -128px; -} - -.ui-icon-plus { - background-position: -16px -128px; -} - -.ui-icon-plusthick { - background-position: -32px -128px; -} - -.ui-icon-minus { - background-position: -48px -128px; -} - -.ui-icon-minusthick { - background-position: -64px -128px; -} - -.ui-icon-close { - background-position: -80px -128px; -} - -.ui-icon-closethick { - background: none !important; - position: relative; - @extend .icon-cross; - - &:before { - text-indent: 0; - position: absolute; - left: 1px; - top: 3px; - font-size: 16px; - } -} - -.ui-icon-key { - background-position: -112px -128px; -} - -.ui-icon-lightbulb { - background-position: -128px -128px; -} - -.ui-icon-scissors { - background-position: -144px -128px; -} - -.ui-icon-clipboard { - background-position: -160px -128px; -} - -.ui-icon-copy { - background-position: -176px -128px; -} - -.ui-icon-contact { - background-position: -192px -128px; -} - -.ui-icon-image { - background-position: -208px -128px; -} - -.ui-icon-video { - background-position: -224px -128px; -} - -.ui-icon-script { - background-position: -240px -128px; -} - -.ui-icon-alert { - background-position: 0 -144px; -} - -.ui-icon-info { - background-position: -16px -144px; -} - -.ui-icon-notice { - background-position: -32px -144px; -} - -.ui-icon-help { - background-position: -48px -144px; -} - -.ui-icon-check { - background-position: -64px -144px; -} - -.ui-icon-bullet { - background-position: -80px -144px; -} - -.ui-icon-radio-off { - background-position: -96px -144px; -} - -.ui-icon-radio-on { - background-position: -112px -144px; -} - -.ui-icon-pin-w { - background-position: -128px -144px; -} - -.ui-icon-pin-s { - background-position: -144px -144px; -} - -.ui-icon-play { - background-position: 0 -160px; -} - -.ui-icon-pause { - background-position: -16px -160px; -} - -.ui-icon-seek-next { - background-position: -32px -160px; -} - -.ui-icon-seek-prev { - background-position: -48px -160px; -} - -.ui-icon-seek-end { - background-position: -64px -160px; -} - -.ui-icon-seek-start { - background-position: -80px -160px; -} - -/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ - -.ui-icon-seek-first { - background-position: -80px -160px; -} - -.ui-icon-stop { - background-position: -96px -160px; -} - -.ui-icon-eject { - background-position: -112px -160px; -} - -.ui-icon-volume-off { - background-position: -128px -160px; -} - -.ui-icon-volume-on { - background-position: -144px -160px; -} - -.ui-icon-power { - background-position: 0 -176px; -} - -.ui-icon-signal-diag { - background-position: -16px -176px; -} - -.ui-icon-signal { - background-position: -32px -176px; -} - -.ui-icon-battery-0 { - background-position: -48px -176px; -} - -.ui-icon-battery-1 { - background-position: -64px -176px; -} - -.ui-icon-battery-2 { - background-position: -80px -176px; -} - -.ui-icon-battery-3 { - background-position: -96px -176px; -} - -.ui-icon-circle-plus { - background-position: 0 -192px; -} - -.ui-icon-circle-minus { - background-position: -16px -192px; -} - -.ui-icon-circle-close { - background-position: -32px -192px; -} - -.ui-icon-circle-triangle-e { - background-position: -48px -192px; -} - -.ui-icon-circle-triangle-s { - background-position: -64px -192px; -} - -.ui-icon-circle-triangle-w { - background-position: -80px -192px; -} - -.ui-icon-circle-triangle-n { - background-position: -96px -192px; -} - -.ui-icon-circle-arrow-e { - background-position: -112px -192px; -} - -.ui-icon-circle-arrow-s { - background-position: -128px -192px; -} - -.ui-icon-circle-arrow-w { - background-position: -144px -192px; -} - -.ui-icon-circle-arrow-n { - background-position: -160px -192px; -} - -.ui-icon-circle-zoomin { - background-position: -176px -192px; -} - -.ui-icon-circle-zoomout { - background-position: -192px -192px; -} - -.ui-icon-circle-check { - background-position: -208px -192px; -} - -.ui-icon-circlesmall-plus { - background-position: 0 -208px; -} - -.ui-icon-circlesmall-minus { - background-position: -16px -208px; -} - -.ui-icon-circlesmall-close { - background-position: -32px -208px; -} - -.ui-icon-squaresmall-plus { - background-position: -48px -208px; -} - -.ui-icon-squaresmall-minus { - background-position: -64px -208px; -} - -.ui-icon-squaresmall-close { - background-position: -80px -208px; -} - -.ui-icon-grip-dotted-vertical { - background-position: 0 -224px; -} - -.ui-icon-grip-dotted-horizontal { - background-position: -16px -224px; -} - -.ui-icon-grip-solid-vertical { - background-position: -32px -224px; -} - -.ui-icon-grip-solid-horizontal { - background-position: -48px -224px; -} - -.ui-icon-gripsmall-diagonal-se { - background-position: -64px -224px; -} - -.ui-icon-grip-diagonal-se { - background-position: -80px -224px; -} - /* Misc visuals ----------------------------------*/ @@ -1131,40 +324,41 @@ .ui-tabs .ui-tabs-nav { padding: 4px 8px 0; - background: transparent; + background: $toolbar-bg-color; border-radius: 0; border-bottom: $default-border; margin: 0; + padding: 0; + height: $tabs-height; } .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; - top: 1px; - margin: 0 .2em 1px 0; + height: $tabs-height - $default-border-width; + margin: 0; border-color: $default-border-color; - border-bottom: 0 !important; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + border-width: 0; + border-right-width: $default-border-width; + border-radius: 0; padding: 0; white-space: nowrap; - background-color: $medium-gray; + background-color: transparent; font-weight: normal; &.ui-tabs-active { background-color: $light-gray; - margin-bottom: 0px; - height: 28px; + height: $tabs-height; a { cursor: default } } } .ui-tabs .ui-tabs-nav li a { - float: left; + display: block; text-decoration: none; - padding: 6px 12px 5px; + padding: 8px 12px 7px; } .ui-tabs .ui-tabs-nav li.ui-tabs-selected { @@ -1195,41 +389,29 @@ /* UI Tabs Paging */ +.ui-tabs li.ui-tabs-paging-prev { + border-right-width: 1px; +} + .ui-tabs li.ui-tabs-paging-next { float: right !important; padding: 0 !important; -} - -.ui-tabs .ui-tabs-nav .ui-tabs-paging-prev, -.ui-tabs .ui-tabs-nav .ui-tabs-paging-next { - height: 26px; + border-right-width: 0; } .ui-tabs .ui-tabs-nav .ui-tabs-paging-prev a, .ui-tabs .ui-tabs-nav .ui-tabs-paging-next a { display: block; - position: relative; - top: 1px; border: 0; z-index: 2; - padding: 5px; - color: #444; + padding: 8px; + color: $icon-color; text-decoration: none; - background: #eee; cursor: pointer; } -.ui-tabs .ui-tabs-paging-next a:hover, -.ui-tabs .ui-tabs-paging-next a:focus, -.ui-tabs .ui-tabs-paging-next a:active, -.ui-tabs .ui-tabs-paging-prev a:hover, -.ui-tabs .ui-tabs-paging-prev a:focus, -.ui-tabs .ui-tabs-paging-prev a:active { - background: #eee; -} - .ui-tabs .ui-tabs-nav .ui-tabs-paging-disabled { - visibility: hidden; + display: none; } /* diff --git a/app/assets/stylesheets/alchemy/jquery.datetimepicker.scss b/app/assets/stylesheets/alchemy/jquery.datetimepicker.scss deleted file mode 100644 index 591991d68cf..00000000000 --- a/app/assets/stylesheets/alchemy/jquery.datetimepicker.scss +++ /dev/null @@ -1,507 +0,0 @@ -$datepicker_current_bg: $light-blue !default; -$datepicker_current_color: $text-color !default; -$datepicker_current_box_shadow: $blue 0 1px 3px 0 inset !default; -$datepicker_hover_bg: $light_yellow !default; -$datepicker_hover_color: $text-color !default; -$datepicker_today_bg: $very-light-blue !default; -$datepicker_today_color: $text-color !default; -$datepicker_header_color: $text-color !default; -$datepicker_day_bg: $light-gray !default; -$datepicker_day_border: 1px solid $medium-gray !default; -$datepicker_day_color: $text-color !default; - -.xdsoft_datetimepicker { - box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.506); - background: #fff; - border-bottom: 1px solid #bbb; - border-left: 1px solid #ccc; - border-right: 1px solid #ccc; - border-top: 1px solid #ccc; - border-radius: $default-border-radius; - color: $text-color; - padding: 8px; - padding-left: 0; - padding-top: 2px; - position: absolute; - z-index: 9999; - box-sizing: border-box; - display: none; -} - -.xdsoft_datetimepicker.xdsoft_rtl { - padding: 8px 0 8px 8px; -} - -.xdsoft_datetimepicker iframe { - position: absolute; - left: 0; - top: 0; - width: 75px; - height: 210px; - background: transparent; - border: none; -} - -/*For IE8 or lower*/ -.xdsoft_datetimepicker button { - border: none !important; -} - -.xdsoft_noselect { - user-select: none; -} - -.xdsoft_noselect::selection { background: transparent } -.xdsoft_noselect::-moz-selection { background: transparent } - -.xdsoft_datetimepicker.xdsoft_inline { - display: inline-block; - position: static; - box-shadow: none; -} - -.xdsoft_datetimepicker * { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -.xdsoft_datetimepicker .xdsoft_datepicker, -.xdsoft_datetimepicker .xdsoft_timepicker { - display: none; -} - -.xdsoft_datetimepicker .xdsoft_datepicker.active, -.xdsoft_datetimepicker .xdsoft_timepicker.active { - display: block; -} - -.xdsoft_datetimepicker .xdsoft_datepicker { - width: 230px; - float: left; - margin-left: 8px; -} - -.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_datepicker { - float: right; - margin-right: 8px; - margin-left: 0; -} - -.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_datepicker { - width: 260px; -} - -.xdsoft_datetimepicker .xdsoft_timepicker { - width: 60px; - float: left; - text-align: center; - margin-left: 8px; - margin-top: 0; -} - -.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker { - float: right; - margin-right: 8px; - margin-left: 0; -} - -.xdsoft_datetimepicker .xdsoft_datepicker.active+.xdsoft_timepicker { - margin-top: 8px; - margin-bottom: 3px; -} - -.xdsoft_datetimepicker .xdsoft_mounthpicker { - position: relative; - text-align: center; -} - -.xdsoft_datetimepicker .xdsoft_label i, -.xdsoft_datetimepicker .xdsoft_prev, -.xdsoft_datetimepicker .xdsoft_next, -.xdsoft_datetimepicker .xdsoft_today_button { - background-image: none; -} - -.xdsoft_datetimepicker .xdsoft_label i { - opacity: 0.5; - display: inline-block; - width: 9px; - height: 20px; - vertical-align: middle; - - &:before { - content: ""; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - width: 0; - height: 0; - border-style: solid; - border-width: 4px 4px 0 4px; - border-color: $text-color transparent transparent; - } -} - -.xdsoft_datetimepicker .xdsoft_prev, -.xdsoft_datetimepicker .xdsoft_next { - position: relative; - - &:before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 0; - height: 0; - border-style: solid; - } -} - -.xdsoft_datetimepicker .xdsoft_prev { - float: left; - - &:before { - border-width: 7px 7px 7px 0; - border-color: transparent $text-color transparent transparent; - } -} - -.xdsoft_datetimepicker .xdsoft_today_button { - float: left; - margin: 0 5px; - - &:before { - font-family: 'Alchemy Icons'; - font-size: 16px; - line-height: 18px; - content: "\e00a"; - } -} - -.xdsoft_datetimepicker .xdsoft_next { - float: right; - - &:before { - border-width: 7px 0 7px 7px; - border-color: transparent transparent transparent $text-color; - } -} - -.xdsoft_datetimepicker .xdsoft_next, -.xdsoft_datetimepicker .xdsoft_prev , -.xdsoft_datetimepicker .xdsoft_today_button { - background-color: transparent; - background-repeat: no-repeat; - border: 0 none; - cursor: pointer; - display: block; - height: 30px; - opacity: 0.5; - outline: medium none; - overflow: hidden; - padding: 0; - position: relative; - white-space: nowrap; - width: 20px; - min-width: 0; -} - -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev, -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_next { - float: none; - height: 15px; - width: 30px; - display: block; - margin-left: 14px; - margin-top: 7px; -} - -.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_prev, -.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_next { - float: none; - margin-left: 0; - margin-right: 14px; -} - -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev { - margin-bottom: 8px; - margin-top: 0; - - &:before { - border-width: 0 8px 8px 8px; - border-color: transparent transparent $text-color; - } -} - -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_next { - - &:before { - border-width: 8px 8px 0 8px; - border-color: $text-color transparent transparent; - } -} - -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box { - height: 151px; - overflow: hidden; - border-bottom: 1px solid #ddd; -} - -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div { - background: #f5f5f5; - border-top: 1px solid #ddd; - color: #666; - font-size: 12px; - text-align: center; - border-collapse: collapse; - cursor: pointer; - border-bottom-width: 0; - height: 25px; - line-height: 25px; -} - -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div > div:first-child { - border-top-width: 0; -} - -.xdsoft_datetimepicker .xdsoft_today_button:hover, -.xdsoft_datetimepicker .xdsoft_next:hover, -.xdsoft_datetimepicker .xdsoft_prev:hover { - opacity: 1; -} - -.xdsoft_datetimepicker .xdsoft_label { - display: inline; - position: relative; - z-index: 9999; - margin: 0; - padding: 5px 3px; - font-size: 14px; - line-height: 20px; - font-weight: bold; - background-color: #fff; - float: left; - width: 182px; - text-align: center; - cursor: pointer; -} - -.xdsoft_datetimepicker .xdsoft_label:hover i { - opacity: 1; -} - -.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select { - border: 1px solid #ccc; - position: absolute; - right: 0; - top: 30px; - z-index: 101; - display: none; - background: #fff; - max-height: 160px; - overflow-y: hidden; -} - -.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select.xdsoft_monthselect{ right: -7px } -.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select.xdsoft_yearselect{ right: 2px } -.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option:hover { - color: $datepicker_hover_color; - background: $datepicker_hover_bg; -} - -.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option { - padding: 2px 10px 2px 5px; - text-decoration: none !important; -} - -.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option.xdsoft_current { - background: $datepicker_current_bg; - box-shadow: $datepicker_current_box_shadow; - color: $datepicker_current_color; - font-weight: 700; -} - -.xdsoft_datetimepicker .xdsoft_month { - width: 100px; - text-align: right; -} - -.xdsoft_datetimepicker .xdsoft_calendar { - clear: both; -} - -.xdsoft_datetimepicker .xdsoft_year { - width: 52px; - margin-left: 5px; -} - -.xdsoft_datetimepicker .xdsoft_calendar table { - border-collapse: collapse; - width: 100%; -} - -.xdsoft_datetimepicker .xdsoft_calendar td > div { - padding-right: 5px; -} - -.xdsoft_datetimepicker .xdsoft_calendar th { - height: 25px; -} - -.xdsoft_datetimepicker .xdsoft_calendar td, -.xdsoft_datetimepicker .xdsoft_calendar th { - width: 14.2857142%; - background: $datepicker_day_bg; - border: $datepicker_day_border; - color: $datepicker_day_color; - font-size: 12px; - text-align: right; - vertical-align: middle; - padding: 0; - border-collapse: collapse; - cursor: pointer; - height: 25px; -} - -.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar td, -.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar th { - width: 12.5%; -} - -.xdsoft_datetimepicker .xdsoft_calendar th { - background: #f1f1f1; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_today { - background: $datepicker_today_bg; - color: $datepicker_today_color; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_default { - background: #ffe9d2; - box-shadow: #ffb871 0 1px 4px 0 inset; - color: #000; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_mint { - background: #c1ffc9; - box-shadow: #00dd1c 0 1px 4px 0 inset; - color: #000; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_default, -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current, -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_current { - background: $datepicker_current_bg; - box-shadow: $datepicker_current_box_shadow; - color: $datepicker_current_color; - font-weight: 700; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month, -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled, -.xdsoft_datetimepicker .xdsoft_time_box >div >div.xdsoft_disabled { - opacity: 0.5; - cursor: default; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month.xdsoft_disabled { - opacity: 0.2; -} - -.xdsoft_datetimepicker .xdsoft_calendar td:hover, -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div:hover { - color: $datepicker_hover_color !important; - background: $datepicker_hover_bg !important; - box-shadow: none !important; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current.xdsoft_disabled:hover, -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_current.xdsoft_disabled:hover { - background: $datepicker_current_bg !important; - box-shadow: $datepicker_current_box_shadow !important; - color: $datepicker_hover_color !important; -} - -.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled:hover, -.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_disabled:hover { - color: inherit !important; - background: inherit !important; - box-shadow: inherit !important; -} - -.xdsoft_datetimepicker .xdsoft_calendar th { - font-weight: 700; - text-align: center; - color: $datepicker_header_color; - cursor: default; -} - -.xdsoft_datetimepicker .xdsoft_copyright { - color: #ccc !important; - font-size: 10px; - clear: both; - float: none; - margin-left: 8px; -} - -.xdsoft_datetimepicker .xdsoft_copyright a { color: #eee !important } -.xdsoft_datetimepicker .xdsoft_copyright a:hover { color: #aaa !important } - -.xdsoft_time_box { - position: relative; - border: 1px solid #ccc; -} - -.xdsoft_scrollbar > .xdsoft_scroller { - background: #ccc !important; - height: 20px; - border-radius: 3px; -} - -.xdsoft_scrollbar { - position: absolute; - width: 7px; - right: 0; - top: 0; - bottom: 0; - cursor: pointer; -} - -.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_scrollbar { - left: 0; - right: auto; -} - -.xdsoft_scroller_box { - position: relative; -} - -.xdsoft_datetimepicker .xdsoft_save_selected { - display: block; - border: 1px solid #dddddd !important; - margin-top: 5px; - width: 100%; - color: #454551; - font-size: 13px; -} - -.xdsoft_datetimepicker .blue-gradient-button { - font-family: "museo-sans", "Book Antiqua", sans-serif; - font-size: 12px; - font-weight: 300; - color: #82878c; - height: 28px; - position: relative; - padding: 4px 17px 4px 33px; - border: 1px solid #d7d8da; - @include background(linear-gradient(top, #fff 0%, #f4f8fa 73%)); -} - -.xdsoft_datetimepicker .blue-gradient-button:hover, .xdsoft_datetimepicker .blue-gradient-button:focus, .xdsoft_datetimepicker .blue-gradient-button:hover span, .xdsoft_datetimepicker .blue-gradient-button:focus span { - color: #454551; - @include background(linear-gradient(top, #f4f8fa 0%, #FFF 73%)); -} diff --git a/app/assets/stylesheets/alchemy/labels.scss b/app/assets/stylesheets/alchemy/labels.scss new file mode 100644 index 00000000000..d4c2f1183b1 --- /dev/null +++ b/app/assets/stylesheets/alchemy/labels.scss @@ -0,0 +1,6 @@ +.label { + @include label-base( + $margin: $default-margin/2 0, + $padding: $default-padding 2*$default-padding $default-padding + ); +} diff --git a/app/assets/stylesheets/alchemy/lists.scss b/app/assets/stylesheets/alchemy/lists.scss index fe0365f619e..890a52be882 100644 --- a/app/assets/stylesheets/alchemy/lists.scss +++ b/app/assets/stylesheets/alchemy/lists.scss @@ -1,29 +1,29 @@ +ul, ol { + padding-left: 16px; + margin-top: 1em; + margin-bottom: 1em; + + li { + margin-bottom: 0.5em; + } +} + ul.list { margin: 0; padding: 0; list-style-type: none; - &#layoutpages { - margin-top: 16px; - - li { - margin-left: 8px; - } - } - - &#sitemap { - padding: 0 0 104px 0; - } - li { list-style-type: none; display: block; margin-bottom: 0; - &.legend, &.list-header { + &.list-header { + overflow: hidden; background-color: transparent; font-weight: bold; - padding: $default-padding; + padding: $default-padding 2*$default-padding; + margin-bottom: 2*$default-margin; .list-secondary { color: $text-color; @@ -31,11 +31,11 @@ ul.list { } &.even { - background-color: #fff; + background-color: $table-row-even-background-color; } &.odd { - background-color: #eaf3f9; + background-color: $table-row-odd-background-color; } } diff --git a/app/assets/stylesheets/alchemy/menubar.scss b/app/assets/stylesheets/alchemy/menubar.scss index 41f4d557899..85e43e7f7cf 100644 --- a/app/assets/stylesheets/alchemy/menubar.scss +++ b/app/assets/stylesheets/alchemy/menubar.scss @@ -2,28 +2,24 @@ *= require_self */ -@import "bourbon"; @import "alchemy/variables"; @import "alchemy/mixins"; -@import "alchemy/extends"; #alchemy_menubar { position: fixed; top: 0; - left: -356px; + left: -358px; width: 400px; z-index: 10000; - background: $light-gray; - @include animate-left; - @include bottom-right-rounded-border; - @include drop-shadow; + background: $main-menu-bg-color; + transition: left 0.25s cubic-bezier(0.23, 1, 0.32, 1); + box-shadow: 0 0 0 1px $white; box-sizing: border-box; - border-right: $default-border; - border-bottom: $default-border; - height: 44px; - padding: 4px 40px 4px 8px; + height: auto; + padding: 8px 40px 8px 8px; overflow: hidden; - font: $default-font-style; + font-family: $default-font-family; + font-size: $base-font-size; * { box-sizing: border-box; @@ -37,41 +33,45 @@ &:after { content: ''; - width: 28px; - height: 28px; + width: 24px; + height: 24px; position: absolute; - right: 8px; - top: 8px; - background: image-url('alchemy/icon.svg') 0 0 no-repeat; - background-size: 28px 28px; + right: 10px; + top: 50%; + background: image-url('alchemy/icon-white.svg') 1px 1px no-repeat; + background-size: 24px 24px; + transform: translateY(-50%); } ul { padding: 0; margin: 0; - height: auto; + height: 100%; li { - width: 32%; - height: 35px; - margin: 0 $default-margin 0 0; - padding: 0; + width: 33.333%; + height: 100%; + margin: 0; + padding: 0 $default-padding; float: left; list-style-type: none; text-align: center; a, button { - @include button-defaults; - padding-left: $default-padding; - padding-right: $default-padding; + @include button-defaults( + $background-color: $main-menu-bg-color, + $hover-color: $blue, + $hover-border-color: $white, + $border: 1px solid $white, + $box-shadow: none, + $padding: 0.5em 0, + $margin: 0, + $color: $white + ); width: 100%; - display: block; - line-height: 14px; - font-weight: normal; text-decoration: none !important; text-transform: none; - color: $text-color !important; - font-family: $default-font-face; + font-family: $default-font-family; &:after { display: none } } diff --git a/app/assets/stylesheets/alchemy/modules.scss b/app/assets/stylesheets/alchemy/modules.scss deleted file mode 100644 index 02179fae42f..00000000000 --- a/app/assets/stylesheets/alchemy/modules.scss +++ /dev/null @@ -1,27 +0,0 @@ -span.module.dashboard { - background-position: -240px 0; -} - -span.module.pages { - background-position: -40px 0; -} - -span.module.library { - background-position: -120px 0; -} - -span.module.languages { - background-position: -200px 0; -} - -span.module.sites { - background-position: -316px 0; -} - -span.module.tags { - background-position: -280px 0; -} - -span.module.exit { - background-position: -160px 0; -} diff --git a/app/assets/stylesheets/alchemy/navigation.scss b/app/assets/stylesheets/alchemy/navigation.scss index 007de3ce761..526141ea109 100644 --- a/app/assets/stylesheets/alchemy/navigation.scss +++ b/app/assets/stylesheets/alchemy/navigation.scss @@ -1,91 +1,50 @@ #top_menu { - box-sizing: border-box; position: fixed; top: 0; - margin-left: $main-menu-width; - padding-right: $main-menu-width; + margin-left: $collapsed-main-menu-width; + padding-right: $collapsed-main-menu-width; z-index: 20; width: 100%; @extend .disable-user-select; - .collapsed-menu & { - margin-left: $collapsed-main-menu-width; - padding-right: $collapsed-main-menu-width; + @media screen and (min-width: $large-screen-break-point) { + body:not(.collapsed-menu) & { + margin-left: $main-menu-width; + padding-right: $main-menu-width; + } } } #left_menu { - box-sizing: border-box; z-index: 30; height: 100%; position: fixed; top: 0; left: 0; - width: $main-menu-width; - border-right: $default-border; + width: $collapsed-main-menu-width; background-color: $main-menu-bg-color; @extend .disable-user-select; - .collapsed-menu & { - width: $collapsed-main-menu-width; - - .main_navi_entry.has_sub_navigation label { - display: none; - } - - label { - visibility: hidden; - opacity: 0; - background-color: $main-menu-active-bg-color; - transition: opacity $transition-duration $transition-easing; - } - - .main_navi_entry:not(.has_sub_navigation), - #logout { - - &:hover label { - visibility: visible; - opacity: 1; - } - - label { - box-sizing: border-box; - position: absolute; - top: 0; - left: $collapsed-main-menu-width - 1; - width: $main-menu-width + 1; - padding-left: 4*$default-padding; - } + @media screen and (min-width: $large-screen-break-point) { + body:not(.collapsed-menu) & { + width: $main-menu-width; } } - - label { - vertical-align: middle; - padding-left: $default-padding; - cursor: pointer; - @include truncate($main-menu-entry-max-width); - } - - a { - text-shadow: $text-shadow-light; - } - - img, .module.icon { - border-style: none; - height: 24px; - width: 24px; - display: inline-block; - vertical-align: middle; - } } #main_navi { - padding-top: 30px; + max-height: 100vh; + padding-top: 28px; padding-bottom: 80px; + + @media screen and (max-width: $medium-screen-break-point) { + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } } .main_navi_entry { - color: $main-menu-text-color; position: relative; display: block; line-height: 46px; @@ -96,77 +55,117 @@ &:hover { background-color: $main-menu-active-bg-color; - &:not(.active), - .collapsed-menu & { - - .sub_navigation { - visibility: visible; - opacity: 1; - background-color: $main-menu-active-bg-color; - } + .sub_navigation { + visibility: visible; + opacity: 1; + background-color: $main-menu-active-bg-color; } } - &.active { + &.active, &:hover { background-color: $main-menu-active-bg-color; - color: $text-color; cursor: default; - } - - a { - display: block; - padding-left: 3 * $default-padding; - transition: color $transition-duration $transition-easing; - &:hover { + a, .icon { + color: $main-menu-active-text-color; text-decoration: none; - color: #000; text-shadow: none; + + &:focus { + @include default-focus-style( + $box-shadow: inset 0 0 0 2px $main-menu-active-text-color, + $border-radius: 0 + ); + } } } -} -body:not(.collapsed-menu) { + label { + position: absolute; + top: 0; + left: $collapsed-main-menu-width - 1; + width: auto; + padding-left: 4*$default-padding; + padding-right: 4*$default-padding; + visibility: hidden; + opacity: 0; + background-color: $main-menu-active-bg-color; + transition: opacity $transition-duration $transition-easing; + vertical-align: middle; + cursor: pointer; + @include truncate($collapsed-main-menu-entry-max-width, $wrap: nowrap); - .subnavi_tab a { - padding-top: 1px; + @media screen and (min-width: $large-screen-break-point) { + body:not(.collapsed-menu) & { + position: static; + visibility: visible; + max-width: $main-menu-entry-max-width; + opacity: 1; + padding-left: $default-padding; + background-color: transparent; + } + } } - .main_navi_entry.active { + &.has_sub_navigation label { + display: none; - .sub_navigation { - position: static; - visibility: visible; - opacity: 1; - width: auto; + @media screen and (min-width: $large-screen-break-point) { + body:not(.collapsed-menu) & { + display: inline-block; + } } + } - .subnavi_tab { - line-height: 24px; + &:hover label { + visibility: visible; + opacity: 1; + } - a { - padding: 8px 4px 8px 44px; - } + img, .icon { + display: inline-block; + width: 24px; + } + + img { + border-style: none; + height: 24px; + vertical-align: -0.6em; + } + + .icon { + vertical-align: -0.15em; + color: inherit; + } + + a { + display: block; + padding-left: 3 * $default-padding; + font-weight: bold; + color: $main-menu-text-color; + transition: color $transition-duration $transition-easing; + @include antialiased-font-smoothing; + + &:focus { + @include default-focus-style( + $box-shadow: inset 0 0 0 2px $focus-color, + $border-radius: 0 + ); } } } .sub_navigation { position: absolute; - width: $main-menu-width; - left: $main-menu-width; + width: auto; + left: $collapsed-main-menu-width; top: 0; visibility: hidden; opacity: 0; transition: opacity $transition-duration $transition-easing; - - .collapsed-menu & { - left: $collapsed-main-menu-width; - } } .subnavi_tab { - &:first-of-type { border-left: 1px solid $main-menu-active-bg-color; margin-left: -1px; @@ -174,34 +173,78 @@ body:not(.collapsed-menu) { a { padding-left: 4*$default-padding; + padding-right: 4*$default-padding; background-color: transparent; transition: background-color $transition-duration $transition-easing; - @include truncate($main-menu-width, $display: block); + @include truncate($main-menu-width, $display: block, $wrap: nowrap); + } + + &.active a, a:hover { + background-color: darken($dark-orange, 5%); + } +} + +@media screen and (min-width: $large-screen-break-point) { + body:not(.collapsed-menu) { + .sub_navigation { + left: $main-menu-width; + } + + .main_navi_entry.active { + .sub_navigation { + position: static; + visibility: visible; + opacity: 1; + width: auto; + } - &:hover { - background-color: rgba(255,255,255,0.2); + .subnavi_tab { + line-height: 24px; + + a { + padding: 8px 4px 8px 44px; + } + } } } +} - &.active a { - font-weight: bold; - color: $main-menu-text-color; +#locked_pages { + display: flex; + flex-grow: 1; + flex-wrap: nowrap; + height: $header-height; + + label { + float: left; + font-size: $small-font-size; + line-height: 27px; + height: 27px; + margin-left: 8px; + margin-right: 4px; + } + + img { + float: left; + } + + .subnavi_tab > a { + padding-right: 0; } } .locked_page { - position: relative; - float: left; + display: flex; + flex-wrap: nowrap; line-height: $header-height - 1; color: #444; border-right: $default-border; border-bottom: $default-border; - padding-right: 24px; form { - position: absolute; - right: $default-padding; - top: 7px; + align-self: center; + justify-self: flex-end; + margin-right: $default-padding; line-height: 1; } @@ -211,8 +254,13 @@ body:not(.collapsed-menu) { } > a { + display: flex; cursor: pointer; - padding: 0px 8px; + padding-left: 2 * $default-padding; + + &:focus { + @include default-focus-style($box-shadow: inset 0 0 0 2px $focus-color); + } } .icon.close { @@ -225,18 +273,16 @@ body:not(.collapsed-menu) { text-shadow: $text-shadow-light; } - .page_language { - display: inline-block; - color: $muted-text-color; - margin-right: 2px; - font-size: 10px; - text-transform: uppercase; - } - + .page_language, .page_site { display: inline-block; color: $muted-text-color; + font-size: $small-font-size; margin-right: $default-margin; - font-size: 10px; + line-height: 31px; + } + + .page_language { + text-transform: uppercase; } } diff --git a/app/assets/stylesheets/alchemy/node-select.scss b/app/assets/stylesheets/alchemy/node-select.scss new file mode 100644 index 00000000000..ebccde55dbc --- /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 new file mode 100644 index 00000000000..4c2aa6512f8 --- /dev/null +++ b/app/assets/stylesheets/alchemy/nodes.scss @@ -0,0 +1,159 @@ +.nodes_tree.list { + margin: 2em 0; + + &.sorting { + padding-top: 100px; + + .page_icon { + cursor: move + } + } + + .sitemap_node-level_0 { + + > .node_name { + font-weight: bold; + } + } + + .node_page, + .node_url { + width: 250px; + max-width: 45%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + > a { + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + + .external & { + max-width: 90%; + } + } + } + + .node_page { + padding: 0 8px; + margin-left: auto; + } + + .node_url { + display: flex; + align-items: center; + padding: 0 2*$default-padding; + white-space: nowrap; + background-color: $sitemap-info-background-color; + line-height: $sitemap-line-height; + font-size: $small-font-size; + @include border-right-radius($default-border-radius); + + > i { + margin-left: auto; + padding-left: $default-padding; + } + } + + .node_folder { + cursor: pointer; + } + + ul { + margin: 0; + padding: 0; + + &.folded > li { + display: none; + } + } + + li { + line-height: $sitemap-line-height; + padding-left: $default-padding; + + li { + padding-left: $sitemap-line-height; + } + } +} + +#node_filter_result { + display: none; + margin-left: 2*$default-margin; +} + +.sitemap_node { + margin: 3*$default-margin 0; + transition: background-color $transition-duration; + + &.highlight { + background-color: $sitemap-highlight-color; + } + + &.no-match .sitemap_pagename_link { + color: $medium-gray; + } + + &:hover { + background-color: $sitemap-page-hover-color; + border-radius: $default-border-radius; + } + + .node_name { + display: flex; + justify-content: space-between; + @include border-left-radius($default-border-radius); + padding: 0 0 0 10px; + margin: 2px; + text-decoration: none; + overflow: hidden; + background-color: $sitemap-page-background-color; + cursor: move; + + &.without-status { + @include border-right-radius($default-border-radius); + } + + &.inactive { + color: #656565; + } + } +} + +.nodes_tree-left_images { + position: relative; + width: 32px; + line-height: $sitemap-line-height; + float: left; + padding: 0 2*$default-padding; + text-align: center; +} + +.nodes_tree-right_tools { + height: $sitemap-line-height; + padding: 0 2*$default-padding; + float: right; + + > a { + float: left; + width: $sitemap-line-height; + height: $sitemap-line-height; + line-height: $sitemap-line-height; + text-align: center; + margin: 0; + + &.disabled .icon { + opacity: 0.25; + filter: grayscale(100%); + } + } + + .icon.blank { + margin-left: 2px; + float: left; + margin-top: 3px; + margin-right: 3px; + } +} diff --git a/app/assets/stylesheets/alchemy/notices.scss b/app/assets/stylesheets/alchemy/notices.scss index e1d55322ff9..0c34a4d37d3 100644 --- a/app/assets/stylesheets/alchemy/notices.scss +++ b/app/assets/stylesheets/alchemy/notices.scss @@ -1,36 +1,35 @@ -// leave this as div, bcz some table columns also have .message -div.message, p.message { +// Some table columns also have .message class +div.message { padding: 8px 8px 8px 32px; line-height: 17px; - border-width: 1px; - border-style: solid; - @extend %rounded-border; + border-width: $default-border-width; + border-style: $default-border-style; + border-radius: $default-border-radius; position: relative; margin-bottom: 8px; text-align: left; - h1 { - font-size: 14px; - margin: 8px 0; - } - - h1, h2 { margin-top: 0 } + h1, h2, h3 { margin-top: 0 } - span.icon { + .icon { position: absolute; left: 8px; - top: 8px; + top: 10px; + color: inherit; } &.footnote { - font-size: 10px; + font-size: $small-font-size; margin: 16px; } a { text-decoration: underline; - &:hover { color: #000 } + &:hover { + color: inherit; + text-decoration: none; + } } ol { @@ -42,23 +41,22 @@ div.message, p.message { margin-bottom: 4px; } } -} -div.info { - @extend div.message; - color: $text-color; - background-color: #eaf4f9; - border-color: #0b5c84; -} + &.info { + background-color: $info_background_color; + border-color: $info_border_color; + color: $info_text_color; + } -div.error { - @extend div.message; - background-color: #f1c9ca; - color: #931f23; -} + &.error { + background-color: $error_background_color; + border-color: $error_border_color; + color: $error_text_color; + } -div.warning, div.warn { - @extend div.message; - background-color: #f7efb8; - color: #756f47; + &.warning, &.warn, &.alert { + background-color: $warning_background_color; + border-color: $warning_border_color; + color: $warning_text_color; + } } diff --git a/app/assets/stylesheets/alchemy/page-select.scss b/app/assets/stylesheets/alchemy/page-select.scss new file mode 100644 index 00000000000..bff5c5f26e6 --- /dev/null +++ b/app/assets/stylesheets/alchemy/page-select.scss @@ -0,0 +1,30 @@ +.page-select--page, +.page-select--page-urlname { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.page-select--page { + display: flex; + align-items: center; + + .icon { + margin: 0 8px 0 4px; + + .select2-highlighted & { + color: $white + } + } +} + +.page-select--page-urlname { + margin-left: auto; + padding: $default-padding 2*$default-padding; + color: $dark-gray; + font-size: $small-font-size; + + .select2-highlighted & { + color: $white + } +} diff --git a/app/assets/stylesheets/alchemy/pagination.scss b/app/assets/stylesheets/alchemy/pagination.scss index f3be7bca6d2..3a7b2dcb4b7 100644 --- a/app/assets/stylesheets/alchemy/pagination.scss +++ b/app/assets/stylesheets/alchemy/pagination.scss @@ -1,13 +1,13 @@ .pagination { - box-sizing: border-box; - background: $light_gray; - color: $text-color; position: fixed; + display: flex; + align-items: center; + background: $light_gray; bottom: 0; right: 0; width: 100%; left: 0px; - height: 50px; + height: $pagination-height; padding: 2*$default-padding; padding-left: $main-menu-width + 10px; text-align: left; @@ -16,9 +16,10 @@ a, span, em { font-size: $default-font-size; - height: 25px; - line-height: 25px; - padding: $default-padding; + font-weight: bold; + color: $icon-color; + padding: $default-padding 2*$default-padding; + margin: 0 $default-margin/2; } a:hover { @@ -29,15 +30,12 @@ span a { padding: 0 } - .button { padding: 0px 8px } - .current { color: $text-color; - border: $default-border; + border: 1px solid $text-color; text-shadow: 0px 1px 1px #fff; cursor: default; - @extend %rounded-border; - padding: 5px 8px; + border-radius: $default-border-radius; } .previous_page { @@ -56,7 +54,17 @@ border: none; background: transparent; cursor: default; + margin: 0; } + + .icon { + color: inherit; + } +} + +.per-page-select-form { + margin-left: auto; + display: inline; } #assign_image_list .pagination, #assign_file_list .pagination { diff --git a/app/assets/stylesheets/alchemy/preview_window.scss b/app/assets/stylesheets/alchemy/preview_window.scss new file mode 100644 index 00000000000..e5b0ef48f6b --- /dev/null +++ b/app/assets/stylesheets/alchemy/preview_window.scss @@ -0,0 +1,24 @@ +#alchemy_preview_window { + position: absolute; + left: $main-menu-width; + top: 75px; + width: calc(100vw - #{$main-menu-width - $default-border-width}); + height: calc(100vh - #{$top-menu-height}); + border: 0 none; + background: #fff; + border-right: $default-border; + transition: $transition-duration ease-in-out; + + .collapsed-menu & { + left: $collapsed-main-menu-width; + width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width}); + } + + .collapsed-menu.elements-window-visible & { + width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-width}); + + @media screen and (min-width: $large-screen-break-point) { + max-width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-min-width}); + } + } +} diff --git a/app/assets/stylesheets/alchemy/resource_info.scss b/app/assets/stylesheets/alchemy/resource_info.scss index 4870cba5583..e70d66a5210 100644 --- a/app/assets/stylesheets/alchemy/resource_info.scss +++ b/app/assets/stylesheets/alchemy/resource_info.scss @@ -1,9 +1,5 @@ .resource_info { - h2 { - @include form-heading; - } - .value { @include clearfix; position: relative; @@ -29,7 +25,7 @@ position: absolute; right: 2*$default-padding; top: 2*$default-padding; - height: $default-form-field-height; + height: $form-field-height; line-height: 25px; background-color: $medium-gray; margin: -$default-margin -2 * $default-margin; diff --git a/app/assets/stylesheets/alchemy/search.scss b/app/assets/stylesheets/alchemy/search.scss index fd9f8bd74dc..5bef78116f8 100644 --- a/app/assets/stylesheets/alchemy/search.scss +++ b/app/assets/stylesheets/alchemy/search.scss @@ -4,109 +4,44 @@ height: 29px; } -.search_field .icon, -.js_filter_field_box .icon { - position: absolute; - left: 8px; - top: 8px; -} - -.js_filter_field_box { - position: absolute; - right: 4px; - top: 8px; - - .js_filter_field { - width: 220px; - text-align: left; - border-radius: $default-border-radius; - padding: 3px 24px 3px 28px; - height: 29px; - margin: 0; +.search_field { + .fa-search { + position: absolute; + left: 8px; + top: 9px; } - label { - font-size: 10px; - display: block; - text-align: center; - padding-top: 3px; + button { + position: absolute; + top: 0; + left: 0; + width: 30px; + height: inherit; + appearance: none; + background-color: transparent; + border: 0 none; + border-radius: 0; + box-shadow: none; + margin: 0; + padding: 0; } } -.search_field_clear, -.js_filter_field_clear { +.search_field_clear { display: none; - width: 16px; - height: 16px; - line-height: 16px; - @extend .icon-cross; position: absolute; - right: 8px; top: 7px; - text-decoration: none !important; -} - -#tags_tag_list { - text-align: left; - padding-top: 5px; - - ul.tags { - max-height: 10em; - padding: 1px; - margin: 0; - margin-top: 2px; - overflow: auto; - text-align: left; - list-style-type: none; - - li { - background-color: $medium-gray; - padding: 4px; - margin: 0 0 4px 0; - position: relative; - @extend %rounded-border; - - &.odd { background-color: #eaf3f9 } - - input { - position: absolute; - top: 3px; - left: 4px; - } - - label { - display: inline-block; - margin-left: 2em; - line-height: 1.6em; - } - } - } - - .js_filter_field_box { - position: relative; - top: 0; - right: 0; - margin: 0; - - .js_filter_field { - width: 100%; - } - - label { - display: none - } - } - - .js_filter_field_clear { - right: 4px; - } + right: 8px; + width: 16px; + height: 16px; + line-height: 14px; + text-align: center; } #toolbar, #overlay_toolbar { - .search_form, - .js_filter_field_box { + .search_form { position: absolute; top: 0; right: 0; @@ -118,11 +53,13 @@ } .search_input_field { - width: 150px; + width: 0; height: inherit; border: none; - background-color: rgba(255, 255, 255, 0.5); - transition: width $transition-duration ease-in-out; + background-color: transparentize($form-field-background-color, 0.25); + transition: + width $transition-duration ease-in-out, + background-color $transition-duration linear; border-radius: 0; padding: 8px 16px 8px 32px; margin: 0; @@ -130,15 +67,26 @@ box-shadow: none; -webkit-appearance: none; + @media screen and (min-width: $medium-screen-break-point) { + width: 150px; + } + &:focus { width: 250px; + background-color: $form-field-background-color; + + @include default-focus-style( + $box-shadow: inset 0 0 0 2px $focus-color, + $border-radius: 0 + ); } } - .search_field .icon, - .js_filter_field_box .icon, - .search_field_clear, - .js_filter_field_clear { + .search_field .icon { + top: 18px; + } + + .search_field_clear { top: 16px; } } diff --git a/app/assets/stylesheets/alchemy/selects.scss b/app/assets/stylesheets/alchemy/selects.scss index 352500b5a2a..1af2d44cf82 100644 --- a/app/assets/stylesheets/alchemy/selects.scss +++ b/app/assets/stylesheets/alchemy/selects.scss @@ -1,60 +1,85 @@ +$medium-select-box-width: 90px; + select { - @include button-defaults; - height: 29px; + @include button-defaults( + $background-color: $form-field-background-color, + $hover-color: $form-field-background-color, + $hover-border-color: darken($default-border-color, 10%), + $padding: 0 2*$default-padding, + $border: 1px solid $default-border-color, + $box-shadow: none, + $color: $text-color, + $margin: 0); + height: $form-field-height; padding: 0.4em 0.6em; max-width: 100%; width: auto; + font-weight: normal; + vertical-align: middle; + + &.alchemy_selectbox.medium { + width: $medium-select-box-width; + } } .select2-container { - &.alchemy_selectbox { - - text-shadow: #FFF 0px 1px 0px; - .select2-choice { - height: 27px; - line-height: 27px; - @include button-defaults($padding: 0 2*$default-padding, $margin: 0); + height: $form-field-height; + @include button-defaults( + $background-color: $form-field-background-color, + $hover-color: $form-field-background-color, + $hover-border-color: darken($default-border-color, 10%), + $padding: $form-field-padding, + $border: 1px solid $default-border-color, + $box-shadow: none, + $color: $text-color, + $margin: 0); + background-image: none; display: block; font-weight: normal; text-align: left; + .select2-chosen { + overflow: visible; + } + .select2-arrow { - top: 2px; - width: 20px; - height: 23px; - line-height: 23px; + top: 0; + width: $form-field-height; + height: $form-field-height; background: transparent; - border-left: 1px solid #CACACA; + border-left: 0 none; b { - @extend .icon-menu; + @extend .fas; background-image: none; + opacity: $addon-icon-opacity; &:before { - position: relative; - top: 5px; - left: 1px; + content: fa-content($fa-var-caret-down); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } } } - } - &.select2-dropdown-open { - .select2-choice .select2-arrow b { - background-position: -17px 0px; + .select2-search-choice-close { + top: 9px; + right: 28px; } } &.medium { - width: 90px; + width: $medium-select-box-width; } &.select2-container-active { .select2-choice, .select2-choices { - @extend %blue-focus-style; + @include default-focus-style($box-shadow: 0 0 0 1px $focus-color); } } @@ -87,13 +112,37 @@ select { } } + &.select2-container-multi, + &.select2-allowclear { + .select2-search-choice-close { + @extend .fas; + @extend .fa-fw; + left: auto; + right: 5px; + top: 5px; + background: none; + font-size: $small-font-size; + text-decoration: none; + + &:before { + @extend .fa-xs; + content: fa-content($fa-var-times); + opacity: $addon-icon-opacity; + } + + &:hover { + color: $text-color; + } + } + } + &.select2-container-multi { margin-top: 0 !important; &.select2-container-active { .select2-choices { - @extend %blue-focus-style; + @include default-focus-style($box-shadow: 0 0 0 1px $focus-color); } } @@ -108,42 +157,23 @@ select { background-image: none; border: 0 none; box-shadow: none; - } - - .select2-search-choice-close { - left: auto; - right: 4px; - top: 6px; - background: none; - @extend .icon-cross; - - &:hover { - color: $text-color; - } + color: $text-color; } .select2-search-field { height: 29px; input { - color: $text-color; - padding: $default-padding 6px; + color: $form-field-text-color; + padding: 3px 6px; + margin: 0; } } } } } -.tag_list { - .select2-container.select2-container-multi { - .select2-search-choice { - @include tag-base($margin: 4px, $padding: 4px 24px); - } - } -} - .select2-drop { - .select2-search { margin: $default-margin 0; @@ -173,8 +203,8 @@ select { padding: 4px 0; &.select2-highlighted { - background: $button-hover-bg-color; - color: $text-color; + background: $select-hover-bg-color; + color: $select-hover-text-color; } &.select2-result-with-children { @@ -198,7 +228,13 @@ select { &.select2-drop-active { border-radius: $default-border-radius; - border: 1px solid $blue; + border: $default-border; + } + + .select2-more-results, + .select2-ajax-error { + padding: 2*$default-padding; + margin-bottom: 0; } } diff --git a/app/assets/stylesheets/alchemy/sitemap.scss b/app/assets/stylesheets/alchemy/sitemap.scss index c7333368f59..13c5fc24a09 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,55 +23,43 @@ #sitemap-wrapper { position: relative; - min-height: 100%; + min-height: calc(100vh - 96px); } .sitemap_pagename_link { - background-color: #fff; - color: black; display: block; - padding: 0 3*$default-padding; + padding: 0 10px; + margin: 2px; text-decoration: none; - @include transition(background-color, 400ms); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; &.inactive { color: #656565; } - - :hover { - text-decoration: underline; - - &:after { - content: ''; - width: 16px; - height: 16px; - float: left; - margin-right: $default-margin; - background: image-url('alchemy/icons.png') -160px -168px; - margin-top: $default-margin; - } - } } -.redirect_url { +.sitemap_url { + display: none; float: right; - text-align: right; - background-color: #fff0c4; - line-height: $sitemap-line-height; - font-size: 10px; - padding: 0 2*$default-padding; - max-width: 45%; + background-color: $sitemap-info-background-color; + line-height: $sitemap-line-height - 2px; + font-size: $small-font-size; + padding: 0 2 * $default-padding; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} + border: 1px solid $sitemap-page-background-color; -#page_selector_container .sitemap_pagename_link:hover { - text-decoration: none; -} + @media screen and (min-width: $large-screen-break-point) { + display: block; + width: $sitemap-url-large-width; + } -#page_selector_container .sitemap_pagename_link:hover:after { - display: none; + @media screen and (min-width: 1440px) { + width: $sitemap-url-xlarge-width; + } } .sitemap_line_spacer { @@ -79,36 +70,47 @@ .sitemap_page { height: $sitemap-line-height; - margin: 3*$default-margin 0; + margin: 3 * $default-margin 0; position: relative; + transition: background-color $transition-duration; - &.highlight .sitemap_pagename_link { - background-color: #FFFBA5; + &.highlight { + background-color: $sitemap-highlight-color; } &.no-match .sitemap_pagename_link { color: $medium-gray; } - &:hover .sitemap_pagename_link { - background-color: $light_yellow; + &:hover { + background-color: $sitemap-page-hover-color; + border-radius: $default-border-radius; + } +} + +.sitemap_sitename { + @include border-left-radius($default-border-radius); + line-height: $sitemap-line-height - 4; + overflow: hidden; + background-color: $sitemap-page-background-color; + + &.without-status { + @include border-right-radius($default-border-radius); } } .sitemap_left_images { - @extend .left-rounded-border; position: relative; - width: 16px; - height: $sitemap-line-height; - line-height: $sitemap-line-height - 2px; + width: 32px; + line-height: $sitemap-line-height; float: left; - padding: 0 2*$default-padding; - background-color: rgba(#fff, .75); + 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 { @@ -137,34 +139,27 @@ } } -#page_selector_container .sitemap_right_tools { - width: 24px; -} - -ul#sitemap { - - li.legend { +#sitemap { + padding: 0 0 104px 0; - span.right { - margin-right: 52px; + &.sorting { + padding-top: 100px; - span.page_move { - display: inline-block; - vertical-align: middle; - width: 74px; - text-align: center; - } + .page_icon { + cursor: move; } } .page_folder { - @extend .icon; position: absolute; left: -23px; - top: 8px; + top: 0; + } - &.collapsed { @extend .icon.expand.small } - &.folded { @extend .icon.collapse.small } + .placeholder { + background-color: $medium-gray; + margin-bottom: 0px; + margin-left: 22px; } ul { @@ -175,11 +170,6 @@ ul#sitemap { li { padding-left: 0; - .selected_page a { - background-color: $linked-color; - color: $dark-gray; - } - li { padding-left: $sitemap-line-height; padding-right: 0; @@ -187,23 +177,9 @@ ul#sitemap { } } -ul#sitemap li .sitemap_toolrow, -ul.list li .sitemap_toolrow { - margin: 0; - overflow: auto; - padding: $default-padding 0; -} - -ul#sitemap .sitemap_sitename, -ul.list .sitemap_sitename { - height: $sitemap-line-height; - line-height: $sitemap-line-height; - overflow: hidden; -} - .page_infos { @extend .right-rounded-border; - background-color: rgba(#fff, .75); + background-color: $sitemap-page-background-color; border-left: 1px solid $light-gray; float: right; height: $sitemap-line-height; @@ -215,140 +191,70 @@ ul.list .sitemap_sitename { } } -#header .page_status_and_name .page_status { +.page_status_and_name .page_status { margin-left: 2px; - line-height: 1; - vertical-align: text-bottom; } .page_status { display: inline-block; - .icon { - vertical-align: text-bottom; - - &.restricted { - background-position: -256px -39px; - } - - &.not_restricted { - background-position: -288px -39px; - opacity: 0.3; - } - - &.visible { - background-position: -128px -39px; - } - - &.not_visible { - background-position: -160px -39px; - opacity: 0.3; - } - - &.public { - background-position: -192px -39px; - } - - &.not_public { - background-position: -224px -39px; - opacity: 0.3; - } + .alchemy-dialog & { + display: block; } } -#sitemap.list { - - &.sorting { - padding-top: 100px; +#sitemap_heading { + display: flex; + padding: 0; + line-height: 28px; - .page.icon { cursor: move } + .page_name { + margin-left: 43px; } - .locked .page.icon { - background-position: -32px -40px; - } + .page_urlname { + display: none; + margin-left: auto; + padding-left: 2 * $default-padding; + padding-right: 2 * $default-padding; - .page.icon.not-allowed { - &:after { - content: ''; - position: absolute; - bottom: -2px; - right: -1px; - width: 8px; - height: 8px; - background: image-url('alchemy/icons.png') -5px -77px; + @media screen and (min-width: $large-screen-break-point) { + display: block; + width: $sitemap-url-large-width; } - } - .page_status .icon { - vertical-align: middle; + @media screen and (min-width: 1440px) { + width: $sitemap-url-xlarge-width; + } } -} - -a.folder_link { - text-decoration: none; - outline: none; -} - -#create_language_tree_form { - width: 80%; - max-width: 500px; - margin: 2em auto; - padding: 4*$default-padding; - border: $default-border; - background: $medium-gray; - @extend %rounded-border; -} -#sitemap_heading { - padding: 0; - - .page_infos { - margin-right: 210px; - text-align: left; - float: right; - line-height: 28px; - background: transparent; - } + .page_status { + padding-left: 2 * $default-padding; + margin-right: 188px; + margin-left: auto; - .page_name { - line-height: 28px; - margin-left: 43px; + @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 ul.list#sitemap { - margin: 0; - padding: 0 8px 8px 0; - - .page.icon { cursor: default } - - li li.selectboxit-option { - padding-left: 8px; - padding-right: 8px; +.alchemy-dialog { + #sitemap-wrapper { + min-height: 0; } - .selectboxit-options { - right: 0; - } + #sitemap { + margin: 0; + padding: 0 24px 8px 8px; - .close_elements_from_page_selector { - position: absolute; - right: $default-padding; - top: 12px; - font-size: 16px; - width: 16px; - height: 16px; - cursor: pointer; - @extend .icon-cross; + .page_icon { + cursor: default; + } } } - -.elements_from_page_selector { - width: 100%; -} diff --git a/app/assets/stylesheets/alchemy/spinner.scss b/app/assets/stylesheets/alchemy/spinner.scss index c03bcc157f8..bbb28c20461 100644 --- a/app/assets/stylesheets/alchemy/spinner.scss +++ b/app/assets/stylesheets/alchemy/spinner.scss @@ -7,7 +7,7 @@ line-height: 1; path { - fill: $dark-gray; + fill: currentColor; opacity: 1; animation: spinner 1s infinite ease-in-out both; } diff --git a/app/assets/stylesheets/alchemy/tables.scss b/app/assets/stylesheets/alchemy/tables.scss index 58140e949b8..393f47a519e 100644 --- a/app/assets/stylesheets/alchemy/tables.scss +++ b/app/assets/stylesheets/alchemy/tables.scss @@ -4,21 +4,43 @@ table { padding: 0; font-size: inherit; width: 100%; + + &.list .tools { + button, a { + display: inline-block; + width: 18px; + height: 18px; + padding: 0; + margin: 0 $default-margin; + line-height: inherit; + text-align: center; + + > .icon { + margin: 0; + } + } + } } .list td, .list th { - padding: $default-padding 2*$default-padding; + padding: 2*$default-padding; vertical-align: top; line-height: 18px; border-right: 1px solid $medium-gray; + transition: background-color $transition-duration; + + &:last-child { + border-right: 0 none; + } &.wrap-text { white-space: normal !important; } -} -.list td:first-child, .list th:first-child { - border-left: 1px solid $medium-gray + &.icon { + width: 16px; + text-align: center; + } } .list th { @@ -29,67 +51,24 @@ table { } } -.floatThead-container { - background: $light-gray; - overflow: visible !important; // the javascript author thought that, but .... - - &:after { - position: absolute; - bottom: -4px; - left: 0; - width: 100%; - height: 4px; - @include background(linear-gradient(top, $light-gray 0%, rgba($light-gray, .0) 100%)); - content: ''; - } -} - th { white-space: nowrap; text-align: left; vertical-align: top; - background: $light-gray; border-bottom: 1px solid $medium-gray; - font-weight: normal; - text-transform: uppercase; - - i { - font-style: normal; - position: absolute; - right: 2px; - top: 4px; - text-shadow: 1px 1px 1px #fff; - } - - a { - - &.sortable { - display: block; - margin: -4px -8px; - padding: 4px 18px 4px 8px; - text-shadow: 1px 1px 1px #fff; - } - - &.sorted { - position: relative; - background: $medium-gray; - } - } + font-weight: bold; } -tr.odd td { - background-color: #eaf3f9; +tr.even td { + background-color: $table-row-even-background-color; } -tr.even td { - background-color: white; +tr.odd td { + background-color: $table-row-odd-background-color; } .list tr .tools { width: 40px; - background: $light-gray; - border-top: 0 none; - border-right: 0 none; white-space: nowrap; @extend .disable-user-select; @@ -104,48 +83,20 @@ td.heading { text-decoration: underline; } -.list td.icon, .list th.icon { - background-color: $light-gray; - background-image: none; - border-left: 0 none; - padding: 0; - border-right: 1px solid $medium-gray; - text-align: center; - display: table-cell; // reset the .icon display: inline-block -} - -.list .tools .icon, .list td.icon .icon { - display: inline-block; - vertical-align: middle; - width: 16px; - height: 16px; +.list .tools .icon, +.list td.icon .icon { @extend .disable-user-select; - background-repeat: no-repeat; margin: 0 $default-margin; - text-indent: -100px; -} - -.list td.icon .icon { - margin: $default-margin 2*$default-margin 0; } .list tr:hover td { - background-color: $light_yellow; + background-color: $table-row-hover-color; } td, th { &.center, &.boolean { text-align: center; - - .icon.false { - @extend .false; - } - - .icon.true { - @extend .icon; - @extend .true; - } } &.right { @@ -167,7 +118,7 @@ td { width: 80px; } - &.date { + &.date, &.datetime { width: 150px; } diff --git a/app/assets/stylesheets/alchemy/tags.scss b/app/assets/stylesheets/alchemy/tags.scss new file mode 100644 index 00000000000..0f94b5244b4 --- /dev/null +++ b/app/assets/stylesheets/alchemy/tags.scss @@ -0,0 +1,145 @@ +.tag-list { + display: flex; + flex-direction: column; + height: 100%; + + .js_filter_field_box { + margin: 0; + + input { + background-color: transparentize($white, 0.25); + + &:focus { + background-color: $white; + } + } + } + + ul { + list-style-type: none; + padding: 0; + margin: 0; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + + li { + display: block; + + &:first-child { + margin-top: 0; + } + + a { + @include tag-base; + text-decoration: none; + display: block; + } + + &:hover { + background-color: $very-light-blue; + } + + &.active { + a { + background-color: $dark-gray; + color: $light-gray; + } + } + } + } +} + +.tag { + @include tag-base( + $margin: $default-margin/2 0, + $padding: $default-padding 2 * $default-padding $default-padding + ); + pointer-events: none; + font-size: $small-font-size; +} + +.tags .list .tag { + padding: 0; +} + +#assign_image_list { + .tag-list ul { + height: 316px; + } + + &.filtered .tag-list ul { + height: 292px; + } +} + +#assign_file_list { + .tag-list ul { + height: 396px; + } + + &.filtered .tag-list ul { + height: 372px; + } +} + +#overlay_file_list.with_tag_list { + padding-right: 234px; +} + +#tags_tag_list { + .js_filter_field_box { + margin-top: 0; + } + + ul.tags { + max-height: 10em; + padding: 1px; + margin: 0; + margin-top: 2px; + overflow: auto; + text-align: left; + list-style-type: none; + + li { + background-color: $medium-gray; + padding: 4px; + margin: 0 0 4px 0; + position: relative; + border-radius: $default-border-radius; + + &.odd { + background-color: #eaf3f9; + } + + input { + position: absolute; + top: 3px; + left: 4px; + } + + label { + display: inline-block; + margin-left: 2em; + line-height: 1.6em; + } + } + } +} + +.tag_list, +.autocomplete_tag_list { + .select2-container.select2-container-multi { + .select2-search-choice { + padding: 0; + + div { + @include tag-base( + $padding: $default-padding 6 * $default-padding $default-padding 3 * + $default-padding, + $margin: 0 + ); + } + } + } +} diff --git a/app/assets/stylesheets/alchemy/toolbar.scss b/app/assets/stylesheets/alchemy/toolbar.scss index 609cd4e239a..43de655b8fa 100644 --- a/app/assets/stylesheets/alchemy/toolbar.scss +++ b/app/assets/stylesheets/alchemy/toolbar.scss @@ -11,7 +11,6 @@ margin: 0; display: inline-block; vertical-align: middle; - line-height: 5px; } } @@ -20,35 +19,27 @@ margin: 3px $default-margin 0 0; padding: 0; } - - .button { - width: 25px; - height: 21px; - text-decoration: none; - float: right; - border-width: 0; - border-style: none; - font-size: 0; - padding: 0; - background-repeat: no-repeat; - background-position: 0 0; - - &:active { - text-decoration: none; - background-position: 0 -21px; - } - } } .toolbar_buttons { margin-right: 8px; white-space: nowrap; + overflow-x: auto; + overflow-y: visible; + max-width: calc(100vw - #{$collapsed-main-menu-width + 65px}); + -webkit-overflow-scrolling: touch; + + @media screen and (min-width: $medium-screen-break-point) { + overflow: visible; + } &.right { position: absolute; - right: 0; - top: 8px; + right: -8px; + top: 0; margin-left: 8px; + padding: 8px; + background-color: $toolbar-bg-color; label { left: auto; @@ -62,6 +53,14 @@ } } } + + .icon_button { + + .icon { + font-size: 1.25em; + text-shadow: 0 0 1px rgba(#fff, 0.75); + } + } } .toolbar_spacer { @@ -79,24 +78,6 @@ #overlay_toolbar { @extend %gradiated-toolbar; border-bottom: $default-border; - - .button { - width: 25px; - height: 21px; - text-decoration: none; - float: right; - border-width: 0; - border-style: none; - font-size: 0; - padding: 0; - background-repeat: no-repeat; - background-position: 0 0; - - &:active { - text-decoration: none; - background-position: 0 -21px; - } - } } .button_group { diff --git a/app/assets/stylesheets/alchemy/trash.scss b/app/assets/stylesheets/alchemy/trash.scss new file mode 100644 index 00000000000..143d6bdbd7a --- /dev/null +++ b/app/assets/stylesheets/alchemy/trash.scss @@ -0,0 +1,8 @@ +#trash_items { + height: 310px; + overflow-x: hidden; + overflow-y: auto; + margin-bottom: 1em; + + .ajax-folder { display: none } +} diff --git a/app/assets/stylesheets/alchemy/typography.scss b/app/assets/stylesheets/alchemy/typography.scss new file mode 100644 index 00000000000..b74b2d1bb05 --- /dev/null +++ b/app/assets/stylesheets/alchemy/typography.scss @@ -0,0 +1,55 @@ +h1, .h1, +h2, .h2, +h3, .h3, +h4, .h4, +h5, .h5 { + padding: 0; + margin: 1em 0; +} + +h1, .h1, +h2, .h2, +h3, .h3, +h4, .h4 { + font-weight: bold; +} + +h1, .h1 { + font-size: 1.5rem; +} + +h2, .h2 { + font-size: 1.25rem; +} + +h3, .h3 { + font-size: 1.125rem; +} + +h4, .h4, +h5, .h5 { + font-size: 1rem; +} + +h5, .h5 { + font-weight: normal; +} + +p { + margin: 0 0 2*$default-margin 0; + white-space: normal; + + a { + color: $anchor-color; + } +} + +kbd { + font-family: Courier, monospaced; + background: $medium-gray; + border: $default-border; + padding: 0.25em 0.5em; + vertical-align: top; + margin-right: 1ex; + border-radius: $default-border-radius; +} diff --git a/app/assets/stylesheets/alchemy/upload.scss b/app/assets/stylesheets/alchemy/upload.scss index f960b02df12..193c7462005 100644 --- a/app/assets/stylesheets/alchemy/upload.scss +++ b/app/assets/stylesheets/alchemy/upload.scss @@ -42,7 +42,6 @@ } .overall-upload { - box-sizing: border-box; position: fixed; bottom: -100%; left: 0; @@ -52,7 +51,7 @@ padding: 8px; overflow: hidden; background-color: rgba($dark-gray, 0.6); - transition: all 250ms ease-in-out; + transition: all $transition-duration ease-in-out; &.visible { bottom: 0; @@ -81,7 +80,7 @@ background-color: $medium-gray; position: relative; margin-top: 8px; - @extend %rounded-border; + border-radius: $default-border-radius; } .progress-name, @@ -94,12 +93,12 @@ } .progress-bar-status { - font-size: 10px; + font-size: $small-font-size; } .progress-bar-container { background-color: #f9f9f9; - @extend %rounded-border; + border-radius: $default-border-radius; height: 16px; overflow: hidden; width: 100%; @@ -130,11 +129,10 @@ } .progress-cancel { - @extend .icon-cross; - width: 16px; - height: 16px; position: absolute; top: 8px; right: 4px; + width: 16px; + height: 16px; } } diff --git a/app/assets/stylesheets/alchemy/welcome.sass b/app/assets/stylesheets/alchemy/welcome.sass index e2b3b1a65eb..18e8259b905 100644 --- a/app/assets/stylesheets/alchemy/welcome.sass +++ b/app/assets/stylesheets/alchemy/welcome.sass @@ -1,15 +1,16 @@ +@import "alchemy/variables" +@import "alchemy/fonts" + body - background: #f2f2f2 - color: #7f8c8d - font-family: 'Open Sans', Helvetica, Arial, sans-serif + background-color: $main-menu-bg-color + color: $white + font-family: $default-font-family font-weight: normal font-size: 15px line-height: 22px h3 - color: #34495e - font-family: 'Bitter', serif - font-weight: normal + font-weight: bold font-size: 25px line-height: 1.2 margin-bottom: 20px @@ -32,18 +33,17 @@ ul list-style-position: outside li - color: #2980b9 - font-weight: 600 + color: $white margin-bottom: 12px background-color: none border-bottom: 0 none line-height: 22px a - color: #87ab5a + color: $white text-decoration: underline transition: all 0.1s ease-in-out &:hover - color: #2980b9 - text-decoration: underline + color: $white + text-decoration: none diff --git a/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss b/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss index 73507b41c26..a6cd0f8d0cd 100644 --- a/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss +++ b/app/assets/stylesheets/tinymce/skins/alchemy/content.min.css.scss @@ -6,6 +6,7 @@ html { box-shadow: inset 0px 1px 1px $medium-gray; border-radius: $default-border-radius; + font-size: $base-font-size; } body { diff --git a/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss b/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss index 3f0fe2cfc02..ec4467346a2 100755 --- a/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +++ b/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss @@ -28,12 +28,8 @@ direction: ltr; } -.mce-widget button { - box-sizing: border-box; -} - .mce-container *[unselectable] { - @include user-select(none); + user-select: none; } .mce-container::-webkit-scrollbar { @@ -71,6 +67,8 @@ overflow: hidden; height: 100%; z-index: 100; + background: #fff; + border-radius: 0; .mce-resizehandle { display: none; @@ -254,8 +252,7 @@ div.mce-tinymce-inline { .mce-panel { border: 0 solid $default-border-color; - background-color: $medium-gray; - box-sizing: border-box; + background-color: $light-gray; } .mce-toolbar-grp { @@ -339,37 +336,18 @@ div.mce-tinymce-inline { } } -.mce-fullscreen { - border: 0; - padding: 0; - margin: 0; - overflow: hidden; - background: #fff; - height: 100%; -} - -div.mce-fullscreen { - position: fixed; - top: 0; - left: 0; -} - #mce-modal-block { opacity: 0; - filter: alpha(opacity=0); - zoom: 1; position: fixed; left: 0; top: 0; width: 100%; height: 100%; - background-color: rgb(229, 229, 229); + background-color: $dialog-overlay-color; } #mce-modal-block.mce-in { - opacity: 0.4; - filter: alpha(opacity=40); - zoom: 1; + opacity: $dialog-overlay-opacity; } .mce-window-move { @@ -377,9 +355,8 @@ div.mce-fullscreen { } .mce-window { - border-radius: $default-border-radius; - border: $default-border; - background-color: $light-gray; + @include border-top-radius($default-border-radius); + border: 0 none; color: $text-color; position: fixed; top: 0; @@ -387,15 +364,28 @@ div.mce-fullscreen { opacity: 0; -webkit-transition: opacity 150ms ease-in; transition: opacity 150ms ease-in; + box-shadow: $dialog-box-shadow; &.mce-in { opacity: 1; } + + &.mce-panel { + background-color: transparent; + } + + .mce-container-body.mce-abs-layout { + overflow: visible; + } +} + +.mce-window-body { + background-color: $dialog-background-color; } .mce-window-head { padding: 8px 12px; - background-color: $light-blue; + background-color: $dialog-header-color; position: relative; @include border-top-radius($default-border-radius); @@ -404,6 +394,8 @@ div.mce-fullscreen { font-weight: bold; text-rendering: optimizelegibility; padding-right: 10px; + color: $dialog-header-text-color; + @include antialiased-font-smoothing; } .mce-dragh { @@ -422,7 +414,7 @@ div.mce-fullscreen { font-size: 14px; font-weight: bold; line-height: 14px; - color: $text-color; + color: $white; cursor: pointer; height: 18px; overflow: hidden; @@ -430,9 +422,12 @@ div.mce-fullscreen { box-shadow: none; &:hover { - color: #000; background: transparent; } + + .mce-ico { + color: inherit; + } } } @@ -442,15 +437,34 @@ div.mce-fullscreen { .mce-foot { display: block; - background-color: transparent; + background-color: $dialog-background-color; border-top: 0 none; - @include border-bottom-radius($default-border-radius); .mce-btn { border-radius: $default-border-radius; + border: 0 none; + + &:hover, &:focus, &:active { + width: auto !important; + background: $button-hover-bg-color !important; + border-color: $button-hover-border-color; + } button { - padding: 0.5em 1.5em; + padding: $button-padding; + width: auto !important; + height: auto !important; + line-height: 1; + border: 1px solid $button-border-color; + background-color: $button-bg-color; + box-shadow: $button-box-shadow; + } + + .mce-txt { + vertical-align: middle; + color: $button-text-color; + font-weight: bold; + @include antialiased-font-smoothing; } } } @@ -502,18 +516,18 @@ body .mce-abs-layout-item { .mce-tooltip { position: absolute; padding: 5px; + transition-delay: .3s; } .mce-tooltip-inner { - font-size: 11px; - background-color: $dark-gray; - color: #fff; + background-color: $tooltip-background-color; + color: $white; max-width: 200px; padding: $default-padding 2*$default-padding; text-align: center; white-space: normal; box-shadow: 0 0 4px $default-border-color; - @extend %rounded-border; + border-radius: $default-border-radius; } .mce-tooltip-arrow { @@ -521,7 +535,7 @@ body .mce-abs-layout-item { width: 0; height: 0; line-height: 0; - border: 5px solid $dark-gray; + border: 5px solid $tooltip-background-color; } .mce-tooltip-arrow-n { @@ -622,37 +636,44 @@ body .mce-abs-layout-item { } .mce-btn { - border: 1px solid #b1b1b1; - border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25) rgba(0,0,0,0.25); + border: 1px solid $default-border-color; position: relative; - text-shadow: 0 1px 1px rgba(255,255,255,0.75); + text-shadow: $button-text-shadow; display: inline-block; *display: inline; *zoom: 1; - box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05); - @include background(linear-gradient($button-gradient-first-color, $button-gradient-second-color)); + box-shadow: none; + background-color: $light-gray; &:focus, &:hover { - color: #333; - @include background(linear-gradient( darken($button-gradient-first-color, 5%), darken($button-gradient-second-color, 5%) )); + color: $text-color; button { - @include background(inherit); + background: inherit; } } + &:hover { + background-color: darken($medium-gray, 3%); + } + + &:focus { + background-color: $focus-color; + } + button { padding: 2px 4px; font-size: 12px; line-height: 20px; *line-height: 16px; cursor: pointer; - color: #333; + color: $text-color; text-align: center; overflow: visible; -webkit-appearance: none; border-radius: inherit; + box-shadow: none; &:focus { box-shadow: none; @@ -685,9 +706,7 @@ body .mce-abs-layout-item { &.mce-active, &.mce-active:hover { - background-color: #e6f0f5; - background-image: linear-gradient(#e5e5e5, #cccccc); - box-shadow: #c4c4c4 0 1px 2px inset; + background-color: $default-border-color; button i { color: #000; @@ -695,18 +714,7 @@ body .mce-abs-layout-item { } &:not(.mce-disabled):active { - background-color: #d6d6d6; - background-image: -moz-linear-gradient(top,#e6e6e6,#c0c0c0); - background-image: -webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#c0c0c0)); - background-image: -webkit-linear-gradient(top,#e6e6e6,#c0c0c0); - background-image: -o-linear-gradient(top,#e6e6e6,#c0c0c0); - background-image: linear-gradient(to bottom,#e6e6e6,#c0c0c0); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr= '#ffe6e6e6',endColorstr='#ffc0c0c0',GradientType=0); - zoom: 1; - -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); - box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); + background-color: $default-border-color; } } @@ -829,8 +837,7 @@ i.mce-i-checkbox { .mce-checkbox.mce-focus i.mce-i-checkbox, .mce-checkbox:focus i.mce-i-checkbox { - border: 1px solid rgba(82,168,236,0.8); - box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.65); + @include default-focus-style; } .mce-checkbox.mce-disabled .mce-label, @@ -995,8 +1002,7 @@ i.mce-i-checkbox { } .mce-path-item:focus { - background: #666; - color: #fff; + @include default-focus-style; } .mce-path .mce-divider { @@ -1499,27 +1505,32 @@ i.mce-i-resize { } .mce-textbox { - background: #fff; - border: 1px solid #c5c5c5; + &:not(.mce-multiline) { + box-sizing: border-box; + width: auto !important; + height: $form-field-height !important; + padding: $form-field-padding; + } + + background: $form-field-background-color; + border-width: $form-field-border-width; + border-style: $form-field-border-style; + border-color: $form-field-border-color; border-radius: $default-border-radius; - box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow: $form-field-box-shadow; display: inline-block; - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; transition: border linear 0.2s, box-shadow linear 0.2s; - height: 28px; resize: none; - padding: 0 4px; white-space: pre-wrap; - *white-space: pre; - color: #333; + color: $form-field-text-color; + cursor: text; + font-size: $form-field-font-size; + line-height: $form-field-line-height; } .mce-textbox.mce-focus, .mce-textbox:focus { - border-color: rgba(82,168,236,0.8); - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.65); - -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.65); - box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.65); + @include default-focus-style; } .mce-placeholder .mce-textbox { @@ -1528,10 +1539,12 @@ i.mce-i-resize { .mce-textbox.mce-multiline { padding: 4px; + height: inherit; } .mce-textbox.mce-disabled { - color: #adadad; + background: $form-field-disabled-bg-color; + color: $form-field-disabled-text-color; } .mce-rtl .mce-textbox { diff --git a/app/controllers/alchemy/admin/attachments_controller.rb b/app/controllers/alchemy/admin/attachments_controller.rb index 281c253afba..47bc3ca3621 100644 --- a/app/controllers/alchemy/admin/attachments_controller.rb +++ b/app/controllers/alchemy/admin/attachments_controller.rb @@ -1,27 +1,30 @@ +# frozen_string_literal: true + module Alchemy module Admin class AttachmentsController < ResourcesController include UploaderResponses + include ArchiveOverlay - helper 'alchemy/admin/tags' + helper "alchemy/admin/tags" def index - @query = Attachment.ransack(params[:q]) + @query = Attachment.ransack(search_filter_params[:q]) + @query.sorts = "name asc" if @query.sorts.empty? @attachments = @query.result - if params[:tagged_with].present? - @attachments = @attachments.tagged_with(params[:tagged_with]) + if search_filter_params[:tagged_with].present? + @attachments = @attachments.tagged_with(search_filter_params[:tagged_with]) end - if params[:file_type].present? - @attachments = @attachments.with_file_type(params[:file_type]) + if search_filter_params[:file_type].present? + @attachments = @attachments.with_file_type(search_filter_params[:file_type]) end @attachments = @attachments .page(params[:page] || 1) - .per(15) + .per(items_per_page) - @options = options_from_params if in_overlay? archive_overlay end @@ -39,17 +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( - per_page: params[:per_page], - page: params[:page], - q: params[:q] - ), - Alchemy.t("File successfully updated") + admin_attachments_path(search_filter_params), + Alchemy.t("File successfully updated"), ) end end @@ -57,24 +56,29 @@ def update def destroy name = @attachment.name @attachment.destroy - @url = admin_attachments_url( - per_page: params[:per_page], - page: params[:page], - q: params[:q] - ) - flash[:notice] = Alchemy.t('File deleted successfully', name: name) + @url = admin_attachments_url(search_filter_params) + 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 private + def search_filter_params + @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:attachment]).permit( + *common_search_filter_includes + [ + :file_type, + :content_id, + ], + ) + end + def handle_uploader_response(status:) if @attachment.valid? render successful_uploader_response(file: @attachment, status: status) @@ -83,19 +87,6 @@ def handle_uploader_response(status:) end end - def in_overlay? - params[:content_id].present? - end - - def archive_overlay - @content = Content.find_by(id: params[:content_id]) - @options = options_from_params - respond_to do |format| - format.html { render partial: 'archive_overlay' } - format.js { render action: 'archive_overlay' } - end - end - def attachment_attributes params.require(:attachment).permit(:file, :name, :file_name, :tag_list) end diff --git a/app/controllers/alchemy/admin/base_controller.rb b/app/controllers/alchemy/admin/base_controller.rb index 4752cf59d36..63a0a4e34e2 100644 --- a/app/controllers/alchemy/admin/base_controller.rb +++ b/app/controllers/alchemy/admin/base_controller.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + module Alchemy module Admin 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? @@ -25,43 +26,35 @@ 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 - def exception_handler(e) - exception_logger(e) - show_error_notice(e) + def exception_handler(error) + exception_logger(error) + show_error_notice(error) if defined?(Airbrake) - notify_airbrake(e) unless Rails.env.development? || Rails.env.test? + notify_airbrake(error) unless Rails.env.development? || Rails.env.test? end end # Displays an error notice in the Alchemy backend. - def show_error_notice(e) - @error = e + def show_error_notice(error) + @error = error # truncate the message, because very long error messages (i.e from mysql2) causes cookie overflow errors - @notice = e.message[0..255] - @trace = e.backtrace[0..50] + @notice = error.message[0..255] + @trace = error.backtrace[0..50] if request.xhr? render action: "error_notice" else - render '500', status: 500 - end - end - - def redirect_back_or_to_default(default_path = admin_dashboard_path) - if request.referer.present? - redirect_to :back - else - redirect_to default_path + render "500", status: 500 end end @@ -95,6 +88,7 @@ def reset_stamper # Returns true if the current_alchemy_user (The logged-in Alchemy User) has the admin role. def is_admin? return false if !current_alchemy_user + current_alchemy_user.admin? end @@ -110,21 +104,15 @@ 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 - def per_page_value_for_screen_size - return 25 if session[:screen_size].blank? - screen_height = session[:screen_size].split('x').last.to_i - (screen_height / 30) - 10 - end - # Does redirects for html and js requests # def do_redirect_to(url_or_path) respond_to do |format| - format.js { + format.js { @redirect_url = url_or_path render :redirect } @@ -132,21 +120,6 @@ def do_redirect_to(url_or_path) end end - # Extracts options from params. - # - # Helps to parse JSONified options into Hash or Array - # - def options_from_params - case params[:options] - when '', nil - {} - when String - JSON.parse(params[:options]) - else - params[:options].permit!.to_h - end.deep_symbolize_keys - end - # This method decides if we want to raise an exception or not. # # I.e. in test environment. @@ -157,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 @@ -168,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 b4efc155b02..06c6733f4a4 100644 --- a/app/controllers/alchemy/admin/clipboard_controller.rb +++ b/app/controllers/alchemy/admin/clipboard_controller.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + module Alchemy module Admin class ClipboardController < Alchemy::Admin::BaseController + REMARKABLE_TYPES = %w(elements pages) + authorize_resource class: :alchemy_admin_clipboard before_action :set_clipboard @@ -12,11 +16,11 @@ def index end def insert - @item = model_class.find(params[:remarkable_id]) - unless @clipboard.detect { |item| item['id'] == params[:remarkable_id] } + @item = model_class.find(remarkable_params[:remarkable_id]) + unless @clipboard.detect { |item| item["id"] == remarkable_params[:remarkable_id] } @clipboard << { - 'id' => params[:remarkable_id], - 'action' => params[:remove] ? 'cut' : 'copy' + "id" => remarkable_params[:remarkable_id], + "action" => params[:remove] ? "cut" : "copy", } end respond_to do |format| @@ -25,8 +29,8 @@ def insert end def remove - @item = model_class.find(params[:remarkable_id]) - @clipboard.delete_if { |item| item['id'] == params[:remarkable_id] } + @item = model_class.find(remarkable_params[:remarkable_id]) + @clipboard.delete_if { |item| item["id"] == remarkable_params[:remarkable_id] } respond_to do |format| format.js end @@ -39,11 +43,21 @@ def clear private def set_clipboard - @clipboard = get_clipboard(params[:remarkable_type]) + @clipboard = get_clipboard(remarkable_type) end def model_class - "alchemy/#{params[:remarkable_type]}".classify.constantize + raise ActionController::BadRequest unless remarkable_type + + "alchemy/#{remarkable_type}".classify.constantize + end + + def remarkable_params + params.permit(:remarkable_type, :remarkable_id) + end + + def remarkable_type + remarkable_params.keep_if { |_k, type| type.in? REMARKABLE_TYPES }[:remarkable_type] end end end diff --git a/app/controllers/alchemy/admin/contents_controller.rb b/app/controllers/alchemy/admin/contents_controller.rb index aa078a229c8..13b460dc695 100644 --- a/app/controllers/alchemy/admin/contents_controller.rb +++ b/app/controllers/alchemy/admin/contents_controller.rb @@ -1,74 +1,20 @@ +# frozen_string_literal: true + module Alchemy module Admin class ContentsController < Alchemy::Admin::BaseController - helper 'alchemy/admin/essences' + helper "alchemy/admin/essences" authorize_resource class: Alchemy::Content - def new - @element = Element.find(params[:element_id]) - @options = options_from_params - @content = @element.contents.build - end - def create - @element = Element.find(params[:content][:element_id]) - @content = Content.create_from_scratch(@element, content_params) - @options = options_from_params - @html_options = params[:html_options] || {} - if picture_gallery_editor? - @content.update_essence(picture_id: params[:picture_id]) - @options = options_for_picture_gallery - @content_dom_id = "#add_picture_#{@element.id}" - else - @content_dom_id = "#add_content_for_element_#{@element.id}" - end - @locals = essence_editor_locals - end - - def update - @content = Content.find(params[:id]) - @content.update_essence(content_params) - end - - def order - Content.transaction do - params[:content_ids].each_with_index do |id, idx| - Content.where(id: id).update_all(position: idx + 1) - end - end - @notice = Alchemy.t("Successfully saved content position") - end - - def destroy - @content = Content.find(params[:id]) - @content_dom_id = @content.dom_id - @notice = Alchemy.t("Successfully deleted content", content: @content.name_for_label) - @content.destroy + @content = Content.create(content_params) end private def content_params - params.require(:content).permit(:element_id, :name, :ingredient, :essence_type) - end - - def picture_gallery_editor? - params[:content][:essence_type] == 'Alchemy::EssencePicture' && @options[:grouped] == 'true' - end - - def options_for_picture_gallery - @gallery_pictures = @element.contents.gallery_pictures - @dragable = @gallery_pictures.size > 1 - @options.merge(dragable: @dragable) - end - - def essence_editor_locals - { - content: @content, - options: @options.symbolize_keys, - html_options: @html_options.symbolize_keys - } + params.require(:content).permit(:element_id, :name, :ingredient) end end end diff --git a/app/controllers/alchemy/admin/dashboard_controller.rb b/app/controllers/alchemy/admin/dashboard_controller.rb index b911aa89507..cde544c5025 100644 --- a/app/controllers/alchemy/admin/dashboard_controller.rb +++ b/app/controllers/alchemy/admin/dashboard_controller.rb @@ -1,5 +1,7 @@ -require 'net/http' -require 'alchemy/version' +# frozen_string_literal: true + +require "net/http" +require "alchemy/version" module Alchemy module Admin @@ -26,12 +28,12 @@ def info def update_check @alchemy_version = Alchemy.version if @alchemy_version < latest_alchemy_version - render text: 'true' + render plain: "true" else - render text: 'false' + render plain: "false" end rescue UpdateServiceUnavailable => e - render text: e, status: 503 + render plain: e, status: 503 end private @@ -39,10 +41,11 @@ 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]/ } - versions.sort.last + versions.max end # Get alchemy versions from rubygems or github, if rubygems failes. @@ -51,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'] }.sort + alchemy_tags.collect { |h| h["name"].tr("v", "") }.sort else # no luck at all? raise UpdateServiceUnavailable @@ -68,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 83818193113..54ad29933ce 100644 --- a/app/controllers/alchemy/admin/elements_controller.rb +++ b/app/controllers/alchemy/admin/elements_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin class ElementsController < Alchemy::Admin::BaseController @@ -6,20 +8,8 @@ class ElementsController < Alchemy::Admin::BaseController def index @page = Page.find(params[:page_id]) - @cells = @page.cells - if @cells.blank? - @elements = @page.elements.not_trashed - else - @elements = @page.elements_grouped_by_cells - end - end - - def list - @page_id = params[:page_id] - if @page_id.blank? && !params[:page_urlname].blank? - @page_id = Language.current.pages.find_by(urlname: params[:page_urlname]).id - end - @elements = Element.published.where(page_id: @page_id) + @elements = @page.all_elements.not_nested.unfixed.not_trashed.includes(*element_includes) + @fixed_elements = @page.all_elements.fixed.not_trashed.includes(*element_includes) end def new @@ -27,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 @@ -37,27 +27,20 @@ def create Element.transaction do if @paste_from_clipboard = params[:paste_from_clipboard].present? @element = paste_element_from_clipboard - @cell = @element.cell else - @element = Element.new_from_scratch(params[:element]) - if @page.can_have_cells? - @cell = find_or_create_cell - @element.cell = @cell - end - @element.save + @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 end - @cell_name = @cell.nil? ? "for_other_elements" : @cell.name if @element.valid? render :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 @@ -70,10 +53,10 @@ def create def update if @element.update_contents(contents_params) @page = @element.page - @element_validated = @element.update_attributes!(element_params) + @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 @@ -93,13 +76,12 @@ def order @parent_element = Element.find_by(id: params[:parent_element_id]) Element.transaction do params.fetch(:element_ids, []).each_with_index do |element_id, idx| - # Ensure to set page_id, cell_id and parent_element_id to the current page and - # cell because of trashed elements could still have old values + # Ensure to set page_id and parent_element_id to the current + # because of trashed elements could still have old values Element.where(id: element_id).update_all( page_id: params[:page_id], - cell_id: params[:cell_id], parent_element_id: params[:parent_element_id], - position: idx + 1 + position: idx + 1, ) end @parent_element.try!(:touch) @@ -114,54 +96,52 @@ def fold private - def load_element - @element = Element.find(params[:id]) + def element_includes + [ + { + contents: { + essence: :ingredient_association, + }, + }, + :tags, + { + all_nested_elements: [ + { + contents: { + essence: :ingredient_association, + }, + }, + :tags, + ], + }, + ] end - # Returns the cell for element name in params. - # Creates the cell if necessary. - def find_or_create_cell - if @paste_from_clipboard - element_with_cell_name = params[:paste_from_clipboard] - else - element_with_cell_name = params[:element][:name] - end - return nil if element_with_cell_name.blank? - return nil unless element_with_cell_name.include?('#') - cell_name = element_with_cell_name.split('#').last - cell_definition = Cell.definition_for(cell_name) - if cell_definition.blank? - raise CellDefinitionError, "Cell definition not found for #{cell_name}" - end - @page.cells.find_or_create_by(name: cell_definition['name']) + def load_element + @element = Element.find(params[:id]) end 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']) - new_attributes = {page_id: @page.id} - if @page.can_have_cells? - new_attributes = new_attributes.merge({cell_id: find_or_create_cell.try(:id)}) - end - element = Element.copy(@source_element, new_attributes) - if element_from_clipboard['action'] == 'cut' - cut_element + @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" + @cut_element_id = @source_element.id + @clipboard.delete_if { |item| item["id"] == @source_element.id.to_s } + @source_element.destroy end element end - def cut_element - @cutted_element_id = @source_element.id - @clipboard.delete_if { |item| item['id'] == @source_element.id.to_s } - @source_element.destroy - end - def contents_params params.fetch(:contents, {}).permit! end @@ -173,6 +153,10 @@ def element_params params.fetch(:element, {}) end end + + def create_element_params + params.require(:element).permit(:name, :page_id, :parent_element_id) + end end end end diff --git a/app/controllers/alchemy/admin/essence_files_controller.rb b/app/controllers/alchemy/admin/essence_files_controller.rb index 5f4d33dca8f..0d47ad17c9b 100644 --- a/app/controllers/alchemy/admin/essence_files_controller.rb +++ b/app/controllers/alchemy/admin/essence_files_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin class EssenceFilesController < Alchemy::Admin::BaseController @@ -9,7 +11,6 @@ class EssenceFilesController < Alchemy::Admin::BaseController def edit @content = @essence_file.content - @options = options_from_params end def update @@ -24,13 +25,10 @@ def assign @content = Content.find_by(id: params[:content_id]) @attachment = Attachment.find_by(id: params[:attachment_id]) @content.essence.attachment = @attachment - @options = options_from_params # We need to update timestamp here because we don't save yet, # but the cache needs to be get invalid. - # And we don't user @content.touch here, because that updates - # also the element and page timestamps what we don't want yet. - @content.update_column(:updated_at, Time.current) + @content.touch end private diff --git a/app/controllers/alchemy/admin/essence_pictures_controller.rb b/app/controllers/alchemy/admin/essence_pictures_controller.rb index a3462fa3c9d..f1f56279a26 100644 --- a/app/controllers/alchemy/admin/essence_pictures_controller.rb +++ b/app/controllers/alchemy/admin/essence_pictures_controller.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + module Alchemy module Admin class EssencePicturesController < Alchemy::Admin::BaseController + FLOAT_REGEX = /\A\d+(\.\d+)?\z/ authorize_resource class: Alchemy::EssencePicture before_action :load_essence_picture, only: [:edit, :crop, :update] before_action :load_content, only: [:edit, :update, :assign] - before_action :load_options - 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 @@ -17,10 +19,8 @@ def edit def crop if @picture = @essence_picture.picture @content = @essence_picture.content - @options[:format] ||= (configuration(:image_store_format) || 'png') - @min_size = sizes_from_essence_or_params - @ratio = ratio_from_size_or_params + @ratio = ratio_from_size_or_settings infer_width_or_height_from_ratio @default_box = @essence_picture.default_mask(@min_size) @@ -34,7 +34,7 @@ def update @essence_picture.update(essence_picture_params) end - # Assigns picture, but does not saves it. + # Assigns picture, but does not save it. # # When the user saves the element the content gets updated as well. # @@ -42,14 +42,10 @@ def assign @picture = Picture.find_by(id: params[:picture_id]) @content.essence.picture = @picture @element = @content.element - @dragable = @options[:grouped] - @options = @options.merge(dragable: @dragable) # We need to update timestamp here because we don't save yet, # but the cache needs to be get invalid. - # And we don't user @content.touch here, because that updates - # also the element and page timestamps what we don't want yet. - @content.update_column(:updated_at, Time.current) + @content.touch end def destroy @@ -62,10 +58,6 @@ def destroy private - def load_options - @options = options_from_params - end - def load_essence_picture @essence_picture = EssencePicture.find(params[:id]) end @@ -76,26 +68,26 @@ def load_content # Gets the minimum size of the image to be rendered. # - # The +render_size+ attribute has preference over the +size+ parameter. + # The +render_size+ attribute has preference over the contents +size+ setting. # def sizes_from_essence_or_params if @essence_picture.render_size? @essence_picture.sizes_from_string(@essence_picture.render_size) - elsif @options[:size] - @essence_picture.sizes_from_string(@options[:size]) + elsif @essence_picture.content.settings[:size] + @essence_picture.sizes_from_string(@essence_picture.content.settings[:size]) else { width: 0, height: 0 } end end - # Infers the aspect ratio from size or parameters. If you don't want a fixed + # Infers the aspect ratio from size or contents settings. If you don't want a fixed # aspect ratio, don't specify a size or only width or height. # - def ratio_from_size_or_params - if @min_size.value?(0) && @options[:fixed_ratio] - @options[:fixed_ratio].to_f + def ratio_from_size_or_settings + if @min_size.value?(0) && @essence_picture.content.settings[:fixed_ratio].to_s =~ FLOAT_REGEX + @essence_picture.content.settings[:fixed_ratio].to_f elsif !@min_size[:width].zero? && !@min_size[:height].zero? - @min_size[:width].to_f / @min_size[:height].to_f + @min_size[:width].to_f / @min_size[:height] else false end diff --git a/app/controllers/alchemy/admin/languages_controller.rb b/app/controllers/alchemy/admin/languages_controller.rb index 98b1fb8e55e..402ee0e395f 100644 --- a/app/controllers/alchemy/admin/languages_controller.rb +++ b/app/controllers/alchemy/admin/languages_controller.rb @@ -1,20 +1,55 @@ +# frozen_string_literal: true + module Alchemy module Admin class LanguagesController < ResourcesController + before_action :load_current_site, only: %i[index new] + def index - @query = Language.on_current_site.ransack(params[:q]) - @languages = @query.result.page(params[:page] || 1).per(per_page_value_for_screen_size) + @query = Language.on_site(@current_site).ransack(search_filter_params[:q]) + @query.sorts = default_sort_order if @query.sorts.empty? + @languages = @query.result.page(params[:page] || 1).per(items_per_page) end def new - @language = Language.new - @language.page_layout = configured_page_layout || @language.page_layout + @language = Language.new( + site: @current_site, + 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") + redirect_to alchemy.admin_pages_path(language_id: @language) + else + render :new + end + end + + def destroy + if @language.destroy + flash[:notice] = Alchemy.t("Language successfully removed") + else + flash[:warning] = @language.errors.full_messages.to_sentence + end + do_redirect_to alchemy.admin_languages_path + end + + def switch + set_alchemy_language(params[:language_id]) + do_redirect_to request.referer || alchemy.admin_dashboard_path end private - def configured_page_layout - Config.get(:default_language).try('[]', 'page_layout') + def load_current_site + @current_site = Alchemy::Site.current + if @current_site.nil? + flash[:warning] = Alchemy.t("Please create a site first.") + redirect_to admin_sites_path + end end end end diff --git a/app/controllers/alchemy/admin/layoutpages_controller.rb b/app/controllers/alchemy/admin/layoutpages_controller.rb index 828c51fba1c..2279f19e429 100644 --- a/app/controllers/alchemy/admin/layoutpages_controller.rb +++ b/app/controllers/alchemy/admin/layoutpages_controller.rb @@ -1,17 +1,22 @@ +# frozen_string_literal: true + module Alchemy module Admin class LayoutpagesController < Alchemy::Admin::BaseController authorize_resource class: :alchemy_admin_layoutpages + + include Alchemy::Admin::CurrentLanguage + helper Alchemy::Admin::PagesHelper def index - @layout_root = Page.find_or_create_layout_root_for(Language.current.id) + @layout_pages = Page.layoutpages.where(language: @current_language) @languages = Language.on_current_site end def edit @page = Page.find(params[:id]) - @page_layouts = PageLayout.layouts_with_own_for_select(@page.page_layout, Language.current.id, true) + @page_layouts = PageLayout.layouts_with_own_for_select(@page.page_layout, @current_language.id, true) end end end diff --git a/app/controllers/alchemy/admin/legacy_page_urls_controller.rb b/app/controllers/alchemy/admin/legacy_page_urls_controller.rb index ab9e24f840b..95d9992757b 100644 --- a/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +++ b/app/controllers/alchemy/admin/legacy_page_urls_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class Admin::LegacyPageUrlsController < Alchemy::Admin::ResourcesController before_action :load_page diff --git a/app/controllers/alchemy/admin/nodes_controller.rb b/app/controllers/alchemy/admin/nodes_controller.rb new file mode 100644 index 00000000000..e0db2ef6c0f --- /dev/null +++ b/app/controllers/alchemy/admin/nodes_controller.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Alchemy + module Admin + class NodesController < Admin::ResourcesController + include Alchemy::Admin::CurrentLanguage + + def index + @root_nodes = Node.language_root_nodes + end + + def new + @node = Node.new( + parent_id: params[:parent_id], + language: @current_language, + ) + end + + private + + def resource_params + params.require(:node).permit( + :menu_type, + :parent_id, + :language_id, + :page_id, + :name, + :url, + :title, + :nofollow, + :external, + ) + end + end + end +end diff --git a/app/controllers/alchemy/admin/pages_controller.rb b/app/controllers/alchemy/admin/pages_controller.rb index a99a8ac21a3..6ff82feb40a 100644 --- a/app/controllers/alchemy/admin/pages_controller.rb +++ b/app/controllers/alchemy/admin/pages_controller.rb @@ -1,32 +1,35 @@ +# frozen_string_literal: true + module Alchemy 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] + + authorize_resource class: Alchemy::Page, except: [:index, :tree] + + before_action only: [:index, :tree, :flush, :new, :order, :create, :copy_language_tree] do + authorize! :index, :alchemy_admin_pages + end + + include Alchemy::Admin::CurrentLanguage before_action :set_translation, except: [:show] - before_action :load_page, - only: [:show, :info, :unlock, :visit, :publish, :configure, :edit, :update, :destroy, :fold, - :tree] - before_action :set_root_page, only: [:index, :show, :sort, :order] - authorize_resource class: Alchemy::Page, except: [:index, :tree] - before_action :run_on_page_layout_callbacks, if: :run_on_page_layout_callbacks?, only: [:show] def index - authorize! :index, :alchemy_admin_pages - - @languages = Language.on_current_site if !@page_root - @language = Language.current + @language = @current_language @languages_with_page_tree = Language.on_current_site.with_root_page @page_layouts = PageLayout.layouts_for_select(@language.id) end @@ -35,8 +38,6 @@ def index # Returns all pages as a tree from the root given by the id parameter # def tree - authorize! :tree, :alchemy_admin_pages - render json: serialized_page_tree end @@ -47,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: 'application' + render(layout: Alchemy::Config.get(:admin_page_preview_layout) || "application") end def info @@ -55,10 +56,10 @@ def info end def new - @page = Page.new(layoutpage: params[:layoutpage] == 'true', parent_id: params[:parent_id]) - @page_layouts = PageLayout.layouts_for_select(Language.current.id, @page.layoutpage?) - @clipboard = get_clipboard('pages') - @clipboard_items = Page.all_from_clipboard_for_select(@clipboard, Language.current.id, @page.layoutpage?) + @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_items = Page.all_from_clipboard_for_select(@clipboard, @current_language.id, @page.layoutpage?) end def create @@ -67,9 +68,7 @@ def create flash[:notice] = Alchemy.t("Page created", name: @page.name) do_redirect_to(redirect_path_after_create_page) else - @page_layouts = PageLayout.layouts_for_select(Language.current.id, @page.layoutpage?) - @clipboard = get_clipboard('pages') - @clipboard_items = Page.all_from_clipboard_for_select(@clipboard, Language.current.id, @page.layoutpage?) + new render :new end end @@ -81,18 +80,18 @@ 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 # Set page configuration like page names, meta tags and states. def configure - @page_layouts = PageLayout.layouts_with_own_for_select(@page.page_layout, Language.current.id, @page.layoutpage?) - render @page.redirects_to_external? ? 'configure_external' : 'configure' + @page_layouts = PageLayout.layouts_with_own_for_select(@page.page_layout, @current_language.id, @page.layoutpage?) end # Updates page @@ -102,9 +101,9 @@ def configure def update # stores old page_layout value, because unfurtunally rails @page.changes does not work here. @old_page_layout = @page.page_layout - if @page.update_attributes(page_params) + 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 @@ -120,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 @@ -138,16 +137,9 @@ def destroy end def link - if configuration(:show_real_root) - @page_root = Page.root - else - set_root_page - end - @content_id = params[:content_id] @attachments = Attachment.all.collect { |f| - [f.name, download_attachment_path(id: f.id, name: f.urlname)] + [f.name, download_attachment_path(id: f.id, name: f.slug)] } - @url_prefix = prefix_locale? ? "#{Language.current.code}/" : "" end def fold @@ -177,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 @@ -187,7 +179,7 @@ def publish # fetching page via before filter @page.publish! flash[:notice] = Alchemy.t(:page_published, name: @page.name) - redirect_back_or_to_default(admin_pages_path) + redirect_back(fallback_location: admin_pages_path) end def copy_language_tree @@ -217,30 +209,23 @@ def order do_redirect_to admin_pages_path end - def switch_language - set_alchemy_language(params[:language_id]) - do_redirect_to redirect_path_for_switch_language - end - def flush - Language.current.pages.flushables.update_all(published_at: Time.current) + @current_language.pages.flushables.update_all(published_at: Time.current) # We need to ensure, that also all layoutpages get the +published_at+ timestamp set, # but not set to public true, because the cache_key for an element is +published_at+ # and we don't want the layout pages to be present in +Page.published+ scope. - Language.current.pages.flushable_layoutpages.update_all(published_at: Time.current) + @current_language.pages.flushable_layoutpages.update_all(published_at: Time.current) respond_to { |format| format.js } end 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: Language.current.code + language_code: @current_language.code, ) - page_copy.move_to_child_of Page.root - page_copy end def language_root_to_copy_from @@ -270,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 @@ -306,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: external and 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['external'] == true || item['visible'] == false - # children ignore an ancestor in their path if external or 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 @@ -331,27 +306,19 @@ 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 - def redirect_path_for_switch_language - if request.referer && request.referer.include?('admin/layoutpages') - admin_layoutpages_path - else - admin_pages_path - end - end - def redirect_path_after_create_page - if @page.redirects_to_external? || !@page.editable_by?(current_alchemy_user) - admin_pages_path - else + if @page.editable_by?(current_alchemy_user) params[:redirect_to] || edit_admin_page_path(@page) + else + admin_pages_path end end @@ -370,30 +337,32 @@ def secure_attributes def page_is_locked? return false if !@page.locker.try(:logged_in?) return false if !current_alchemy_user.respond_to?(:id) + @page.locked? && @page.locker.id != current_alchemy_user.id end def page_needs_lock? return true unless @page.locker + @page.locker.try!(:id) != current_alchemy_user.try!(:id) end 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 def set_root_page - @page_root = Language.current_root_page + @page_root = @current_language.root_page end 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 ab69cb0816a..f0bee2de249 100644 --- a/app/controllers/alchemy/admin/pictures_controller.rb +++ b/app/controllers/alchemy/admin/pictures_controller.rb @@ -1,19 +1,28 @@ +# frozen_string_literal: true + module Alchemy module Admin 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] + before_action :set_size, only: [:index, :show, :edit_multiple] + authorize_resource class: Alchemy::Picture def index - @size = params[:size].present? ? params[:size] : 'medium' - @query = Picture.ransack(params[:q]) - @pictures = Picture.search_by(params, @query, pictures_per_page_for_size(@size)) + @query = Picture.ransack(search_filter_params[:q]) + @pictures = Picture.search_by( + search_filter_params, + @query, + items_per_page, + ) + @pictures = @pictures.includes(:thumbs) if in_overlay? archive_overlay @@ -23,8 +32,8 @@ def index def show @previous = @picture.previous(params) @next = @picture.next(params) - @pages = @picture.essence_pictures.group_by(&:page) - render action: 'show' + @assignments = @picture.essence_pictures.joins(content: {element: :page}) + render action: "show" end def create @@ -39,19 +48,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 @@ -82,7 +91,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) @@ -90,7 +99,7 @@ def delete_multiple else flash[:warn] = Alchemy.t("Could not delete Pictures") end - rescue => e + rescue StandardError => e flash[:error] = e.message ensure redirect_to_index @@ -100,48 +109,60 @@ def destroy name = @picture.name @picture.destroy flash[:notice] = Alchemy.t("Picture deleted successfully", name: name) - rescue => e + rescue StandardError => e flash[:error] = e.message ensure redirect_to_index end - private - - def pictures_per_page_for_size(size) - case size - when 'small' - per_page = in_overlay? ? 25 : (per_page_value_for_screen_size * 2.9).floor - when 'large' - per_page = in_overlay? ? 4 : (per_page_value_for_screen_size / 1.7).floor + 1 + def items_per_page + if in_overlay? + case @size + when "small" then 25 + when "large" then 4 + else + 9 + end else - per_page = in_overlay? ? 9 : (per_page_value_for_screen_size / 1.0).ceil + 4 + cookies[:alchemy_pictures_per_page] = params[:per_page] || + cookies[:alchemy_pictures_per_page] || + pictures_per_page_for_size end - per_page end - def in_overlay? - params[:element_id].present? + def items_per_page_options + per_page = pictures_per_page_for_size + [per_page, per_page * 2, per_page * 4] end - def archive_overlay - @content = Content.select('id').find_by(id: params[:content_id]) - @element = Element.select('id').find_by(id: params[:element_id]) - @options = options_from_params + private + + def set_size + @size = params[:size] || session[:alchemy_pictures_size] || "medium" + session[:alchemy_pictures_size] = @size + end - respond_to do |format| - format.html { render partial: 'archive_overlay' } - format.js { render action: 'archive_overlay' } + def pictures_per_page_for_size + case @size + when "small" then 60 + when "large" then 12 + else + 20 end end def redirect_to_index - do_redirect_to admin_pictures_path( - filter: params[:filter].presence, - page: params[:page].presence, - q: params[:q].presence, - size: params[:size].presence, - tagged_with: params[:tagged_with].presence + do_redirect_to admin_pictures_path(search_filter_params) + end + + def search_filter_params + @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:picture_ids]).permit( + *common_search_filter_includes + [ + :size, + :element_id, + :swap, + :content_id, + ], ) end diff --git a/app/controllers/alchemy/admin/resources_controller.rb b/app/controllers/alchemy/admin/resources_controller.rb index 472dc9d3b1c..e1683c16966 100644 --- a/app/controllers/alchemy/admin/resources_controller.rb +++ b/app/controllers/alchemy/admin/resources_controller.rb @@ -1,41 +1,45 @@ -require 'csv' -require 'alchemy/resource' -require 'alchemy/resources_helper' +# frozen_string_literal: true + +require "csv" +require "alchemy/resource" +require "alchemy/resources_helper" module Alchemy module Admin class ResourcesController < Alchemy::Admin::BaseController + COMMON_SEARCH_FILTER_EXCLUDES = [:id, :utf8, :_method, :_, :format].freeze + include Alchemy::ResourcesHelper helper Alchemy::ResourcesHelper, TagsHelper - helper_method :resource_handler + helper_method :resource_handler, :search_filter_params, + :items_per_page, :items_per_page_options before_action :load_resource, only: [:show, :edit, :update, :destroy] - before_action do - authorize!(action_name.to_sym, resource_instance_variable || resource_handler.model) - end + before_action :authorize_resource def index - @query = resource_handler.model.ransack(params[:q]) + @query = resource_handler.model.ransack(search_filter_params[:q]) + @query.sorts = default_sort_order if @query.sorts.empty? items = @query.result if contains_relations? items = items.includes(*resource_relations_names) end - if params[:tagged_with].present? - items = items.tagged_with(params[:tagged_with]) + if search_filter_params[:tagged_with].present? + items = items.tagged_with(search_filter_params[:tagged_with]) end - if params[:filter].present? + if search_filter_params[:filter].present? items = items.public_send(sanitized_filter_params) end respond_to do |format| format.html { - items = items.page(params[:page] || 1).per(per_page_value_for_screen_size) + items = items.page(params[:page] || 1).per(items_per_page) instance_variable_set("@#{resource_handler.resources_name}", items) } format.csv { @@ -49,7 +53,7 @@ def new end def show - render action: 'edit' + render action: "edit" end def edit; end @@ -59,24 +63,27 @@ def create resource_instance_variable.save render_errors_or_redirect( resource_instance_variable, - resources_path(resource_handler.namespaced_resources_name, current_location_params), - flash_notice_for_resource_action + resources_path(resource_instance_variable.class, search_filter_params), + flash_notice_for_resource_action, ) end def update - resource_instance_variable.update_attributes(resource_params) + resource_instance_variable.update(resource_params) render_errors_or_redirect( resource_instance_variable, - resources_path(resource_handler.namespaced_resources_name, current_location_params), - flash_notice_for_resource_action + resources_path(resource_instance_variable.class, search_filter_params), + 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(current_location_params.merge(action: 'index')) + do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index")) end def resource_handler @@ -89,6 +96,7 @@ def resource_handler # The key should look like "Modelname successfully created|updated|destroyed." def flash_notice_for_resource_action(action = params[:action]) return if resource_instance_variable.errors.any? + case action.to_sym when :create verb = "created" @@ -101,17 +109,21 @@ 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 instance_variable_set("@#{resource_handler.resource_name}", resource_handler.model.find(params[:id])) end + def authorize_resource + authorize!(action_name.to_sym, resource_instance_variable || resource_handler.model) + end + # Permits all parameters as default! # # THIS IS INSECURE! Although only signed in admin users can send requests anyway, but we should change this. @@ -126,9 +138,40 @@ def resource_params def sanitized_filter_params resource_model.alchemy_resource_filters.detect do |filter| - filter == params[:filter] + filter == search_filter_params[:filter] end || :all end + + def search_filter_params + @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES).permit(*common_search_filter_includes).to_h + end + + def common_search_filter_includes + [ + { q: [ + resource_handler.search_field_name, + :s, + ] }, + :tagged_with, + :filter, + :page, + :per_page, + ] + end + + def items_per_page + cookies[:alchemy_items_per_page] = params[:per_page] || cookies[:alchemy_items_per_page] || Alchemy::Config.get(:items_per_page) + end + + def items_per_page_options + per_page = Alchemy::Config.get(:items_per_page) + [per_page, per_page * 2, per_page * 4] + end + + def default_sort_order + name = resource_handler.attributes.detect { |attr| attr[:name] == "name" } + name ? "name asc" : "#{resource_handler.attributes.first[:name]} asc" + end end end end diff --git a/app/controllers/alchemy/admin/sites_controller.rb b/app/controllers/alchemy/admin/sites_controller.rb index 1ce7576aa9e..be79b0dd294 100644 --- a/app/controllers/alchemy/admin/sites_controller.rb +++ b/app/controllers/alchemy/admin/sites_controller.rb @@ -1,6 +1,26 @@ +# frozen_string_literal: true + module Alchemy module Admin 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.") + redirect_to alchemy.admin_languages_path(site_id: @site) + else + render :new + end + end + + def destroy + if @site.destroy + flash[:notice] = Alchemy.t("Site successfully removed") + else + flash[:warning] = @site.errors.full_messages.to_sentence + end + do_redirect_to alchemy.admin_sites_path + end end end end diff --git a/app/controllers/alchemy/admin/styleguide_controller.rb b/app/controllers/alchemy/admin/styleguide_controller.rb new file mode 100644 index 00000000000..16473cb13a2 --- /dev/null +++ b/app/controllers/alchemy/admin/styleguide_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Alchemy + module Admin + class StyleguideController < BaseController + authorize_resource class: :alchemy_admin_styleguide + end + end +end diff --git a/app/controllers/alchemy/admin/tags_controller.rb b/app/controllers/alchemy/admin/tags_controller.rb index 30bd4e138ec..857e4f47785 100644 --- a/app/controllers/alchemy/admin/tags_controller.rb +++ b/app/controllers/alchemy/admin/tags_controller.rb @@ -1,39 +1,41 @@ +# frozen_string_literal: true + module Alchemy module Admin class TagsController < ResourcesController before_action :load_tag, only: [:edit, :update, :destroy] def index - @query = ActsAsTaggableOn::Tag.ransack(params[:q]) + @query = Gutentag::Tag.ransack(search_filter_params[:q]) + @query.sorts = default_sort_order if @query.sorts.empty? @tags = @query .result .page(params[:page] || 1) - .per(per_page_value_for_screen_size) + .per(items_per_page) .order("name ASC") end def new - @tag = ActsAsTaggableOn::Tag.new + @tag = Gutentag::Tag.new end def create - @tag = ActsAsTaggableOn::Tag.create(tag_params) - render_errors_or_redirect @tag, admin_tags_path, Alchemy.t('New Tag Created') + @tag = Gutentag::Tag.create(tag_params) + render_errors_or_redirect @tag, admin_tags_path, Alchemy.t("New Tag Created") end def edit - @tags = ActsAsTaggableOn::Tag.order("name ASC").to_a - [@tag] + @tags = Gutentag::Tag.order("name ASC").to_a - [@tag] end def update if tag_params[:merge_to] - @new_tag = ActsAsTaggableOn::Tag.find(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_attributes(tag_params) - @tag.save + @tag.update(tag_params) operation_text = Alchemy.t(:successfully_updated_tag) end render_errors_or_redirect @tag, admin_tags_path, operation_text @@ -55,7 +57,7 @@ def autocomplete private def load_tag - @tag = ActsAsTaggableOn::Tag.find(params[:id]) + @tag = Gutentag::Tag.find(params[:id]) end def tag_params @@ -64,7 +66,8 @@ def tag_params def tags_from_term(term) return [] if term.blank? - ActsAsTaggableOn::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 8f2c5798581..98c3dce5143 100644 --- a/app/controllers/alchemy/admin/trash_controller.rb +++ b/app/controllers/alchemy/admin/trash_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin class TrashController < Alchemy::Admin::BaseController @@ -6,7 +8,7 @@ class TrashController < Alchemy::Admin::BaseController authorize_resource class: false def index - @elements = Element.trashed + @elements = Element.trashed.includes(*element_includes) @page = Page.find(params[:page_id]) @allowed_elements = @page.available_element_definitions end @@ -16,6 +18,25 @@ def clear @elements = Element.trashed @elements.map(&:destroy) end + + private + + def element_includes + [ + { + contents: { + essence: :ingredient_association, + }, + all_nested_elements: [ + { + contents: { + essence: :ingredient_association, + }, + }, + ], + }, + ] + end end end end diff --git a/app/controllers/alchemy/api/base_controller.rb b/app/controllers/alchemy/api/base_controller.rb index 5f97e5b080b..c79f5badcde 100644 --- a/app/controllers/alchemy/api/base_controller.rb +++ b/app/controllers/alchemy/api/base_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class Api::BaseController < Alchemy::BaseController layout false @@ -9,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 cb0c743be94..c864de5fe2f 100644 --- a/app/controllers/alchemy/api/contents_controller.rb +++ b/app/controllers/alchemy/api/contents_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class Api::ContentsController < Api::BaseController # Returns all contents as json object @@ -5,11 +7,18 @@ class Api::ContentsController < Api::BaseController # You can either load all or only these for :element_id param # def index - @contents = Content.accessible_by(current_ability, :index) + # Fix for cancancan not able to merge multiple AR scopes for logged in users + if can? :manage, Alchemy::Content + @contents = Content.all + else + @contents = Content.accessible_by(current_ability, :index) + end if params[:element_id].present? @contents = @contents.where(element_id: params[:element_id]) end - respond_with @contents + @contents = @contents.includes(*content_includes) + + render json: @contents, adapter: :json, root: "contents" end # Returns a json object for content @@ -23,12 +32,25 @@ def index # def show if params[:id] - @content = Content.find(params[:id]) + @content = Content.where(id: params[:id]).includes(:essence).first elsif params[:element_id] && params[:name] - @content = Content.find_by!(element_id: params[:element_id], name: params[:name]) + @content = Content.where( + element_id: params[:element_id], + name: params[:name], + ).includes(*content_includes).first || raise(ActiveRecord::RecordNotFound) end authorize! :show, @content respond_with @content end + + private + + def content_includes + [ + { + essence: :ingredient_association, + }, + ] + end end end diff --git a/app/controllers/alchemy/api/elements_controller.rb b/app/controllers/alchemy/api/elements_controller.rb index d79d19e3e31..1ad95830651 100644 --- a/app/controllers/alchemy/api/elements_controller.rb +++ b/app/controllers/alchemy/api/elements_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class Api::ElementsController < Api::BaseController # Returns all elements as json object @@ -7,22 +9,51 @@ class Api::ElementsController < Api::BaseController # If you want to only load a specific type of element pass ?named=an_element_name # def index - @elements = Element.accessible_by(current_ability, :index) + @elements = Element.not_nested + # Fix for cancancan not able to merge multiple AR scopes for logged in users + if cannot? :manage, Alchemy::Element + @elements = @elements.accessible_by(current_ability, :index) + end if params[:page_id].present? @elements = @elements.where(page_id: params[:page_id]) end if params[:named].present? @elements = @elements.named(params[:named]) end - respond_with @elements + @elements = @elements.includes(*element_includes) + + render json: @elements, adapter: :json, root: "elements" end # Returns a json object for element # def show - @element = Element.find(params[:id]) + @element = Element.where(id: params[:id]).includes(*element_includes).first authorize! :show, @element respond_with @element end + + private + + def element_includes + [ + { + nested_elements: [ + { + contents: { + essence: :ingredient_association, + }, + }, + :tags, + ], + }, + { + contents: { + essence: :ingredient_association, + }, + }, + :tags, + ] + end end end diff --git a/app/controllers/alchemy/api/nodes_controller.rb b/app/controllers/alchemy/api/nodes_controller.rb new file mode 100644 index 00000000000..766f85ce7a7 --- /dev/null +++ b/app/controllers/alchemy/api/nodes_controller.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Alchemy + class Api::NodesController < Api::BaseController + 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]) + render json: @node, serializer: NodeSerializer + end + + def toggle_folded + @node.update(folded: !@node.folded) + render json: @node, serializer: NodeSerializer + end + + private + + def load_node + @node = Node.find(params[:id]) + end + + 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 38ddb1287e2..1b24ddfebe5 100644 --- a/app/controllers/alchemy/api/pages_controller.rb +++ b/app/controllers/alchemy/api/pages_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class Api::PagesController < Api::BaseController before_action :load_page, only: [:show] @@ -5,11 +7,20 @@ class Api::PagesController < Api::BaseController # Returns all pages as json object # def index - @pages = Page.accessible_by(current_ability, :index) - if params[:page_layout].present? - @pages = @pages.where(page_layout: params[:page_layout]) + # Fix for cancancan not able to merge multiple AR scopes for logged in users + if can? :edit_content, Alchemy::Page + @pages = Page.all + else + @pages = Page.accessible_by(current_ability, :index) + end + @pages = @pages.includes(*page_includes) + @pages = @pages.ransack(params[:q]).result + + if params[:page] + @pages = @pages.page(params[:page]).per(params[:per_page]) end - respond_with @pages + + render json: @pages, adapter: :json, root: "pages", meta: meta_data end # Returns all pages as nested json object for tree views @@ -40,12 +51,72 @@ def show private def load_page - @page = Page.find_by(id: params[:id]) || - Language.current.pages.find_by( - urlname: params[:urlname], - language_code: params[:locale] || Language.current.code - ) || - raise(ActiveRecord::RecordNotFound) + @page = load_page_by_id || load_page_by_urlname || raise(ActiveRecord::RecordNotFound) + end + + def load_page_by_id + # The route param is called :urlname although it might be an integer + Page.where(id: params[:urlname]).includes(page_includes).first + end + + def load_page_by_urlname + return unless Language.current + + Language.current.pages.where( + urlname: params[:urlname], + language_code: params[:locale] || Language.current.code, + ).includes(page_includes).first + end + + def meta_data + { + total_count: total_count_value, + per_page: per_page_value, + page: page_value, + } + end + + def total_count_value + params[:page] ? @pages.total_count : @pages.size + end + + def per_page_value + if params[:page] + (params[:per_page] || Kaminari.config.default_per_page).to_i + else + @pages.size + end + end + + def page_value + params[:page] ? params[:page].to_i : nil + end + + def page_includes + [ + :tags, + { + language: :site, + elements: [ + { + nested_elements: [ + { + contents: { + essence: :ingredient_association, + }, + }, + :tags, + ], + }, + { + contents: { + essence: :ingredient_association, + }, + }, + :tags, + ], + }, + ] end end end diff --git a/app/controllers/alchemy/attachments_controller.rb b/app/controllers/alchemy/attachments_controller.rb index d835de8a126..8841ad8a35e 100644 --- a/app/controllers/alchemy/attachments_controller.rb +++ b/app/controllers/alchemy/attachments_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class AttachmentsController < BaseController before_action :load_attachment @@ -5,22 +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 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 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 231bf45b390..ea201d954d4 100644 --- a/app/controllers/alchemy/base_controller.rb +++ b/app/controllers/alchemy/base_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This is the main Alchemy controller all other controllers inherit from. # module Alchemy @@ -6,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) @@ -24,19 +25,15 @@ class BaseController < ApplicationController # Sets +I18n.locale+ to current Alchemy language. # def set_locale - ::I18n.locale = Language.current.locale + return unless Language.current + + ::I18n.locale = Language.current&.locale end def not_found_error!(msg = "Not found \"#{request.fullpath}\"") raise ActionController::RoutingError, msg end - # Shortcut for Alchemy::I18n.translate method - def _t(key, *args) - ActiveSupport::Deprecation.warn("Alchemys `_t` method is deprecated! Use `Alchemy.t` instead.", caller.unshift) - Alchemy.t(key, *args) - end - # Store current request path into session, # so we can later redirect to it. def store_location @@ -51,11 +48,10 @@ def mailer_set_url_options def permission_denied(exception = nil) if exception - Rails.logger.debug <<-WARN - -/!\\ Failed to permit #{exception.action} on #{exception.subject.inspect} for: -#{current_alchemy_user.inspect} -WARN + Rails.logger.debug <<-WARN.strip_heredoc + /!\\ Failed to permit #{exception.action} on #{exception.subject.inspect} for: + #{current_alchemy_user.inspect} + WARN end if current_alchemy_user handle_redirect_for_user @@ -65,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 @@ -77,11 +73,11 @@ def redirect_or_render_notice if request.xhr? respond_to do |format| format.js do - render text: flash.discard(:warning), status: 403 + 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 @@ -90,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 @@ -100,9 +96,11 @@ def handle_redirect_for_guest end # Logs the current exception to the error log. - def exception_logger(e) - Rails.logger.error("\n#{e.class} #{e.message} in #{e.backtrace.first}") - Rails.logger.error(e.backtrace[1..50].each { |l| l.gsub(/#{Rails.root.to_s}/, '') }.join("\n")) + 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}/, "") + }.join("\n")) end end end diff --git a/app/controllers/alchemy/elements_controller.rb b/app/controllers/alchemy/elements_controller.rb index 3b1f377c7ce..3ce41ac391f 100644 --- a/app/controllers/alchemy/elements_controller.rb +++ b/app/controllers/alchemy/elements_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy class ElementsController < Alchemy::BaseController load_and_authorize_resource diff --git a/app/controllers/alchemy/messages_controller.rb b/app/controllers/alchemy/messages_controller.rb index 55d232f23c3..0148f29d1cf 100644 --- a/app/controllers/alchemy/messages_controller.rb +++ b/app/controllers/alchemy/messages_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy # # == Sending Messages: @@ -17,7 +19,7 @@ module Alchemy # - name: mail_from # type: EssenceText # - name: success_page - # type: EssenceSelect + # type: EssencePage # # The fields +mail_to+, +mail_from+, +subject+ and +success_page+ are recommended. # The +Alchemy::MessagesController+ uses them to send your mails. So your customer has full controll of these values inside his contactform element. @@ -30,48 +32,42 @@ module Alchemy # elements: [pageheading, heading, contactform] # autogenerate: [contactform] # - # Disabling the page caching is stronlgy recommended! - # - # The editor view for your element should have this layout: - # - # <%= render_essence_editor_by_name(element, 'mail_from') %> - # <%= render_essence_editor_by_name(element, 'mail_to') %> - # <%= render_essence_editor_by_name(element, 'subject') %> - # <%= page_selector(element, 'success_page', page_attribute: :urlname) %> + # Disabling the page caching is strongly recommended! # # Please have a look at the +alchemy/config/config.yml+ file for further Message settings. # 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: - @message = Message.new(params[:message]) + @message = Message.new(message_params) @message.ip = request.remote_ip @element = Element.find_by(id: @message.contact_form_id) if @element.nil? raise ActiveRecord::RecordNotFound, "Contact form id not found. Please pass the :contact_form_id in a hidden field. Example: <%= f.hidden_field :contact_form_id, value: element.id %>" end + @page = @element.page @root_page = @page.get_language_root if @message.valid? 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 @@ -82,38 +78,56 @@ 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') - if @element.ingredient(:success_page) - urlname = @element.ingredient(:success_page) - elsif mailer_config['forward_to_page'] && mailer_config['mail_success_page'] - urlname = Page.find_by(urlname: mailer_config['mail_success_page']).urlname + 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 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 + def success_page + @_success_page ||= @element.ingredient(:success_page) + end + + def success_page_urlname + case success_page + when Alchemy::Page + success_page.urlname + when String + success_page + end + 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"]) + end end end diff --git a/app/controllers/alchemy/pages_controller.rb b/app/controllers/alchemy/pages_controller.rb index fd0590d9f42..a0d0bedf24a 100644 --- a/app/controllers/alchemy/pages_controller.rb +++ b/app/controllers/alchemy/pages_controller.rb @@ -1,10 +1,22 @@ +# frozen_string_literal: true + module Alchemy class PagesController < Alchemy::BaseController + SHOW_PAGE_PARAMS_KEYS = [ + "action", + "controller", + "urlname", + "locale", + ] + include OnPageLayout::CallbacksRunner # Redirecting concerns. Order is important here! include SiteRedirects - include LocaleRedirects + + before_action :enforce_no_locale, + if: :locale_prefix_not_allowed?, + only: [:index, :show] before_action :load_index_page, only: [:index] before_action :load_page, only: [:show] @@ -12,11 +24,13 @@ class PagesController < Alchemy::BaseController # Legacy page redirects need to run after the page was loaded and before we render 404. include LegacyPageRedirects - # From here on, we need a +@page+ to work with! - before_action :page_not_found!, if: -> { @page.blank? }, only: [:index, :show] + # From here on, we need a published +@page+ to work with! + before_action :page_not_found!, unless: -> { @page&.public? }, only: [:index, :show] - # Page redirects need to run after the page was loaded and we're sure to have a +@page+ set. - include PageRedirects + # Page redirects need to run after the page was loaded and we're sure to have a public +@page+ set. + before_action :enforce_locale, + if: :locale_prefix_missing?, + only: [:index, :show] # We only need to set the +@root_page+ if we are sure that no more redirects happen. before_action :set_root_page, only: [:index, :show] @@ -42,13 +56,7 @@ class PagesController < Alchemy::BaseController # If no public page can be found it renders a 404 error. # def index - if Alchemy::Config.get(:redirect_index) - ActiveSupport::Deprecation.warn("The configuration option `redirect_index` is deprecated and will be removed with the release of Alchemy v4.0") - raise "Remove deprecated `redirect_index` configuration!" if Alchemy.version == "4.0.0.rc1" - redirect_permanently_to page_redirect_url - else - show - end + show end # == The show action gets invoked if one requests '/:urlname' or '/:locale/:urlname' @@ -63,36 +71,44 @@ def index # descendant it finds. If no public page can be found it renders a 404 error. # def show - if redirect_url.present? - redirect_permanently_to redirect_url - else - authorize! :show, @page - render_page if render_fresh_page? - end + authorize! :show, @page + render_page if render_fresh_page? end # Renders a search engine compatible xml sitemap. def sitemap @pages = Page.sitemap respond_to do |format| - format.xml { render layout: 'alchemy/sitemap' } + format.xml { render layout: "alchemy/sitemap" } end end private + # Redirects to requested action without locale prefixed + def enforce_no_locale + redirect_permanently_to additional_params.merge(locale: nil) + end + + # Is the requested locale allowed? + # + # If Alchemy is not in multi language mode or the requested locale is the default locale, + # then we want to redirect to a non prefixed url. + # + def locale_prefix_not_allowed? + params[:locale].present? && !multi_language? || + params[:locale].presence == ::I18n.default_locale.to_s + end + # == Loads index page # # Loads the current public language root page. # - # If the root page is not public it redirects to the first published child. - # This can be configured via +redirect_to_public_child+ [default: true] - # # If no index page and no admin users are present we show the "Welcome to Alchemy" page. # 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 @@ -105,12 +121,36 @@ def load_index_page # @return NilClass # def load_page + page_not_found! unless Language.current + @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 + def enforce_locale + redirect_permanently_to page_locale_redirect_url(locale: Language.current.code) + end + + def locale_prefix_missing? + multi_language? && params[:locale].blank? && !default_locale? + end + + def default_locale? + Language.current.code.to_sym == ::I18n.default_locale.to_sym + end + + # Page url with or without locale while keeping all additional params + def page_locale_redirect_url(options = {}) + options = { + locale: prefix_locale? ? @page.language_code : nil, + urlname: @page.urlname, + }.merge(options) + + alchemy.show_page_path additional_params.merge(options) + end + # Redirects to given url with 301 status def redirect_permanently_to(url) redirect_to url, status: :moved_permanently @@ -124,8 +164,8 @@ def redirect_permanently_to(url) # * locale # def additional_params - params.symbolize_keys.delete_if do |key, _| - [:action, :controller, :urlname, :locale].include?(key) + params.to_unsafe_hash.delete_if do |key, _| + SHOW_PAGE_PARAMS_KEYS.include?(key) end end @@ -145,17 +185,17 @@ 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 end def set_expiration_headers - if @page.cache_page? - expires_in @page.expiration_time, public: !@page.restricted - else + if must_not_cache? expires_now + else + expires_in @page.expiration_time, public: !@page.restricted, must_revalidate: true end end @@ -187,10 +227,15 @@ def page_etag # or the cache is stale, because it's been republished by the user. # def render_fresh_page? - !@page.cache_page? || stale?(etag: page_etag, - last_modified: @page.published_at, - public: !@page.restricted, - template: 'pages/show') + must_not_cache? || stale?(etag: page_etag, + 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 + def must_not_cache? + flash.present? || !@page.cache_page? end def page_not_found! diff --git a/app/controllers/concerns/alchemy/admin/archive_overlay.rb b/app/controllers/concerns/alchemy/admin/archive_overlay.rb new file mode 100644 index 00000000000..6346d646d6b --- /dev/null +++ b/app/controllers/concerns/alchemy/admin/archive_overlay.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +module Alchemy + module Admin + module ArchiveOverlay + private + + def in_overlay? + params[:content_id].present? + end + + 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" } + end + end + end + end +end diff --git a/app/controllers/concerns/alchemy/admin/current_language.rb b/app/controllers/concerns/alchemy/admin/current_language.rb new file mode 100644 index 00000000000..d2bf50a6e71 --- /dev/null +++ b/app/controllers/concerns/alchemy/admin/current_language.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Alchemy + module Admin + module CurrentLanguage + extend ActiveSupport::Concern + + included do + before_action :load_current_language + end + + private + + def load_current_language + @current_language = Alchemy::Language.current + if @current_language.nil? + flash[:warning] = Alchemy.t("Please create a language first.") + redirect_to admin_languages_path + end + end + end + end +end diff --git a/app/controllers/concerns/alchemy/admin/uploader_responses.rb b/app/controllers/concerns/alchemy/admin/uploader_responses.rb index 19553de9d5b..6a2f21f2e6e 100644 --- a/app/controllers/concerns/alchemy/admin/uploader_responses.rb +++ b/app/controllers/concerns/alchemy/admin/uploader_responses.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin module UploaderResponses @@ -6,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 @@ -19,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 @@ -33,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 a4f82e16e0e..aa18739edf2 100644 --- a/app/controllers/concerns/alchemy/legacy_page_redirects.rb +++ b/app/controllers/concerns/alchemy/legacy_page_redirects.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy # Handles Legacy page redirects # @@ -34,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/locale_redirects.rb b/app/controllers/concerns/alchemy/locale_redirects.rb deleted file mode 100644 index 220e8ac2803..00000000000 --- a/app/controllers/concerns/alchemy/locale_redirects.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Alchemy - # Handles locale redirects - # - # If the current URL has a locale prefix, but should not have one it redirects - # to url without locale prefix. - # - # Situations we don't want a locale prefix: - # - # 1. If only one language is published - # 2. If the requested locale is the current default locale - # - module LocaleRedirects - extend ActiveSupport::Concern - - included do - before_action :enforce_no_locale, - if: :locale_prefix_not_allowed?, - only: [:index, :show] - end - - private - - # Redirects to requested action without locale prefixed - def enforce_no_locale - redirect_permanently_to additional_params.merge(locale: nil) - end - - # Is the requested locale allowed? - # - # If Alchemy is not in multi language mode or the requested locale is the default locale, - # then we want to redirect to a non prefixed url. - # - def locale_prefix_not_allowed? - params[:locale].present? && !multi_language? || - params[:locale].presence == ::I18n.default_locale.to_s - end - end -end diff --git a/app/controllers/concerns/alchemy/page_redirects.rb b/app/controllers/concerns/alchemy/page_redirects.rb deleted file mode 100644 index 1e42694e1a0..00000000000 --- a/app/controllers/concerns/alchemy/page_redirects.rb +++ /dev/null @@ -1,73 +0,0 @@ -module Alchemy - # Handles page redirect urls - # - # Lots of reasons exist to redirect to another URL than the requested one. - # These module holds the logic behind these needs. - # - module PageRedirects - extend ActiveSupport::Concern - - private - - # Returns an URL to redirect the request to. - # - # == Lookup: - # - # 1. If the page is not published and we have a published child, - # we return the url top that page. (Configurable through +redirect_to_public_child+). - # 2. If the page layout of the page found has a controller and action configured, - # we return the url to that route. (Configure controller and action in `page_layouts.yml`). - # 3. If the current page URL has no locale prefixed, but we should have one, - # we return the prefixed URL. - # 4. If no redirection is needed returns nil. - # - # @return String - # @return NilClass - # - def redirect_url - @_redirect_url ||= public_child_redirect_url || controller_and_action_url || - locale_prefixed_url || nil - end - - def locale_prefixed_url - return unless locale_prefix_missing? - - page_redirect_url(locale: Language.current.code) - end - - def public_child_redirect_url - return if @page.public? - - if configuration(:redirect_to_public_child) - @page = @page.descendants.published.not_restricted.first - @page ? page_redirect_url : page_not_found! - else - page_not_found! - end - end - - def controller_and_action_url - return unless @page.has_controller? - - main_app.url_for(@page.controller_and_action) - end - - # Page url with or without locale while keeping all additional params - def page_redirect_url(options = {}) - options = { - locale: prefix_locale? ? @page.language_code : nil, - urlname: @page.urlname - }.merge(options) - - alchemy.show_page_path additional_params.merge(options) - end - - def default_locale? - Language.current.code.to_sym == ::I18n.default_locale.to_sym - end - - def locale_prefix_missing? - multi_language? && params[:locale].blank? && !default_locale? - end - end -end diff --git a/app/controllers/concerns/alchemy/site_redirects.rb b/app/controllers/concerns/alchemy/site_redirects.rb index 96fe55ffb34..059bd7a5d32 100644 --- a/app/controllers/concerns/alchemy/site_redirects.rb +++ b/app/controllers/concerns/alchemy/site_redirects.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module SiteRedirects extend ActiveSupport::Concern @@ -14,8 +16,8 @@ def enforce_primary_host_for_site end def needs_redirect_to_primary_host? - current_alchemy_site.redirect_to_primary_host? && - current_alchemy_site.host != '*' && + current_alchemy_site&.redirect_to_primary_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 new file mode 100644 index 00000000000..4d7bcf63178 --- /dev/null +++ b/app/decorators/alchemy/content_editor.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Alchemy + class ContentEditor < SimpleDelegator + alias_method :content, :__getobj__ + + def to_partial_path + "alchemy/essences/#{essence_partial_name}_editor" + end + + def css_classes + [ + "content_editor", + essence_partial_name, + ].compact + end + + def data_attributes + { + content_id: id, + content_name: name, + } + end + + # Returns a string to be passed to Rails form field tags to ensure we have same params layout everywhere. + # + # === Example: + # + # <%= text_field_tag content_editor.form_field_name, content_editor.ingredient %> + # + # === Options: + # + # You can pass an Essence column_name. Default is 'ingredient' + # + # ==== Example: + # + # <%= text_field_tag content_editor.form_field_name(:link), content_editor.ingredient %> + # + def form_field_name(essence_column = "ingredient") + "contents[#{id}][#{essence_column}]" + end + + def form_field_id(essence_column = "ingredient") + "contents_#{id}_#{essence_column}" + end + + # Fixes Rails partial renderer calling to_model on the object + # which reveals the delegated content instead of this decorator. + def respond_to?(method_name) + return false if method_name == :to_model + + super + end + end +end diff --git a/app/decorators/alchemy/element_editor.rb b/app/decorators/alchemy/element_editor.rb new file mode 100644 index 00000000000..29c94780793 --- /dev/null +++ b/app/decorators/alchemy/element_editor.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Alchemy + class ElementEditor < SimpleDelegator + alias_method :element, :__getobj__ + + def to_partial_path + "alchemy/admin/elements/element" + end + + # 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(" ") + end + + # Tells us, if we should show the element footer and form inputs. + def editable? + return false if folded? + + content_definitions.present? || taggable? + end + + # Fixes Rails partial renderer calling to_model on the object + # which reveals the delegated element instead of this decorator. + def respond_to?(method_name) + return false if method_name == :to_model + + super + end + end +end diff --git a/app/helpers/alchemy/admin/attachments_helper.rb b/app/helpers/alchemy/admin/attachments_helper.rb index e09586a5d66..8bc876a2752 100644 --- a/app/helpers/alchemy/admin/attachments_helper.rb +++ b/app/helpers/alchemy/admin/attachments_helper.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + module Alchemy module Admin 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 b3a802b8bd8..496f68f0c3d 100644 --- a/app/helpers/alchemy/admin/base_helper.rb +++ b/app/helpers/alchemy/admin/base_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin # This module contains helper methods for rendering dialogs, toolbar buttons and confirmation windows. @@ -21,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 @@ -53,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. @@ -97,14 +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('', '', class: 'js_filter_field_clear', title: Alchemy.t(:click_to_show_all)) - concat content_tag(:label, Alchemy.t(:search), for: options[:id]) + concat link_to(render_icon(:times, size: "xs"), "", class: "js_filter_field_clear", title: Alchemy.t(:click_to_show_all)) end end @@ -135,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 @@ -169,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 @@ -187,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: 'destroy' + 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_only #{html_options.delete(:class)}".strip + class: "icon_button #{html_options.delete(:class)}".strip, }.merge(html_options) ) end @@ -213,19 +214,12 @@ def render_alchemy_title "Alchemy CMS - #{title}" end - # (internal) Returns max image count as integer or nil. Used for the picture editor in element editor views. - def max_image_count - return nil if !@options - image_count = @options[:maximum_amount_of_images] || @options[:max_images] - image_count.blank? ? nil : image_count.to_i - end - # Renders a toolbar button for the Alchemy toolbar # # == Example # # <%= toolbar_button( - # icon: 'create', + # icon: :plus, # label: 'Create', # url: new_resource_path, # title: 'Create Resource', @@ -265,11 +259,11 @@ def toolbar_button(options = {}) active: false, link_options: {}, dialog_options: {}, - loading_indicator: true + 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 @@ -278,48 +272,6 @@ def toolbar_button(options = {}) end end - # Renders the toolbar shown on top of the records. - # - # == Example - # - # <% label_title = Alchemy.t("Create #{resource_name}", default: Alchemy.t('Create')) %> - # <% toolbar( - # buttons: [ - # { - # icon: 'create', - # label: label_title, - # url: new_resource_path, - # title: label_title, - # hotkey: 'alt+n', - # dialog_options: { - # title: label_title, - # size: "430x400" - # }, - # if_permitted_to: [:create, resource_model] - # } - # ] - # ) %> - # - # @option options [Array] :buttons ([]) - # Pass an Array with button options. They will be passed to {#toolbar_button} helper. - # @option options [Boolean] :search (true) - # Show searchfield. - # - def toolbar(options = {}) - defaults = { - buttons: [], - search: true - } - options = defaults.merge(options) - content_for(:toolbar) do - content = <<-CONTENT -#{options[:buttons].map { |button_options| toolbar_button(button_options) }.join} - #{render('alchemy/admin/partials/search_form', url: options[:search_url]) if options[:search]} - CONTENT - content.html_safe - end - end - # (internal) Used by upload form def new_asset_path_with_session_information(asset_type) session_key = Rails.application.config.session_options[:key] @@ -332,12 +284,14 @@ def new_asset_path_with_session_information(asset_type) # Renders a textfield ready to display a datepicker # - # Uses a HTML5 ++ field. # A Javascript observer converts this into a fancy Datepicker. # If you pass +'datetime'+ as +:type+ the datepicker will also have a Time select. + # If you pass +'time'+ as +:type+ the datepicker will only have a Time select. # - # The date value gets localized via +I18n.l+. The format on Time and Date is +datepicker+ - # or +datetimepicker+, if you pass another +type+. + # This helper always renders "text" as input type because: + # HTML5 supports input types like 'date' but Browsers are using the users OS settings + # to validate the input format. Since Alchemy is localized in the backend the date formats + # should be aligned with the users locale setting in the backend but not the OS settings. # # === Date Example # @@ -347,63 +301,40 @@ def new_asset_path_with_session_information(asset_type) # # <%= alchemy_datepicker(@page, :public_on, type: 'datetime') %> # + # === Time Example + # + # <%= alchemy_datepicker(@meeting, :starts_at, type: 'time') %> + # # @param [ActiveModel::Base] object # An instance of a model # @param [String or Symbol] method # The attribute method to be called for the date value # - # @option html_options [String] :type (date) - # The type of text field + # @option html_options [String] :data-datepicker-type (type) + # The value of the data attribute for the type # @option html_options [String] :class (type) # CSS classes of the input field # @option html_options [String] :value (value of method on object) # 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 ? l(date, format: "#{type}picker".to_sym) : nil + value = date ? date.iso8601 : nil text_field object.class.name.demodulize.underscore.to_sym, - method.to_sym, {type: type, class: type, value: value}.merge(html_options) - end - - # Merges the params-hash with the given hash - def merge_params(p = {}) - params.merge(p).delete_if { |_k, v| v.blank? } - end - - # Deletes one or several params from the params-hash and merges some new params in - def merge_params_without(excludes, p = {}) - current_params = params.clone.symbolize_keys - if excludes.is_a?(Array) - excludes.map { |i| current_params.delete(i.to_sym) } - else - current_params.delete(excludes.to_sym) - end - current_params.merge(p).delete_if { |_k, v| v.blank? } - end - - # Deletes all params from the params-hash except the given ones and merges some new params in - def merge_params_only(includes, p = {}) - current_params = params.clone.symbolize_keys - if includes.is_a?(Array) - symbolized_includes = includes.map(&:to_sym) - current_params.delete_if { |k, _v| !symbolized_includes.include?(k) } - else - current_params.delete_if { |k, _v| k != includes.to_sym } - end - current_params.merge(p).delete_if { |_k, v| v.blank? } + method.to_sym, {type: "text", class: type, "data-datepicker-type" => type, value: value}.merge(html_options) end # Render a hint icon with tooltip for given object. # The model class needs to include the hints module def render_hint_for(element) return unless element.has_hint? - content_tag :span, class: 'hint-with-icon' do - render_icon(:questionmark) + - 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 @@ -413,40 +344,41 @@ 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 # (internal) Returns options for the clipboard select tag def clipboard_select_tag_options(items) - if @page.persisted? && @page.can_have_cells? - grouped_options_for_select(grouped_elements_for_select(items, :id)) - else - options = items.map do |item| - [item.respond_to?(:display_name_with_preview_text) ? item.display_name_with_preview_text : item.name, item.id] + options = items.map do |item| + if item.respond_to?(:display_name_with_preview_text) + name = item.display_name_with_preview_text + else + name = item.name end - options_for_select(options) + [name, item.id] end + options_for_select(options) end # 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 # # == Example # - # <%= hint_with_tooltip('Page layout is missing', class: 'warning icon') %> + # <%= hint_with_tooltip('Page layout is missing', icon: 'info') %> # # @param text [String] - The text displayed in the tooltip - # @param html_options [Hash] - Options passed to the wrapper `content_tag` + # @param icon: 'exclamation-triangle' [String] - Icon name # - def hint_with_tooltip(text, html_options = {}) - css_class = "#{html_options[:class]} with-hint" - content_tag :span, html_options.merge(class: css_class) do - content_tag(:span, text, class: 'hint-bubble') + # @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") end end @@ -455,7 +387,6 @@ def hint_with_tooltip(text, html_options = {}) def page_layout_missing_warning hint_with_tooltip( Alchemy.t(:page_definition_missing), - class: 'inline warning icon' ) end @@ -470,10 +401,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 a9c49b264c4..18b4fcf2148 100644 --- a/app/helpers/alchemy/admin/contents_helper.rb +++ b/app/helpers/alchemy/admin/contents_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin module ContentsHelper @@ -11,7 +13,7 @@ module ContentsHelper # def render_content_name(content) if content.blank? - warning('Content is nil') + warning("Content is nil") return end @@ -22,7 +24,6 @@ def render_content_name(content) icon = hint_with_tooltip( Alchemy.t(:content_definition_missing), - class: 'inline warning icon' ) content_name = "#{icon} #{content_name}".html_safe @@ -37,8 +38,8 @@ def render_content_name(content) # Renders the label and a remove link for a content. def content_label(content) - content_tag :label do - [render_hint_for(content), render_content_name(content)].compact.join(' ').html_safe + content_tag :label, for: content.form_field_id do + [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 6a645c6bd25..56d0aeec669 100644 --- a/app/helpers/alchemy/admin/elements_helper.rb +++ b/app/helpers/alchemy/admin/elements_helper.rb @@ -1,43 +1,11 @@ +# frozen_string_literal: true + module Alchemy module Admin module ElementsHelper - include Alchemy::ElementsHelper - include Alchemy::ElementsBlockHelper - include Alchemy::Admin::BaseHelper include Alchemy::Admin::ContentsHelper include Alchemy::Admin::EssencesHelper - # Renders the element editor partial - def render_editor(element) - render_element(element, :editor) - end - - # Renders a drag'n'drop picture gallery editor for all EssencePictures. - # - # It brings full functionality for adding images, deleting images and sorting them via drag'n'drop. - # Just place this helper inside your element editor view, pass the element as parameter and that's it. - # - # === Options: - # - # :maximum_amount_of_images [Integer] # This option let you handle the amount of images your customer can add to this element. - # - def render_picture_gallery_editor(element, options = {}) - default_options = { - maximum_amount_of_images: nil, - grouped: true - } - options = default_options.merge(options) - render( - partial: "alchemy/admin/elements/picture_gallery_editor", - locals: { - pictures: element.contents.gallery_pictures, - element: element, - options: options - } - ) - end - alias_method :render_picture_editor, :render_picture_gallery_editor - # Returns an elements array for select helper. # # @param [Array] elements definitions @@ -45,92 +13,14 @@ def render_picture_gallery_editor(element, options = {}) # def elements_for_select(elements) return [] if elements.nil? - elements.collect do |e| - [ - Element.display_name_for(e['name']), - e['name'] - ] - end - end - # Returns all elements that can be placed on the current page. - # The elements will be grouped by cell. - # - # @param [Array] elements - # collection of element objects - # @param [String] object_method - # method that is called on the element objects used for the select option value - # - def grouped_elements_for_select(elements, object_method = 'name') - return [] if elements.blank? - cells_definition = @page.cell_definitions - return [] if cells_definition.blank? - options = {} - cells_definition.each do |cell| - cell_elements = elements_for_cell(elements, cell) - optgroup_label = Cell.translated_label_for(cell['name']) - options[optgroup_label] = cell_elements.map do |e| - element_array_for_options(e, object_method, cell) - end - end - options[Alchemy.t(:main_content)] = elements_for_main_content(elements).map do |e| - element_array_for_options(e, object_method) - end - # Remove empty cells - options.delete_if { |_c, e| e.blank? } - end - - def element_array_for_options(e, object_method, cell = nil) - if e.class.name == 'Alchemy::Element' - [ - e.display_name_with_preview_text, - e.send(object_method).to_s + (cell ? "##{cell['name']}" : "") - ] - else + elements.collect do |e| [ - Element.display_name_for(e['name']), - e[object_method] + (cell ? "##{cell['name']}" : "") + Element.display_name_for(e["name"]), + e["name"], ] end end - - # CSS classes for the element editor partial. - def element_editor_classes(element, local_assigns) - [ - 'element-editor', - element.content_definitions.present? ? 'with-contents' : 'without-contents', - element.nestable_elements.any? ? 'nestable' : 'not-nestable', - element.taggable? ? 'taggable' : 'not-taggable', - element.folded ? 'folded' : 'expanded', - local_assigns[:draggable] == false ? 'not-draggable' : 'draggable' - ].join(' ') - end - - # Tells us, if we should show the element footer. - def show_element_footer?(element, with_nestable_elements = nil) - return false if element.folded? - if with_nestable_elements - element.content_definitions.present? || element.taggable? - else - element.nestable_elements.empty? - end - end - - private - - def elements_for_main_content(elements) - page_definition = @page.definition['elements'] - elements.select do |e| - page_definition.include?(e.class.name == 'Element' ? e.name : e['name']) - end - end - - def elements_for_cell(elements, cell) - cell_elements = cell['elements'] - elements.select do |e| - cell_elements.include?(e.class.name == 'Element' ? e.name : e['name']) - end - end end end end diff --git a/app/helpers/alchemy/admin/essences_helper.rb b/app/helpers/alchemy/admin/essences_helper.rb index 6d067303e0a..00fefb240ab 100644 --- a/app/helpers/alchemy/admin/essences_helper.rb +++ b/app/helpers/alchemy/admin/essences_helper.rb @@ -1,117 +1,29 @@ +# frozen_string_literal: true + module Alchemy module Admin module EssencesHelper - include Alchemy::EssencesHelper - include Alchemy::Admin::ContentsHelper - - # Renders the Content editor partial from the given Content. - # For options see -> render_essence - def render_essence_editor(content, options = {}, html_options = {}) - render_essence(content, :editor, {for_editor: options}, html_options) - end - - # Renders the Content editor partial found in views/contents/ for the content with name inside the passed Element. - # For options see -> render_essence - # - # Content creation on the fly: - # - # If you update the elements.yml file after creating an element this helper displays a error message with an option to create the content. - # - def render_essence_editor_by_name(element, name, options = {}, html_options = {}) - if element.blank? - return warning('Element is nil', Alchemy.t(:no_element_given)) - end - content = element.content_by_name(name) - if content.nil? - render_missing_content(element, name, options) - else - render_essence_editor(content, options, html_options) - end - end - - # Returns all public pages from current language as an option tags string suitable or the Rails +select_tag+ helper. - # - # @param [Array] - # A collection of pages so it only returns these pages and does not query the database. - # @param [String] - # Pass a +Page#name+ or +Page#id+ as selected item to the +options_for_select+ helper. - # @param [String] - # Used as prompt message in the select tag - # @param [Symbol] - # Method that is called on the page object to get the value that is passed with the params of the form. - # - def pages_for_select(pages = nil, selected = nil, prompt = "Choose page", page_attribute = :id) - values = [[Alchemy.t(prompt), ""]] - pages ||= begin - nested = true - Language.current.pages.published.order(:lft) - end - values += pages_attributes_for_select(pages, page_attribute, nested) - options_for_select(values, selected.to_s) - end - - # Renders the missing content partial - # - def render_missing_content(element, name, options) - render 'alchemy/admin/contents/missing', {element: element, name: name, options: options} - end - # Renders a thumbnail for given EssencePicture content with correct cropping and size - def essence_picture_thumbnail(content, options = {}) + def essence_picture_thumbnail(content) picture = content.ingredient essence = content.essence return if picture.nil? image_tag( - essence.thumbnail_url(options), + 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, options = {}) - if content.settings_value(:caption_as_textarea, options) - content.settings_value(:sizes, options) ? '380x320' : '380x300' - else - content.settings_value(:sizes, options) ? '380x290' : '380x255' - end - end - - private - - # Returns an Array with page attributes for select options - # - # @param [Array] - # The pages - # @param [String || Symbol] - # The attribute that is used as value - # @param [Boolean] (false) - # Should the name be indented or not - # - def pages_attributes_for_select(pages, page_attribute, indent = false) - pages.map do |page| - [ - page_name_attribute_for_select(page, indent), - page.send(page_attribute).to_s - ] - end - end - - # Returns the page name for pages_for_select helper - # - # @param [Alchemy::Page] - # The page - # @param [Boolean] (false) - # Should the page be indented or not - # - def page_name_attribute_for_select(page, indent = false) - if indent - ("  " * (page.level - 1) + page.name).html_safe + def edit_picture_dialog_size(content) + if content.settings[:caption_as_textarea] + content.settings[:sizes] ? "380x320" : "380x300" else - page.name + 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 16bc17794e3..1a132029a22 100644 --- a/app/helpers/alchemy/admin/form_helper.rb +++ b/app/helpers/alchemy/admin/form_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin module FormHelper @@ -17,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 98e83a9654a..b122eac6633 100644 --- a/app/helpers/alchemy/admin/navigation_helper.rb +++ b/app/helpers/alchemy/admin/navigation_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin # This module contains helper methods for rendering the admin navigation. @@ -10,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 @@ -34,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 @@ -43,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 @@ -72,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 @@ -89,9 +91,10 @@ def url_for_module(alchemy_module) def url_for_module_sub_navigation(navigation) alchemy_module = module_definition_for(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 @@ -103,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 @@ -135,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. @@ -145,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 @@ -172,7 +175,8 @@ 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] || + entry.fetch("nested_controllers", []).include?(params[:controller]) end # Returns true if the given entry's action is current controllers action @@ -180,8 +184,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 4537a6555a7..af2037bf3ee 100644 --- a/app/helpers/alchemy/admin/pages_helper.rb +++ b/app/helpers/alchemy/admin/pages_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin module PagesHelper @@ -7,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 @@ -25,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 @@ -37,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 @@ -48,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 3b017118c91..d2c5f47cc40 100644 --- a/app/helpers/alchemy/admin/pictures_helper.rb +++ b/app/helpers/alchemy/admin/pictures_helper.rb @@ -1,36 +1,13 @@ +# frozen_string_literal: true + module Alchemy module Admin module PicturesHelper - def create_or_assign_url(picture_to_assign, options) - if @content.nil? - { - controller: :contents, - action: :create, - picture_id: picture_to_assign.id, - content: { - essence_type: "Alchemy::EssencePicture", - element_id: @element.id - }, - options: options - } - else - { - controller: :essence_pictures, - action: :assign, - picture_id: picture_to_assign.id, - content_id: @content.id, - options: options - } - end - end - 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 13eba08f765..05212f15f0e 100644 --- a/app/helpers/alchemy/admin/tags_helper.rb +++ b/app/helpers/alchemy/admin/tags_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy module Admin module TagsHelper @@ -10,100 +12,49 @@ 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? - li_s = [] - class_name.constantize.tag_counts.sort { |x, y| x.name.downcase <=> y.name.downcase }.each do |tag| - tags = filtered_by_tag?(tag) ? tag_filter(remove: tag) : tag_filter(add: tag) - li_s << content_tag('li', name: tag.name, class: tag_list_tag_active?(tag) ? 'active' : nil) do + 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 link_to( - "#{tag.name} (#{tag.count})", + "#{tag.name} (#{tag.taggings_count})", url_for( - tag_list_params.reject { |k, _v| k == "page" }.merge( - action: 'index', - tagged_with: tags - ) + search_filter_params.except(:page, :tagged_with).merge( + tagged_with: tags_for_filter(current: tag).presence, + ), ), remote: request.xhr?, - class: 'please_wait' ) end - end - li_s.join.html_safe + end.join.html_safe end - # Returns true if the given tag is in +params[:tag_list]+ + # Returns true if the given tag is in +params[:tagged_with]+ # - # @param tag [ActsAsTaggableOn::Tag] - # the tag - # @param params [Hash] - # url params - # @return [Boolean] - # - def tag_list_tag_active?(tag) - tag_list_params[:tagged_with].to_s.split(',').include?(tag.name) - end - - # Checks if the tagged_with param contains the given tag def filtered_by_tag?(tag) - if tag_list_params[:tagged_with].present? - tags = tag_list_params[:tagged_with].split(',') - tags.include?(tag.name) - else - false - end - end - - # Adds the given tag to the tag filter. - def add_to_tag_filter(tag) - if tag_list_params[:tagged_with].present? - tags = tag_list_params[:tagged_with].split(',') - tags << tag.name - else - [tag.name] - end + tags_from_params.include?(tag.name) end - # Removes the given tag from the tag filter. - def remove_from_tag_filter(tag) - if tag_list_params[:tagged_with].present? - tags = tag_list_params[:tagged_with].split(',') - tags.delete_if { |t| t == tag.name } + # Returns the tags from params suitable for the tags filter. + # + # @param current [Gutentag::Tag] - The current tag that will be added or removed if already present + # @returns [String] + def tags_for_filter(current:) + if filtered_by_tag?(current) + tags_from_params - Array(current.name) else - [] - end + tags_from_params.push(current.name) + end.uniq.join(",") end - # Returns the tag filter from params. - # - # A tag can be added to the filter. - # A tag can also be removed. - # - # Options are: - # * options (Hash): - # ** :add (ActsAsTaggableOn::Tag) - The tag that should be added to the tag-filter - # ** :remove (ActsAsTaggableOn::Tag) - The tag that should be removed from the tag-filter - # - def tag_filter(options = {}) - case - when options[:add] - taglist = add_to_tag_filter(options[:add]) if options[:add] - when options[:remove] - taglist = remove_from_tag_filter(options[:remove]) if options[:remove] - else - return tag_list_params[:tagged_with] - end - return nil if taglist.blank? - taglist.uniq.join(',') + # Returns tags from params + # @returns [Array] + def tags_from_params + search_filter_params[:tagged_with].to_s.split(",") end - def tag_list_params - params.permit( - :controller, - :use_route, - :tagged_with, - :filter, - q: params.fetch(:q, {}).keys - ) + def sorted_tags_from(class_name:) + class_name.constantize.tag_counts.sort { |x, y| x.name.downcase <=> y.name.downcase } end end end diff --git a/app/helpers/alchemy/base_helper.rb b/app/helpers/alchemy/base_helper.rb index e009247e8ba..76b98263264 100644 --- a/app/helpers/alchemy/base_helper.rb +++ b/app/helpers/alchemy/base_helper.rb @@ -1,10 +1,7 @@ +# frozen_string_literal: true + module Alchemy module BaseHelper - def _t(key, *args) - ActiveSupport::Deprecation.warn("Alchemys `_t` method is deprecated! Use `Alchemy.t` instead.", caller.unshift) - Alchemy.t(key, *args) - end - # An alias for truncate. # Left here for downwards compatibilty. def shorten(text, length) @@ -14,18 +11,32 @@ def shorten(text, length) # Logs a message in the Rails logger (warn level) # and optionally displays an error message to the user. def warning(message, text = nil) - Logger.warn(message, caller.first) + Logger.warn(message, caller(1..1)) unless text.nil? - warning = content_tag('p', class: 'content_editor_error') do - render_icon('warning') + text + render_message(:warning) do + text.html_safe end - return warning end end - # Returns an icon - def render_icon(icon_class) - content_tag('span', '', class: "icon #{icon_class}") + # Render a Fontawesome icon + # + # @param icon_class [String] Fontawesome icon name + # @param size: nil [String] Fontawesome icon size + # @param transform: nil [String] Fontawesome transform style + # + # @return [String] + def render_icon(icon_class, 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], + ].compact + content_tag("i", nil, class: classes) end # Returns a div with an icon and the passed content @@ -39,10 +50,11 @@ def render_icon(icon_class) # <% end %> # def render_message(type = :info, msg = nil, &blk) + icon_class = message_icon_class(type) if block_given? - content_tag :div, render_icon(type) + capture(&blk), class: "#{type} message" + content_tag :div, render_icon(icon_class) + capture(&blk), class: "#{type} message" else - content_tag :div, render_icon(type) + msg, class: "#{type} message" + content_tag :div, render_icon(icon_class) + msg, class: "#{type} message" end end @@ -52,22 +64,41 @@ 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. # If a String is given, it tries to find the page via page_layout # Logs a warning if no page is given. def page_or_find(page) + unless Language.current + warning("No default language set up") + return nil + end + if page.is_a?(String) page = Language.current.pages.find_by(page_layout: page) end if page.blank? warning("No Page found for #{page.inspect}") - return + nil else page end end + + # Returns the FontAwesome icon name for given message type + # + # @param message_type [String] The message type. One of +warning+, +info+, +notice+, +error+ + # @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" + else + message_type + end + end end end diff --git a/app/helpers/alchemy/deprecated_pages_helper.rb b/app/helpers/alchemy/deprecated_pages_helper.rb deleted file mode 100644 index 4bdbc38a723..00000000000 --- a/app/helpers/alchemy/deprecated_pages_helper.rb +++ /dev/null @@ -1,54 +0,0 @@ -module Alchemy - module DeprecatedPagesHelper - # All these helper methods are deprecated. - # They are mixed into Alchemy::PagesHelper but will be removed in the future. - - def alchemy_preview_mode_code - ActiveSupport::Deprecation.warn('PageHelper `alchemy_preview_mode_code` is deprecated and will be removed with Alchemy v4.0. Please use `render "alchemy/preview_mode_code"` in your layout instead.') - render "alchemy/preview_mode_code" - end - - def render_meta_data(options = {}) - ActiveSupport::Deprecation.warn('PageHelper `render_meta_data` is deprecated and will be removed with Alchemy v4.0. Please use `render "alchemy/pages/meta_data"` in your view instead.') - render "alchemy/pages/meta_data", options - end - - def render_page_title(options = {}) - ActiveSupport::Deprecation.warn('PageHelper `render_page_title` is deprecated and will be removed with Alchemy v4.0. Please use `render "alchemy/pages/meta_data"` in your view instead.') - return "" if @page.title.blank? - options = { - prefix: "", - separator: "" - }.update(options) - title_parts = [options[:prefix]] - if response.status == 200 - title_parts << @page.title - else - title_parts << response.status - end - title_parts.join(options[:separator]).html_safe - end - - def render_title_tag(options = {}) - ActiveSupport::Deprecation.warn('PageHelper `render_title_tag` is deprecated and will be removed with Alchemy v4.0. Please use `render "alchemy/pages/meta_data"` in your view instead.') - default_options = { - prefix: "", - separator: "" - } - options = default_options.merge(options) - content_tag(:title, render_page_title(options)) - end - - def render_meta_tag(options = {}) - ActiveSupport::Deprecation.warn('PageHelper `render_meta_tag` is deprecated and will be removed with Alchemy v4.0. Please use `tag()` instead. (http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag)') - default_options = { - name: "", - default_language: "de", - content: "" - } - options = default_options.merge(options) - lang = (@page.language.blank? ? options[:default_language] : @page.language.code) - tag(:meta, name: options[:name], content: options[:content], lang: lang) - end - end -end diff --git a/app/helpers/alchemy/elements_block_helper.rb b/app/helpers/alchemy/elements_block_helper.rb index 06617eb5099..6d5e0459c86 100644 --- a/app/helpers/alchemy/elements_block_helper.rb +++ b/app/helpers/alchemy/elements_block_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Alchemy # Provides a collection of block-level helpers, allowing for a much more # concise way of writing element view/editor partials. @@ -24,8 +26,15 @@ def element class ElementViewHelper < BlockHelper # Renders one of the element's contents. # - def render(name, *args) - helpers.render_essence_view_by_name(element, name.to_s, *args) + def render(name, options = {}, html_options = {}) + content = element.content_by_name(name) + return if content.nil? + + helpers.render(content, { + content: content, + options: options, + html_options: html_options, + }) end # Returns one of the element's contents (ie. essence instances). @@ -53,14 +62,6 @@ def essence(name) end end - # Block-level helper class for element editors. - # - class ElementEditorHelper < BlockHelper - def edit(name, *args) - helpers.render_essence_editor_by_name(element, name.to_s, *args) - end - end - # Block-level helper for element views. Constructs a DOM element wrapping # your content element and provides a block helper object you can use for # concise access to Alchemy's various helpers. @@ -106,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 @@ -130,25 +131,5 @@ def element_view_for(element, options = {}) # that's it! output end - - # Block-level helper for element editors. Provides a block helper object - # you can use for concise access to Alchemy's various helpers. - # - # === Example: - # - # <%= element_editor_for(element) do |el| %> - # <%= el.edit :title %> - # <%= el.edit :body %> - # <%= el.edit :target_url %> - # <% end %> - # - # @param [Alchemy::Element] element - # The element to display. - # - def element_editor_for(element) - capture do - yield ElementEditorHelper.new(self, element: element) if block_given? - end - end end end diff --git a/app/helpers/alchemy/elements_helper.rb b/app/helpers/alchemy/elements_helper.rb index ae0caaac620..0d3e31e247d 100644 --- a/app/helpers/alchemy/elements_helper.rb +++ b/app/helpers/alchemy/elements_helper.rb @@ -1,14 +1,15 @@ +# frozen_string_literal: true + module Alchemy # This helpers are useful to render elements from pages. # # The most important helper for frontend developers is the {#render_elements} helper. # module ElementsHelper - include Alchemy::EssencesHelper include Alchemy::UrlHelper include Alchemy::ElementsBlockHelper - # Renders all elements from current page + # Renders elements from given page # # == Examples: # @@ -27,12 +28,6 @@ module ElementsHelper # <%= render_elements from_page: 'footer' %> # # - # === Render elements from cell: - # - # - # # === Fallback to elements from global page: # # You can use the fallback option as an override for elements that are stored on another page. @@ -49,69 +44,75 @@ module ElementsHelper # with: 'contact_teaser' # }) %> # - # @param [Hash] options - # Additional options. + # === Custom elements finder: + # + # Having a custom element finder class: + # + # class MyCustomNewsArchive + # def elements(page:) + # news_page.elements.named('news').order(created_at: :desc) + # end + # + # private # + # def news_page + # Alchemy::Page.where(page_layout: 'news-archive') + # end + # end + # + # In your view: + # + #
    + # <%= render_elements finder: MyCustomNewsArchive.new %> + #
    + # + # @option options [Alchemy::Page|String] :from_page (@page) + # The page the elements are rendered from. You can pass a page_layout String or a {Alchemy::Page} object. + # @option options [Array|String] :only + # A list of element names only to be rendered. + # @option options [Array|String] :except + # A list of element names not to be rendered. # @option options [Number] :count # The amount of elements to be rendered (begins with first element found) - # @option options [Array or String] :except ([]) - # A list of element names not to be rendered. + # @option options [Number] :offset + # The offset to begin loading elements from # @option options [Hash] :fallback # Define elements that are rendered from another page. - # @option options [Alchemy::Cell or String] :from_cell - # The cell the elements are rendered from. You can pass a {Alchemy::Cell} name String or a {Alchemy::Cell} object. - # @option options [Alchemy::Page or String] :from_page (@page) - # The page the elements are rendered from. You can pass a page_layout String or a {Alchemy::Page} object. - # @option options [Array or String] :only ([]) - # A list of element names only to be rendered. - # @option options [Boolean] :random + # @option options [Boolean] :random (false) # Randomize the output of elements - # @option options [Boolean] :reverse + # @option options [Boolean] :reverse (false) # Reverse the rendering order - # @option options [String] :sort_by - # The name of a {Alchemy::Content} to sort the elements by # @option options [String] :separator - # A string that will be used to join the element partials. Default nil + # A string that will be used to join the element partials. + # @option options [Class] :finder (Alchemy::ElementsFinder) + # A class instance that will return elements that get rendered. + # Use this for your custom element loading logic in views. # def render_elements(options = {}) options = { from_page: @page, - render_format: 'html', - reverse: false + render_format: "html", }.update(options) - pages = pages_holding_elements(options.delete(:from_page)) + finder = options[:finder] || Alchemy::ElementsFinder.new(options) + elements = finder.elements(page: options[:from_page]) - if pages.blank? - warning('No page to get elements from was found') - return - end - - elements = collect_elements_from_pages(pages, options) - - if options[:sort_by].present? - elements = sort_elements_by_content( - elements, - options.delete(:sort_by), - options[:reverse] - ) + buff = [] + elements.each_with_index do |element, i| + buff << render_element(element, options, i + 1) end - - render_element_view_partials(elements, options) + buff.join(options[:separator]).html_safe end - # This helper renders a {Alchemy::Element} partial. - # - # A element has always two partials: + # This helper renders a {Alchemy::Element} view partial. # - # 1. A view partial (This is the view presented to the website visitor) - # 2. A editor partial (This is the form presented to the website editor while in page edit mode) + # A element view partial is the html snippet presented to the website visitor. # - # The partials are located in app/views/alchemy/elements. + # The partial is located in app/views/alchemy/elements. # # == View partial naming # - # The partials have to be named after the name of the element as defined in the elements.yml file and has to be suffixed with the partial part. + # The partial has to be named after the name of the element as defined in the elements.yml file. # # === Example # @@ -123,10 +124,9 @@ def render_elements(options = {}) # - name: text # type: EssenceText # - # Then your element view partials has to be named like: + # Then your element view partial has to be named like: # - # app/views/alchemy/elements/_headline_editor.html.erb - # app/views/alchemy/elements/_headline_view.html.erb + # app/views/alchemy/elements/_headline.html.{erb|haml|slim} # # === Element partials generator # @@ -136,62 +136,57 @@ def render_elements(options = {}) # # == Usage # - # <%= render_element(Alchemy::Element.published.named(:headline).first) %> + # <%= render_element(Alchemy::Element.available.named(:headline).first) %> # # @param [Alchemy::Element] element # The element you want to render the view for - # @param [Symbol] part - # The type of element partial (:editor or :view) you want to render # @param [Hash] options # Additional options # @param [Number] counter # a counter # - # @note If the view partial is not found alchemy/elements/_view_not_found.html.erb - # or alchemy/elements/_editor_not_found.html.erb gets rendered. + # @note If the view partial is not found + # alchemy/elements/_view_not_found.html.erb gets rendered. # - def render_element(element, part = :view, options = {}, counter = 1) + def render_element(element, options = {}, counter = 1) if element.nil? - warning('Element is nil') - render "alchemy/elements/#{part}_not_found", {name: 'nil'} + warning("Element is nil") + render "alchemy/elements/view_not_found", {name: "nil"} return end - options = { + element.store_page(@page) + + render element, { element: element, counter: counter, options: options, - locals: options.delete(:locals) || {} - } - - element.store_page(@page) if part.to_sym == :view - render "alchemy/elements/#{element.name}_#{part}", options + }.merge(options.delete(:locals) || {}) rescue ActionView::MissingTemplate => e warning(%( - Element #{part} partial not found for #{element.name}.\n + Element view partial not found for #{element.name}.\n #{e} )) - render "alchemy/elements/#{part}_not_found", { - name: element.name, - error: "Element #{part} partial not found.
    Use rails generate alchemy:elements to generate it." - } + render "alchemy/elements/view_not_found", name: element.name end # Returns a string for the id attribute of a html element for the given element def element_dom_id(element) return "" if element.nil? + "#{element.name}_#{element.id}".html_safe end # Renders the HTML tag attributes required for preview mode. def element_preview_code(element) - tag_options(element_preview_code_attributes(element)) + 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,7 +198,7 @@ def element_preview_code_attributes(element) # HTML tag attributes containing the element's tag information. # def element_tags(element, options = {}) - tag_options(element_tags_attributes(element, options)) + tag_builder.tag_options(element_tags_attributes(element, options)) end # Returns the element's tags information as an attribute hash. @@ -219,81 +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) } - end - - # Sort given elements by content. - # - # @param [Array] elements - The elements you want to sort - # @param [String] content_name - The name of the content you want to sort by - # @param [Boolean] reverse - Reverse the sorted elements order - # - # @return [Array] - # - def sort_elements_by_content(elements, content_name, reverse = false) - sorted_elements = elements.sort_by do |element| - content = element.content_by_name(content_name) - content ? content.ingredient.to_s : '' - end - - reverse ? sorted_elements.reverse : sorted_elements - end - - private - - def pages_holding_elements(page) - case page - when String - Language.current.pages.where( - page_layout: page, - restricted: false - ).to_a - when Page - page - end - end - def collect_elements_from_pages(page, options) - if page.is_a? Array - elements = page.collect { |p| p.find_elements(options) }.flatten - else - elements = page.find_elements(options) - end - if fallback_required?(elements, options) - elements += fallback_elements(options) - end - elements - end - - def fallback_required?(elements, options) - options[:fallback] && elements.detect { |e| e.name == options[:fallback][:for] }.nil? - end - - def fallback_elements(options) - fallback_options = options.delete(:fallback) - case fallback_options[:from] - when String - page = Language.current.pages.find_by( - page_layout: fallback_options[:from], - restricted: false - ) - when Page - page = fallback_options[:from] - end - return [] if page.blank? - page.elements.not_trashed.named(fallback_options[:with].presence || fallback_options[:for]) - end - - def render_element_view_partials(elements, options = {}) - buff = [] - elements.each_with_index do |element, i| - buff << render_element(element, :view, options, i + 1) - end - buff.join(options[:separator]).html_safe + { "data-element-tags" => options[:formatter].call(element.tag_list) } end end end diff --git a/app/helpers/alchemy/essences_helper.rb b/app/helpers/alchemy/essences_helper.rb deleted file mode 100644 index ddace18b49c..00000000000 --- a/app/helpers/alchemy/essences_helper.rb +++ /dev/null @@ -1,90 +0,0 @@ -module Alchemy - # This helper contains methods to render the +essence+ from an +Element+ +Content+. - # - # Essences have two kinds of partials. An +editor+ and a +view+ partial. - # - # They both resist in +'app/views/alchemy/essences'+ - # - # The partials are suffixed with the type of part. - # - # == Example: - # - # For an EssenceText - # - # The view partial is: - # - # +_essence_text_view.html.erb+ - # - # The editor partial is: - # - # +_essence_text_editor.html.erb+ - # - # == Usage: - # - # For front end web development you should mostly use the +render_essence_view_by_name+ helper. - # - # And the +render_essence_editor_by_name+ helper for Alchemy backend views. - # - module EssencesHelper - # Renders the +Essence+ view partial from +Element+ by name. - # - # Pass the name of the +Content+ from +Element+ as second argument. - # - # == Example: - # - # This renders the +Content+ named "intro" from element. - # - # <%= render_essence_view_by_name(element, "intro") %> - # - def render_essence_view_by_name(element, name, options = {}, html_options = {}) - if element.blank? - warning('Element is nil') - return "" - end - content = element.content_by_name(name) - render_essence_view(content, options, html_options) - end - - # Renders the +Esssence+ partial for given +Content+. - # - # The helper renders the view partial as default. - # - # Pass +:editor+ as second argument to render the editor partial - # - # == Options: - # - # You can pass a options Hash to each type of essence partial as third argument. - # - # This Hash is available as +options+ local variable. - # - # for_view: {} - # for_editor: {} - # - def render_essence(content, part = :view, options = {}, html_options = {}) - options = {for_view: {}, for_editor: {}}.update(options) - if content.nil? - return part == :view ? "" : warning('Content is nil', Alchemy.t(:content_not_found)) - elsif content.essence.nil? - return part == :view ? "" : warning('Essence is nil', Alchemy.t(:content_essence_not_found)) - end - render partial: "alchemy/essences/#{content.essence_partial_name}_#{part}", locals: { - content: content, - options: options["for_#{part}".to_sym], - html_options: html_options - } - end - - # Renders the +Esssence+ view partial for given +Content+. - # - # == Options: - # - # :size => "111x93" # Used by EssencePicture to resize the image to that size. [Default nil] - # :date_format => "Am %d. %m. %Y, um %H:%Mh" # Especially for EssenceDate. See Rubys Date.strftime for date formatting options. [Default nil] - # :show_caption => false # Pass Boolean to show/hide the caption of an EssencePicture. [Default true] - # :disable_link => true # You can surpress the link of an EssencePicture. Default false - # - def render_essence_view(content, options = {}, html_options = {}) - render_essence(content, :view, {for_view: options}, html_options) - end - end -end diff --git a/app/helpers/alchemy/pages_helper.rb b/app/helpers/alchemy/pages_helper.rb index 4bb73bfdd5b..c98ee22d4ca 100644 --- a/app/helpers/alchemy/pages_helper.rb +++ b/app/helpers/alchemy/pages_helper.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + module Alchemy module PagesHelper include Alchemy::BaseHelper include Alchemy::ElementsHelper - include Alchemy::DeprecatedPagesHelper def picture_essence_caption(content) content.try(:essence).try(:caption) @@ -24,18 +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 @@ -49,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 @@ -68,142 +70,32 @@ def render_site_layout render current_alchemy_site rescue ActionView::MissingTemplate warning("Site layout for #{current_alchemy_site.try(:name)} not found. Please run `rails g alchemy:site_layouts`") - return "" + "" end - # Renders the navigation. - # - # It produces a html
    structure with all necessary classes so you can produce every navigation the web uses today. - # I.E. dropdown-navigations, simple mainnavigations or even complex nested ones. - # - # === HTML output: - # - # - # - # As you can see: Everything you need. - # - # Not pleased with the way Alchemy produces the navigation structure? - # - # Then feel free to overwrite the partials (_renderer.html.erb and _link.html.erb) found in +views/navigation/+ or pass different partials via the options +:navigation_partial+ and +:navigation_link_partial+. - # - # === Passing HTML classes and ids to the renderer - # - # A second hash can be passed as html_options to the navigation renderer partial. - # - # ==== Example: - # - # <%= render_navigation({from_page: 'subnavi'}, {class: 'navigation', id: 'subnavigation'}) %> - # - # - # @option options submenu [Boolean] (false) - # Do you want a nested
  • -
    +
    <%= render 'filter_bar' %> diff --git a/app/views/alchemy/admin/attachments/_attachment.html.erb b/app/views/alchemy/admin/attachments/_attachment.html.erb index 935d2a8ad97..53064d629c7 100644 --- a/app/views/alchemy/admin/attachments/_attachment.html.erb +++ b/app/views/alchemy/admin/attachments/_attachment.html.erb @@ -1,6 +1,6 @@ - <%= render_icon attachment.icon_css_class %> + <%= render_icon attachment.icon_css_class, style: 'regular', size: 'lg' %> <% if can?(:show, attachment) %> @@ -22,11 +22,11 @@ <%= attachment.file_name %> <%= mime_to_human(attachment.file_mime_type) %> <%= number_to_human_size(attachment.file_size) %> - <%= l(attachment.created_at, format: :default) %> + <%= l(attachment.created_at, format: :'alchemy.default') %> <% if can?(:show, attachment) %> <%= link_to_dialog( - render_icon(:info), + render_icon('info-circle'), alchemy.admin_attachment_path(attachment), { title: attachment.name, @@ -38,13 +38,10 @@ ) %> <% end %> <% if can?(:download, attachment) %> - <%= link_to( - "", + <%= link_to render_icon(:download), alchemy.download_admin_attachment_path(attachment), title: Alchemy.t("download_file", filename: attachment.file_name), - class: "icon file_download", - target: "_blank" - ) %> + target: "_blank" %> <% end %> <% if can?(:edit, attachment) %> <%= render 'alchemy/admin/attachments/replace_button', @@ -53,31 +50,24 @@ file_attribute: 'file' %> <% end %> <% if can?(:destroy, attachment) %> - <%= link_to_confirm_dialog( - "", + <%= link_to_confirm_dialog render_icon(:minus), Alchemy.t(:confirm_to_delete_file), alchemy.admin_attachment_path( id: attachment, - q: params[:q], + q: search_filter_params[:q], page: params[:page], per_page: params[:per_page] ), - { - class: 'icon file_delete', - title: Alchemy.t(:delete_file) - } - ) %> + title: Alchemy.t(:delete_file) %> <% end %> <% if can?(:edit, attachment) %> - <%= link_to_dialog("", - alchemy.edit_admin_attachment_path(attachment, q: params[:q], page: params[:page]), + <%= link_to_dialog render_icon(:edit), + alchemy.edit_admin_attachment_path(attachment, q: search_filter_params[:q], page: params[:page]), { title: Alchemy.t(:rename_file), size: '500x250' }, - class: 'icon file_edit', - title: Alchemy.t(:rename_file) - ) %> + title: Alchemy.t(:rename_file) %> <% end %> diff --git a/app/views/alchemy/admin/attachments/_file_to_assign.html.erb b/app/views/alchemy/admin/attachments/_file_to_assign.html.erb index 76d5a71244d..3b1d3e501ff 100644 --- a/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +++ b/app/views/alchemy/admin/attachments/_file_to_assign.html.erb @@ -1,8 +1,7 @@
  • <%= link_to alchemy.assign_admin_essence_files_path( attachment_id: file_to_assign.id, - content_id: @content.id, - options: @options.to_json + content_id: @content.id ), remote: true, method: 'put' do %> diff --git a/app/views/alchemy/admin/attachments/_files_list.html.erb b/app/views/alchemy/admin/attachments/_files_list.html.erb index 9e0e466b4d0..b184d5ecd13 100644 --- a/app/views/alchemy/admin/attachments/_files_list.html.erb +++ b/app/views/alchemy/admin/attachments/_files_list.html.erb @@ -1,31 +1,28 @@ -<% if @attachments.blank? && params[:q].blank? %> -
    - <%= render_icon('info') %> - <%= Alchemy.t(:no_files_in_archive) %> -
    -<% elsif @attachments.blank? %> -
    - <%= render_icon('info') %> - <%= Alchemy.t(:no_search_results) %> -
    -<% end %> - - <% unless @attachments.blank? %> - - - - - - - - - - - +<% if @attachments.empty? %> + <%= render_message do %> + <% if search_filter_params[:q].present? %> + <%= Alchemy.t(:no_search_results) %> + <% else %> + <%= Alchemy.t(:no_files_in_archive) %> + <% end %> <% end %> - - <%= render partial: 'alchemy/admin/attachments/attachment', collection: @attachments %> - -
    <%= sort_link(@query, :name, hide_indicator: true) %><%= sort_link(@query, :file_name, hide_indicator: true) %><%= Alchemy::Attachment.human_attribute_name('file_mime_type') %><%= sort_link(@query, :file_size, hide_indicator: true) %><%= sort_link(@query, :created_at, hide_indicator: true) %>
    +<% else %> + + + + + + + + + + + + + + <%= render partial: 'alchemy/admin/attachments/attachment', collection: @attachments %> + +
    <%= sort_link(@query, :name) %><%= sort_link(@query, :file_name) %><%= Alchemy::Attachment.human_attribute_name('file_mime_type') %><%= sort_link(@query, :file_size) %><%= sort_link(@query, :created_at, default_order: 'desc') %>
    -<%= paginate @attachments, theme: 'alchemy' %> + <%= paginate @attachments, theme: 'alchemy' %> +<% end %> diff --git a/app/views/alchemy/admin/attachments/_filter_bar.html.erb b/app/views/alchemy/admin/attachments/_filter_bar.html.erb index 95dd483193e..62a29ff0a32 100644 --- a/app/views/alchemy/admin/attachments/_filter_bar.html.erb +++ b/app/views/alchemy/admin/attachments/_filter_bar.html.erb @@ -1,12 +1,10 @@ -<% params_to_keep = [:tagged_with, :q, :content_id, :options] %> -
    -

    <%= Alchemy.t('Filter') %>

    +

    <%= Alchemy.t('Filter') %>

    <%= select_tag( 'file_type_filter', options_for_select( Alchemy::Attachment.file_types_for_select, - params[:file_type] + search_filter_params[:file_type] ), include_blank: Alchemy.t('Show all files'), data: { remote: !!request.xhr? }, @@ -18,13 +16,12 @@ $(function() { $('#file_type_filter').on('change', function(e) { var $this = $(this); - var url = '<%= alchemy.admin_attachments_path(merge_params_only(params_to_keep)).html_safe %>'; + var url = '<%= alchemy.admin_attachments_path(search_filter_params.except(:file_type).to_h) %>'; if ($this.data('remote') === true) { $.get(url, {file_type: $this.val()}, null, 'script'); } else { - Alchemy.pleaseWaitOverlay(); delimiter = url.match(/\?/) ? '&' : '?'; - window.location = url + delimiter + 'file_type=' + encodeURIComponent($this.val()); + Turbolinks.visit(url + delimiter + 'file_type=' + encodeURIComponent($this.val())); } return false; }); diff --git a/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb b/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb index 7ded393227b..2a345f024d6 100644 --- a/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb +++ b/app/views/alchemy/admin/attachments/_overlay_file_list.html.erb @@ -1,12 +1,11 @@ <% if @attachments.empty? %> -
    - <%= render_icon('info') %> - <% if params[:q].present? %> + <%= render_message do %> + <% if search_filter_params[:q].present? %> <%= Alchemy.t(:no_search_results) %> <% else %> <%= Alchemy.t(:no_files_in_archive) %> <% end %> -
    + <% end %> <% else %>
    • diff --git a/app/views/alchemy/admin/attachments/_replace_button.html.erb b/app/views/alchemy/admin/attachments/_replace_button.html.erb index da60f2c2fb1..addaf6453d7 100644 --- a/app/views/alchemy/admin/attachments/_replace_button.html.erb +++ b/app/views/alchemy/admin/attachments/_replace_button.html.erb @@ -4,7 +4,7 @@ name: "#{f.object_name}[#{file_attribute}]", id: "replace_#{dom_id(object)}" %> <%= label_tag "replace_#{dom_id(object)}", title: Alchemy.t(:replace_file) do %> - <%= render_icon :file_change %> + <%= render_icon :sync %> <% end %> <% end %> @@ -17,8 +17,7 @@ selector: '#replace_<%= dom_id(object) %>', file_types: '<%= file_types.join("|") %>', complete: function() { - Alchemy.pleaseWaitOverlay(); - window.location.href = '<%= redirect_url.html_safe %>'; + Turbolinks.visit('<%= redirect_url.html_safe %>'); } }; Alchemy.Uploader(options); diff --git a/app/views/alchemy/admin/attachments/_tag_list.html.erb b/app/views/alchemy/admin/attachments/_tag_list.html.erb index ef35d7ab978..81240103f3c 100644 --- a/app/views/alchemy/admin/attachments/_tag_list.html.erb +++ b/app/views/alchemy/admin/attachments/_tag_list.html.erb @@ -1,15 +1,15 @@ <% if Alchemy::Attachment.tag_counts.any? %> -

      <%= Alchemy.t("Filter by tag") %>

      +

      <%= Alchemy.t("Filter by tag") %>

      <%= js_filter_field '.tag-list li' %>
        <%= render_tag_list('Alchemy::Attachment') %>
      - <% if params[:tagged_with].present? %> + <% if search_filter_params[:tagged_with].present? %> <%= link_to( - render_icon('delete-small') + Alchemy.t('Remove tag filter'), - url_for(tag_list_params.delete_if { |k, v| k == "tagged_with" }.merge(action: 'index')), + render_icon(:times, size: 'xs') + Alchemy.t('Remove tag filter'), + url_for(search_filter_params.except(:tagged_with)), remote: request.xhr?, - class: 'button small with_icon please_wait' + class: 'secondary button small with_icon' ) %> <% end %> <% end %> diff --git a/app/views/alchemy/admin/attachments/destroy.js.erb b/app/views/alchemy/admin/attachments/destroy.js.erb index 14bccc74056..6296a81d279 100644 --- a/app/views/alchemy/admin/attachments/destroy.js.erb +++ b/app/views/alchemy/admin/attachments/destroy.js.erb @@ -1 +1 @@ -window.location.href = '<%= @url %>'; +Turbolinks.visit('<%= @url %>'); diff --git a/app/views/alchemy/admin/attachments/edit.html.erb b/app/views/alchemy/admin/attachments/edit.html.erb index 5c449e8f932..f5eb0d1d795 100644 --- a/app/views/alchemy/admin/attachments/edit.html.erb +++ b/app/views/alchemy/admin/attachments/edit.html.erb @@ -1,8 +1,8 @@ <%= alchemy_form_for([:admin, @attachment], - url: {action: :update, q: params[:q], page: params[:page]}) do |f| -%> + url: {action: :update, q: search_filter_params[:q], page: params[:page]}) do |f| -%> <%= f.input :name, input_html: {autofocus: true} %> <%= f.input :file_name, input_html: {autofocus: true}, hint: Alchemy.t(:attachment_filename_notice) %> -
      +
      <%= f.label :tag_list %> <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %>
      diff --git a/app/views/alchemy/admin/attachments/index.html.erb b/app/views/alchemy/admin/attachments/index.html.erb index df3f2be4470..2a9bced6a83 100644 --- a/app/views/alchemy/admin/attachments/index.html.erb +++ b/app/views/alchemy/admin/attachments/index.html.erb @@ -7,18 +7,19 @@ file_attribute: 'file' %> <% end %>
      - <%= render 'alchemy/admin/partials/search_form' %> + <%= render 'alchemy/admin/partials/search_form', + additional_params: [:file_type, :tagged_with] %> <% end %>
      - <%= resources_header %> + <%= render 'alchemy/admin/resources/table_header' %> <%= render 'files_list' %>
      <%= render 'filter_bar' %> <% if Alchemy::Attachment.tag_counts.any? %> -
      +
      <%= render 'tag_list' %>
      <% end %> diff --git a/app/views/alchemy/admin/attachments/show.html.erb b/app/views/alchemy/admin/attachments/show.html.erb index 776c3b648e4..77e7a3f0278 100644 --- a/app/views/alchemy/admin/attachments/show.html.erb +++ b/app/views/alchemy/admin/attachments/show.html.erb @@ -1,42 +1,41 @@
      <% case @attachment.icon_css_class %> -<% when "image" %> +<% when "file-image" %>
      - <%= image_tag(alchemy.show_attachment_path(@attachment), class: "full_width") %> + <%= image_tag(@attachment.url, class: "full_width") %>
      -<% when "audio" %> +<% when "file-audio" %>
      - <%= audio_tag(alchemy.show_attachment_path(@attachment), preload: "none", controls: true, class: "full_width") %> + <%= audio_tag(@attachment.url, preload: "none", controls: true, class: "full_width") %>
      -<% when "video" %> +<% when "file-video" %>
      - <%= video_tag(alchemy.show_attachment_path(@attachment), preload: "metadata", controls: true, class: "full_width") %> + <%= video_tag(@attachment.url, preload: "metadata", controls: true, class: "full_width") %>
      -<% when "archive", "file" %> -<% else %> - <% end %> diff --git a/app/views/alchemy/admin/clipboard/clear.js.erb b/app/views/alchemy/admin/clipboard/clear.js.erb index a7ccdf8eca4..8e94bc8b83e 100644 --- a/app/views/alchemy/admin/clipboard/clear.js.erb +++ b/app/views/alchemy/admin/clipboard/clear.js.erb @@ -1,3 +1,3 @@ $("#clipboard_items").replaceWith("<%= j render_message { Alchemy.t('No items in your clipboard') } -%>"); -$('#clipboard_button .icon.clipboard').removeClass('full'); +$('#clipboard_button .icon').removeClass('fa-paste').addClass('fa-clipboard'); Alchemy.pleaseWaitOverlay(false); diff --git a/app/views/alchemy/admin/clipboard/index.html.erb b/app/views/alchemy/admin/clipboard/index.html.erb index b9481fbfda9..970aab60bd2 100644 --- a/app/views/alchemy/admin/clipboard/index.html.erb +++ b/app/views/alchemy/admin/clipboard/index.html.erb @@ -1,22 +1,38 @@ <%- if @clipboard_items.blank? -%> -
      - <%= render_icon('info') %> +<%= render_message do %> <%= Alchemy.t('No items in your clipboard') %> -
      +<% end %> <%- else -%>
        <%- @clipboard_items.each do |item| -%> <% item_class = item.class.name.demodulize.underscore.pluralize %>
      • - <%= render_icon("clipboard_#{item_class}") %> - <%= item.class.to_s == 'Alchemy::Element' ? item.display_name_with_preview_text(60) : item.name %> - <%= link_to(render_icon('delete-small'), alchemy.remove_admin_clipboard_path(remarkable_type: item_class, remarkable_id: item.id), remote: true, method: 'delete', title: Alchemy.t('Remove item from clipboard')) %> + <% if item_class == 'pages' %> + <%= render_icon(:file, style: 'regular') %> + <%= truncate(item.name, length: 50) %> + <% else %> + <% if item.public? %> + <%= render_icon('window-maximize', style: 'regular', class: 'element') %> + <% else %> + <%= render_icon('window-close', class: 'element') %> + <% end %> + <%= truncate(item.display_name_with_preview_text(50), length: 50) %> + <% end %> + + <%= link_to render_icon(:times, size: 'xs'), + alchemy.remove_admin_clipboard_path(remarkable_type: item_class, remarkable_id: item.id), + remote: true, method: 'delete', + title: Alchemy.t('Remove item from clipboard') %> +
      • <%- end -%>

      - <%= link_to_confirm_dialog(Alchemy.t('clear clipboard'), Alchemy.t('Do you really want to clear the clipboard?'), alchemy.clear_admin_clipboard_path(remarkable_type: params[:remarkable_type]), class: 'button') %> + <%= link_to_confirm_dialog Alchemy.t('clear clipboard'), + Alchemy.t('Do you really want to clear the clipboard?'), + alchemy.clear_admin_clipboard_path(remarkable_type: params[:remarkable_type]), + class: 'button' %>

      <%- end -%> diff --git a/app/views/alchemy/admin/clipboard/insert.js.erb b/app/views/alchemy/admin/clipboard/insert.js.erb index d8d186693a3..900d1b54272 100644 --- a/app/views/alchemy/admin/clipboard/insert.js.erb +++ b/app/views/alchemy/admin/clipboard/insert.js.erb @@ -1,19 +1,16 @@ <% if params[:remove] -%> + $(".element-editor[data-element-id='<%= @item.id %>']").remove(); -$(".element-editor[data-element-id='<%= @item.id %>']").remove(); + <% if @item.class.name == 'Alchemy::Element' -%> + $('#element_area .sortable-elements').sortable('refresh'); -<% if @item.class.to_s == 'Element' -%> - -$('#element_area .sortable_cell').sortable('refresh'); - -<% end -%> - -Alchemy.growl('<%= j Alchemy.t("item moved to clipboard", name: @item.class.to_s == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>'); + <% if @item.fixed? -%> + Alchemy.FixedElements.removeTab(<%= @item.id %>); + <% end -%> + <% end -%> + Alchemy.growl('<%= j Alchemy.t("item moved to clipboard", name: @item.class.name == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>'); <% else -%> - -Alchemy.growl('<%= j Alchemy.t("item copied to clipboard", name: @item.class.to_s == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>') - + Alchemy.growl('<%= j Alchemy.t("item copied to clipboard", name: @item.class.name == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>') <% end -%> - -$('#clipboard_button .icon.clipboard').addClass('full'); +$('#clipboard_button .icon').removeClass('fa-clipboard').addClass('fa-paste'); diff --git a/app/views/alchemy/admin/clipboard/remove.js.erb b/app/views/alchemy/admin/clipboard/remove.js.erb index fa29b8c2317..0e42470a846 100644 --- a/app/views/alchemy/admin/clipboard/remove.js.erb +++ b/app/views/alchemy/admin/clipboard/remove.js.erb @@ -3,8 +3,8 @@ $("#clipboard_item_<%= @item.id -%>").remove(); Alchemy.growl('<%= Alchemy.t("item removed from clipboard", :name => @item.class.to_s == "Element" ? @item.display_name_with_preview_text : @item.name) -%>'); <%- if @clipboard.blank? -%> - $('#clipboard_button .icon.clipboard').removeClass('full'); - $("#clipboard_items").replaceWith("

      <%= Alchemy.t('No items in your clipboard') -%>

      "); + $('#clipboard_button .icon').removeClass('fa-paste').addClass('fa-clipboard'); + $("#clipboard_items").replaceWith("<%= j render_message { Alchemy.t('No items in your clipboard') } %>"); <%- end -%> })(jQuery); diff --git a/app/views/alchemy/admin/contents/_missing.html.erb b/app/views/alchemy/admin/contents/_missing.html.erb deleted file mode 100644 index 03e0af6e48c..00000000000 --- a/app/views/alchemy/admin/contents/_missing.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
      -missing-content="<%= name %>"> - - <%= render_message :warning do %> -

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

      - <%= link_to Alchemy.t(:create), - alchemy.admin_contents_path( - content: { - element_id: element.id, - name: name - }, - was_missing: true, - options: options.to_json - ), - 'data-create-missing-content' => true, - class: 'small button' %> - <% end %> -
      diff --git a/app/views/alchemy/admin/contents/create.js.erb b/app/views/alchemy/admin/contents/create.js.erb index b81a67403ad..b99a1e4a82b 100644 --- a/app/views/alchemy/admin/contents/create.js.erb +++ b/app/views/alchemy/admin/contents/create.js.erb @@ -1,48 +1,14 @@ -<% if params[:was_missing] %> - -$("[data-element-<%= @element.id %>-missing-content=\"<%= @content.name %>\"]").replaceWith('<%= j( - render( - partial: "alchemy/essences/#{@content.essence_partial_name}_editor", - locals: @locals - ) -) %>'); - -<% else %> - -$("<%= @content_dom_id %>").before('<%= j( - render( - partial: "alchemy/essences/#{@content.essence_partial_name}_editor", - locals: @locals - ) -) %>'); -Alchemy.Buttons.enable(); -Alchemy.growl('<%= j Alchemy.t("Successfully added content") % {content: @content.name_for_label} %>') +var editor_html = '<%= j render(Alchemy::ContentEditor.new(@content)) %>'; -<% end %> +$("[data-element-<%= @content.element_id %>-missing-content=\"<%= @content.name %>\"]").replaceWith(editor_html); -<% if @content.essence_type == "Alchemy::EssencePicture" %> +<% if @content.essence_type == "Alchemy::EssencePicture" && @content.ingredient %> -<% if @content.ingredient %> $('#picture_to_assign_<%= @content.ingredient.id %> a').attr('href', '#').off('click'); -<% end %> - -<% if @gallery_pictures %> - -<% if @gallery_pictures.size > 1 %> -$('#element_<%= @element.id %>_contents .essence_picture_editor').addClass('dragable_picture'); -<% end %> - -<% if !max_image_count.blank? && @gallery_pictures.size >= max_image_count %> -$("#add_picture_<%= @element.id %>").remove(); -<% end %> - -Alchemy.SortableContents('#element_<%= @element.id %>_contents', '<%= form_authenticity_token %>'); - -<% end %> <% elsif @content.essence_type == "Alchemy::EssenceDate" %> -Alchemy.Datepicker('#element_<%= @element.id %>'); +Alchemy.Datepicker('#element_<%= @content.element_id %>'); <% elsif @content.essence_type == "Alchemy::EssenceRichtext" %> @@ -52,4 +18,4 @@ Alchemy.Tinymce.initEditor(<%= @content.id %>); Alchemy.reloadPreview(); Alchemy.closeCurrentDialog(); -Alchemy.SelectBox("#element_<%= @element.id %>"); +Alchemy.SelectBox("#element_<%= @content.element_id %>"); diff --git a/app/views/alchemy/admin/contents/destroy.js.erb b/app/views/alchemy/admin/contents/destroy.js.erb deleted file mode 100644 index caa71ae4b29..00000000000 --- a/app/views/alchemy/admin/contents/destroy.js.erb +++ /dev/null @@ -1,4 +0,0 @@ -$('#<%= @content_dom_id %>').remove(); -Alchemy.growl('<%= j @notice %>'); -Alchemy.reloadPreview(); -Alchemy.pleaseWaitOverlay(false); diff --git a/app/views/alchemy/admin/contents/new.html.erb b/app/views/alchemy/admin/contents/new.html.erb deleted file mode 100644 index feace17f280..00000000000 --- a/app/views/alchemy/admin/contents/new.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -<%= alchemy_form_for [:admin, @content] do |f| %> - <%= f.hidden_field :element_id %> - <%= hidden_field_tag('options', @options.to_json) %> - <%= f.input :name, collection: @contents.map { |content| - [ - Alchemy.t(content['name'], scope: 'content_names', default: content['name'].humanize), - content['name'] - ] - }, include_blank: false, input_html: {class: 'alchemy_selectbox'} %> - <%= f.submit Alchemy.t(:create) %> -<% end %> diff --git a/app/views/alchemy/admin/contents/order.js.erb b/app/views/alchemy/admin/contents/order.js.erb deleted file mode 100644 index e4e8f117ba1..00000000000 --- a/app/views/alchemy/admin/contents/order.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -Alchemy.growl('<%= j @notice %>'); -Alchemy.SortableContents('#element_area .picture_gallery_images', '<%= form_authenticity_token %>'); -Alchemy.reloadPreview(); diff --git a/app/views/alchemy/admin/dashboard/_locked_pages.html.erb b/app/views/alchemy/admin/dashboard/_locked_pages.html.erb index 20a5f5e0fd7..8e1d0c4a018 100644 --- a/app/views/alchemy/admin/dashboard/_locked_pages.html.erb +++ b/app/views/alchemy/admin/dashboard/_locked_pages.html.erb @@ -1,13 +1,16 @@ +<% colspan = multi_site? ? 5 : 4 %>
      -

      <%= Alchemy.t('Currently locked pages') %>:

      - +
      + + + <% if @all_locked_pages.blank? %> - - + + <% else %> <% @all_locked_pages.each do |page| %> - + diff --git a/app/views/alchemy/admin/tags/edit.html.erb b/app/views/alchemy/admin/tags/edit.html.erb index d943cbb5806..fdbb921c6d8 100644 --- a/app/views/alchemy/admin/tags/edit.html.erb +++ b/app/views/alchemy/admin/tags/edit.html.erb @@ -1,15 +1,15 @@ <%= alchemy_form_for @tag, as: 'tag', url: alchemy.admin_tag_path(@tag), html: {method: 'patch'} do |f| %> - <%= render_message :info do %> +

      <%= Alchemy.t(:you_can_rename_this_tag) %> - <% end %> +

      <%= f.input :name %> <%= f.submit Alchemy.t(:rename) %> <% end %> <% if @tags.any? -%> <%= alchemy_form_for @tag, as: 'tag', url: alchemy.admin_tag_path(@tag), html: {method: 'patch'} do |f| %> - <%= render_message :info do %> +

      <%= Alchemy.t(:or_replace_it_with_an_existing_tag) %> - <% end %> +

      diff --git a/app/views/alchemy/admin/tags/index.html.erb b/app/views/alchemy/admin/tags/index.html.erb index 13961c08ed8..b7542c5d2ec 100644 --- a/app/views/alchemy/admin/tags/index.html.erb +++ b/app/views/alchemy/admin/tags/index.html.erb @@ -1,34 +1,30 @@ -<%= toolbar( - buttons: [ - { - icon: 'tag_add', - label: Alchemy.t('New Tag'), - url: alchemy.new_admin_tag_path, +<% content_for :toolbar do %> + <%= toolbar_button( + icon: :plus, + label: Alchemy.t('New Tag'), + url: alchemy.new_admin_tag_path, + title: Alchemy.t('New Tag'), + hotkey: 'alt+n', + dialog_options: { title: Alchemy.t('New Tag'), - hotkey: 'alt+n', - dialog_options: { - title: Alchemy.t('New Tag'), - size: '310x180' - }, - if_permitted_to: [:create, Alchemy::Tag] - } - ] -) %> + size: '310x180' + }, + if_permitted_to: [:create, Alchemy::Tag] + ) %> + <%= render 'alchemy/admin/partials/search_form' %> +<% end %>
      -

      - <%= @tags.total_count %> - <%= ActsAsTaggableOn::Tag.model_name.human(count: @tags.total_count) %> -

      + <%= render 'alchemy/admin/resources/table_header' %> <% if @tags.any? %>
      <%= Alchemy.t('Currently locked pages') %>
      <%= Alchemy.t('no pages') %>
      <%= Alchemy.t('no pages') %>
      <% if current_alchemy_user.id == page.locked_by %> <%= link_to(page.name, alchemy.edit_admin_page_path(page)) %> @@ -36,7 +39,7 @@ <% if current_alchemy_user.id == page.locked_by %> <%= form_tag(alchemy.unlock_admin_page_path(page, :redirect_to => alchemy.admin_dashboard_url)) do %> <% end %> <% end %> diff --git a/app/views/alchemy/admin/dashboard/_recent_pages.html.erb b/app/views/alchemy/admin/dashboard/_recent_pages.html.erb index 940b5de8646..d865eb7540b 100644 --- a/app/views/alchemy/admin/dashboard/_recent_pages.html.erb +++ b/app/views/alchemy/admin/dashboard/_recent_pages.html.erb @@ -1,9 +1,11 @@
      -

      <%= Alchemy.t('Your last updated pages') %>:

      - +
      + + + <% if @last_edited_pages.blank? %> - + <% else %> <% @last_edited_pages.each do |page| %> @@ -22,7 +24,7 @@ <% end %> <% end %> diff --git a/app/views/alchemy/admin/dashboard/_sites.html.erb b/app/views/alchemy/admin/dashboard/_sites.html.erb index dcd794e3615..ad3e7fcd748 100644 --- a/app/views/alchemy/admin/dashboard/_sites.html.erb +++ b/app/views/alchemy/admin/dashboard/_sites.html.erb @@ -1,6 +1,8 @@
      -

      <%= Alchemy::Site.model_name.human(count: 2) %>:

      -
      <%= Alchemy.t('Your last updated pages') %>
      <%= Alchemy.t('no pages') %><%= Alchemy.t('no pages') %>
      - <%= l(page.updated_at, format: :short) %> + <%= l(page.updated_at, format: :'alchemy.short_datetime') %>
      +
      + + + <% @sites.each do |site| %> -<% resource_handler.attributes.each do |attribute| %> +<% resource_handler.sorted_attributes.each do |attribute| %> <% end %> diff --git a/app/views/alchemy/admin/resources/_table.html.erb b/app/views/alchemy/admin/resources/_table.html.erb index d35b2521746..47d65de87ce 100644 --- a/app/views/alchemy/admin/resources/_table.html.erb +++ b/app/views/alchemy/admin/resources/_table.html.erb @@ -2,12 +2,12 @@
      <%= Alchemy::Site.model_name.human(count: 2) %>
      diff --git a/app/views/alchemy/admin/dashboard/_users.html.erb b/app/views/alchemy/admin/dashboard/_users.html.erb index b7e4e840746..7e92d6df86f 100644 --- a/app/views/alchemy/admin/dashboard/_users.html.erb +++ b/app/views/alchemy/admin/dashboard/_users.html.erb @@ -1,9 +1,11 @@ -
      -

      <%= Alchemy.t('Who else is online') %>:

      - +
      +
      + + + <% if @online_users.blank? %> - - + + <% else %> <% @online_users.each do |user| %> diff --git a/app/views/alchemy/admin/dashboard/index.html.erb b/app/views/alchemy/admin/dashboard/index.html.erb index 758c17b8238..7fdca1e35ba 100644 --- a/app/views/alchemy/admin/dashboard/index.html.erb +++ b/app/views/alchemy/admin/dashboard/index.html.erb @@ -1,20 +1,17 @@ -<%= toolbar( - buttons: [ - { - icon: 'info', - label: Alchemy.t(:info), - url: alchemy.dashboard_info_path, +<%= content_for :toolbar do %> + <%= toolbar_button( + icon: 'info-circle', + label: Alchemy.t(:info), + url: alchemy.dashboard_info_path, + title: Alchemy.t(:info), + dialog_options: { title: Alchemy.t(:info), - dialog_options: { - title: Alchemy.t(:info), - size: "420x380" - }, - if_permitted_to: [:info, :alchemy_admin_dashboard], - hotkey: 'alt+i' - } - ], - search: false -) %> + size: "420x435" + }, + if_permitted_to: [:info, :alchemy_admin_dashboard], + hotkey: 'alt+i' + ) %> +<% end %>

      @@ -26,7 +23,7 @@

      <% if @last_sign_at %>

      - <%= Alchemy.t('Your last login was on', time: l(@last_sign_at)) %> + <%= Alchemy.t('Your last login was on', time: l(@last_sign_at, format: :'alchemy.default')) %>

      <% end %>
      @@ -34,9 +31,10 @@ <%= render 'recent_pages' %>
      - <% if @online_users %> + <% if @online_users.try(:any?) %> <%= render 'users' %> <% end %> + <% if multi_site? %> <%= render 'sites' %> <% end %> diff --git a/app/views/alchemy/admin/dashboard/info.html.erb b/app/views/alchemy/admin/dashboard/info.html.erb index fe608316d1d..a98f0426117 100644 --- a/app/views/alchemy/admin/dashboard/info.html.erb +++ b/app/views/alchemy/admin/dashboard/info.html.erb @@ -1,25 +1,45 @@ -

      <%= image_tag('alchemy/alchemy-logo.svg') %>

      +

      + <%= image_tag('alchemy/alchemy-logo.svg', width: 267, height: 91) %> +

      - <%= Alchemy.t("Version") %>: <%= @alchemy_version %> + v<%= @alchemy_version %>
      + + Ruby v<%= RUBY_VERSION %>, Rails v<%= Rails.version %> +

      <% if can? :update_check, :alchemy_admin_dashboard %>

      - <%= render_icon('warning') %> + <%= render_icon(:exclamation) %> <%= Alchemy.t 'Update available' %> - <%= render_icon('ok') %> + <%= render_icon(:check) %> <%= Alchemy.t 'Alchemy is up to date' %> - <%= render_icon('warning') %> + <%= render_icon(:exclamation) %> <%= Alchemy.t 'Update status unavailable' %>

      + <% end %> -
      - <%= render_icon('info') %> +<%= render_message do %>

      <%= Alchemy.t('Alchemy is open software and itself uses open software and free resources:') %>

      -
      -<% if can? :update_check, :alchemy_admin_dashboard %> - <% end %> diff --git a/app/views/alchemy/admin/elements/_add_picture.html.erb b/app/views/alchemy/admin/elements/_add_picture.html.erb deleted file mode 100644 index a65049031ff..00000000000 --- a/app/views/alchemy/admin/elements/_add_picture.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -
      - <%= link_to_dialog( - render_icon('add'), - alchemy.admin_pictures_path( - element_id: element, - options: options - ), - { - title: Alchemy.t(:add_image_to_element), - size: '780x580', - padding: false - } - ) %> -
      diff --git a/app/views/alchemy/admin/elements/_element.html.erb b/app/views/alchemy/admin/elements/_element.html.erb index cc43dbd77d3..5bda07aa3af 100644 --- a/app/views/alchemy/admin/elements/_element.html.erb +++ b/app/views/alchemy/admin/elements/_element.html.erb @@ -1,57 +1,79 @@ <%= content_tag :div, id: "element_#{element.id}", data: {'element-id' => element.id, 'element-name' => element.name}, - class: element_editor_classes(element, local_assigns) do %> + class: element.css_classes do %> - <%= render 'alchemy/admin/elements/element_header', element: element %> + <% unless element.fixed? %> + <%= render 'alchemy/admin/elements/element_header', element: element %> + <% end %> - <% if element.expanded? %> + <% if element.expanded? || element.fixed? %> <%= render 'alchemy/admin/elements/element_toolbar', element: element %> - <%= form_for [alchemy, :admin, element], remote: true, - html: {id: "element_#{element.id}_form".html_safe, class: 'element-content'} do |f| %> + <% element.definition[:message].tap do |message| %> + <%= render_message(:info, sanitize(message)) if message %> + <% end %> + + <% element.definition[:warning].tap do |warning| %> + <%= render_message(:warning, sanitize(warning)) if warning %> + <% end %> -
      + <% if element.editable? %> + <%= form_for [alchemy, :admin, element], remote: true, + html: {id: "element_#{element.id}_form".html_safe, class: 'element-content'} do |f| %> -
      - <%== render_editor(element) %> -
      +
      - <% if element.taggable? %> -
      - <%= f.label :tag_list %> - <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> +
      + <%= render element.contents.map { |content| Alchemy::ContentEditor.new(content) } %>
      + + <% if element.taggable? %> +
      + <%= f.label :tag_list %> + <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> +
      + <% end %> <% end %> + + <%= render 'alchemy/admin/elements/element_footer', element: element %> <% end %> <% end %> <% if element.nestable_elements.any? %> - <% if show_element_footer?(element, :with_nestable_elements) %> - <%= render 'alchemy/admin/elements/element_footer', element: element %> - <% end %>
      <%= content_tag :div, - class: "nested-elements", data: { - 'droppable-elements' => element.nestable_elements.join(' ') - } do %> - <%= render partial: 'alchemy/admin/elements/element', collection: element.nested_elements %> + class: "nested-elements", data: { + 'droppable-elements' => element.nestable_elements.join(' ') + } do %> + <%= render element.all_nested_elements.map { |element| + Alchemy::ElementEditor.new(element) + } %> <% end %> - <% if element.expanded? %> - <%= link_to_dialog render_icon(:create) + Alchemy.t("New Element"), - alchemy.new_admin_element_path( - parent_element_id: element.id, - page_id: element.page.id - ), { - size: "320x125", - title: Alchemy.t("New Element") - }, class: "button with_icon add-nestable-element-button" %> + <% if element.expanded? || element.fixed? %> + <% if element.nestable_elements.length == 1 %> + <% nestable_element = element.nestable_elements.first %> + <%= form_for [:admin, Alchemy::Element.new(name: nestable_element)], + remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %> + <%= f.hidden_field :name %> + <%= f.hidden_field :page_id, value: element.page_id %> + <%= f.hidden_field :parent_element_id, value: element.id %> + + <% end %> + <% else %> + <%= link_to_dialog Alchemy.t("New Element"), + alchemy.new_admin_element_path( + parent_element_id: element.id, + page_id: element.page.id + ), { + size: "320x125", + title: Alchemy.t("New Element") + }, class: "button add-nestable-element-button" %> + <% end %> <% end %>
      <% end %> - - <% if show_element_footer?(element) %> - <%= render 'alchemy/admin/elements/element_footer', element: element %> - <% end %> <% end %> diff --git a/app/views/alchemy/admin/elements/_element_header.html.erb b/app/views/alchemy/admin/elements/_element_header.html.erb index 72681ca20c0..e8f4a0f61b8 100644 --- a/app/views/alchemy/admin/elements/_element_header.html.erb +++ b/app/views/alchemy/admin/elements/_element_header.html.erb @@ -1,13 +1,13 @@
      - <%= render_icon(:element) %> <% if element.definition.blank? %> - <%= hint_with_tooltip( - Alchemy.t(:element_definition_missing), - class: 'warning icon' - ) %> + <%= hint_with_tooltip Alchemy.t(:element_definition_missing) %> <% else %> - <%= render_icon("element_#{element.public? ? 'public' : 'draft'}") %> + <% if element.public? %> + <%= render_icon('window-maximize', style: 'regular', class: 'element') %> + <% else %> + <%= render_icon('window-close', class: 'element') %> + <% end %> <% end %> @@ -17,12 +17,12 @@ <%= render_hint_for(element) unless element.trashed? %> - > - <%= link_to '', '#', { - 'data-element-toggle' => element.id, - class: element.folded? ? 'expand_element' : 'fold_element', - title: element.folded? ? Alchemy.t(:show_element_content) : Alchemy.t(:hide_element_content), - id: "element_#{element.id}_folder" - } %> - + <%= link_to '#', { + 'data-element-toggle' => element.id, + title: Alchemy.t(element.folded? ? :show_element_content : :hide_element_content), + id: "element_#{element.id}_folder", + class: "ajax-folder" + } do %> + <%= render_icon element.folded? ? 'plus-square' : 'minus-square' %> + <% end %>
      diff --git a/app/views/alchemy/admin/elements/_element_toolbar.html.erb b/app/views/alchemy/admin/elements/_element_toolbar.html.erb index 1caff80ba38..c139d246fab 100644 --- a/app/views/alchemy/admin/elements/_element_toolbar.html.erb +++ b/app/views/alchemy/admin/elements/_element_toolbar.html.erb @@ -3,7 +3,7 @@
      <%= link_to( - render_icon(:element_copy), + render_icon(:clone), alchemy.insert_admin_clipboard_path(remarkable_type: remarkable_type, remarkable_id: element.id), remote: true, method: :post, @@ -23,7 +23,7 @@
      <%= link_to( - render_icon(:remove), + render_icon('trash-alt'), alchemy.trash_admin_element_path(element), method: :delete, class: "icon_button", @@ -33,7 +33,7 @@
      <%= link_to( - render_icon('element-hide'), + render_icon(element.public? ? 'eye-slash' : 'eye'), alchemy.publish_admin_element_path(id: element.id), remote: true, method: :patch, diff --git a/app/views/alchemy/admin/elements/_new_element_form.html.erb b/app/views/alchemy/admin/elements/_new_element_form.html.erb index 7d641db4f7e..cca521f0679 100644 --- a/app/views/alchemy/admin/elements/_new_element_form.html.erb +++ b/app/views/alchemy/admin/elements/_new_element_form.html.erb @@ -1,26 +1,15 @@ <%- if @elements.blank? -%> -
      - <%= render_icon('info') %> + <%= render_message do %> <%= Alchemy.t(:no_more_elements_to_add) %> -
      + <% end %> <%- else -%> <%= alchemy_form_for [:admin, @element] do |form| %> <%= form.hidden_field :page_id %> - <%- if @page.can_have_cells? && @parent_element.blank? -%> - <%= form.input :name, - label: Alchemy.t(:element_of_type), - collection: grouped_elements_for_select(@elements), - as: :grouped_select, - group_method: :second, - prompt: Alchemy.t(:select_element), - input_html: {class: 'alchemy_selectbox', autofocus: true} %> - <%- else -%> <%= form.input :name, label: Alchemy.t(:element_of_type), collection: elements_for_select(@elements), prompt: Alchemy.t(:select_element), input_html: {class: 'alchemy_selectbox', autofocus: true} %> - <%- end -%> <%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %> <%= form.submit Alchemy.t(:add) %> <%- end -%> diff --git a/app/views/alchemy/admin/elements/_picture_gallery_editor.html.erb b/app/views/alchemy/admin/elements/_picture_gallery_editor.html.erb deleted file mode 100644 index 55beec3e460..00000000000 --- a/app/views/alchemy/admin/elements/_picture_gallery_editor.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -<%- max_image_count = options[:maximum_amount_of_images] || options[:max_images] -%> - -<%- if pictures.size > 1 -%> - -<%- end -%> diff --git a/app/views/alchemy/admin/elements/create.js.erb b/app/views/alchemy/admin/elements/create.js.erb index b0165a14223..53a5e31bb13 100644 --- a/app/views/alchemy/admin/elements/create.js.erb +++ b/app/views/alchemy/admin/elements/create.js.erb @@ -1,24 +1,24 @@ (function() { var $el; var $element_area; - var element_html = '<%= j render("element", element: @element, draggable: true) %>'; + var element_html = '<%= j render(Alchemy::ElementEditor.new(@element)) %>'; -<%- if @cutted_element_id -%> - $('.element-editor[data-element-id="<%= @cutted_element_id %>"]').remove(); +<%- if @cut_element_id -%> + $('.element-editor[data-element-id="<%= @cut_element_id %>"]').remove(); <%- end -%> -<%- if @page.can_have_cells? -%> - if ($('#cells').length == 0) { - Alchemy.buildTabbedCells('<%= Alchemy.t(:main_content) %>'); +<% if @element.fixed? %> + if ($('#fixed-elements').length == 0) { + Alchemy.FixedElements.buildTabs('<%= Alchemy.t(:main_content) %>'); } - Alchemy.selectOrCreateCellTab('<%= @cell_name %>', '<%= @cell.nil? ? Alchemy.t(:main_content) : @cell.name_for_label %>'); -<%- end -%> - -<%- if @element.parent_element -%> + Alchemy.FixedElements.createTab('<%= @element.id %>', '<%= @element.display_name %>'); + $element_area = $('#fixed-element-<%= @element.id %>'); +<% elsif @element.parent_element %> $element_area = $('#element_<%= @element.parent_element_id %> > .nestable-elements > .nested-elements'); -<%- else -%> - $element_area = $('#cell_<%= @cell_name %>'); -<%- end -%> + Alchemy.Buttons.enable('.nestable-elements'); +<% else %> + $element_area = $('#main-content-elements'); +<% end %> <%- if @insert_at_top -%> $element_area.prepend(element_html); @@ -36,7 +36,7 @@ Alchemy.closeCurrentDialog(); Alchemy.Tinymce.init(<%= @element.richtext_contents_ids.to_json %>); Alchemy.PreviewWindow.refresh(function() { - Alchemy.ElementEditors.selectElementInPreview(<%= @element.id %>); + Alchemy.ElementEditors.focusElementPreview(<%= @element.id %>); }); $el = $('#element_<%= @element.id %>'); diff --git a/app/views/alchemy/admin/elements/fold.js.erb b/app/views/alchemy/admin/elements/fold.js.erb index 853c5ef9847..f2bbee325a7 100644 --- a/app/views/alchemy/admin/elements/fold.js.erb +++ b/app/views/alchemy/admin/elements/fold.js.erb @@ -8,9 +8,9 @@ <% else -%> - $el.replaceWith('<%= j render(partial: "element", object: @element) %>'); + $el.replaceWith('<%= j render(Alchemy::ElementEditor.new(@element)) %>'); $el = $('#element_<%= @element.id %>'); - $('#element_area .sortable_cell').sortable('refresh'); + $('#element_area .sortable-elements').sortable('refresh'); <% if @element.folded? -%> diff --git a/app/views/alchemy/admin/elements/index.html.erb b/app/views/alchemy/admin/elements/index.html.erb index fbdc2c38a0f..233cc47fa5d 100644 --- a/app/views/alchemy/admin/elements/index.html.erb +++ b/app/views/alchemy/admin/elements/index.html.erb @@ -1,30 +1,33 @@ -<% if @cells.any? %> -
      +<% if @fixed_elements.any? %> +
      -
      - <%= render partial: 'alchemy/admin/elements/element', - collection: @page.elements.not_trashed.not_in_cell %> +
      + <%= render @elements.map { |element| Alchemy::ElementEditor.new(element) } %>
      - <% @elements.each do |cell, elements| -%> - <%= content_tag :div, - id: "cell_#{cell.name}", - class: ["sortable_cell", "#{cell.name}_cell"].join(' '), - data: {'cell-id' => cell.id, 'data-droppable-elements' => cell.element_definitions.join(' ')} do %> - <%= render partial: 'alchemy/admin/elements/element', collection: elements %> - <% end %> + <% @fixed_elements.each do |element| %> +
      + <%= render Alchemy::ElementEditor.new(element) %> +
      <% end %>
      <% else %> -
      - <%= render partial: 'alchemy/admin/elements/element', collection: @elements %> +
      + <%= render @elements.map { |element| Alchemy::ElementEditor.new(element) } %>
      <% end %> diff --git a/app/views/alchemy/admin/elements/list.html.erb b/app/views/alchemy/admin/elements/list.html.erb deleted file mode 100644 index f93cd5acfe2..00000000000 --- a/app/views/alchemy/admin/elements/list.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -<%= render_message do %> -

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

      -<% end %> -<%= form_tag '' do %> - <%= select_tag "element_from_page_#{@page_id}_to_link", - options_for_select( - @elements.collect { |m| [m.display_name_with_preview_text.html_safe, "##{m.dom_id}"] } - ), - prompt: Alchemy.t(:choose_element_to_link), - class: 'elements_from_page_selector alchemy_selectbox', - autofocus: true - %> -
      - <%= button_tag Alchemy.t(:apply) %> -
      -<% end %> diff --git a/app/views/alchemy/admin/elements/new.html.erb b/app/views/alchemy/admin/elements/new.html.erb index 68fe74cdcee..0d9a34af9f3 100644 --- a/app/views/alchemy/admin/elements/new.html.erb +++ b/app/views/alchemy/admin/elements/new.html.erb @@ -11,7 +11,8 @@
      <%= alchemy_form_for([:admin, @element]) do |f| %> - <%= f.hidden_field(:page_id) %> + <%= f.hidden_field :page_id %> + <%= f.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
      <%= select_tag 'paste_from_clipboard', diff --git a/app/views/alchemy/admin/elements/publish.js.erb b/app/views/alchemy/admin/elements/publish.js.erb index d2ecec8e263..e83b6241c91 100644 --- a/app/views/alchemy/admin/elements/publish.js.erb +++ b/app/views/alchemy/admin/elements/publish.js.erb @@ -1,14 +1,19 @@ var el = $('.element-editor[data-element-id="<%= @element.id %>"]'); -var icon = el.find('> .element-header > .element-handle .icon:not(.element)'); +var icon = el.find('> .element-header > .element-handle > .icon'); var label = el.find('> .element-toolbar .publish-element-button label'); +var eye = el.find('> .element-toolbar .publish-element-button .icon'); <%- if @element.public? -%> - icon.removeClass('element_draft'); - icon.addClass('element_public'); + icon.removeClass('fa-window-close fas'); + icon.addClass('fa-window-maximize far'); + eye.removeClass('fa-eye'); + eye.addClass('fa-eye-slash'); label.text('<%= Alchemy.t(:hide_element) %>'); <%- else -%> - icon.removeClass('element_public'); - icon.addClass('element_draft'); + icon.removeClass('fa-window-maximize far'); + icon.addClass('fa-window-close fas'); + eye.removeClass('fa-eye-slash'); + eye.addClass('fa-eye'); label.text('<%= Alchemy.t(:show_element) %>'); <%- end -%> diff --git a/app/views/alchemy/admin/elements/trash.js.erb b/app/views/alchemy/admin/elements/trash.js.erb index e8e1e509b9c..38559431a7b 100644 --- a/app/views/alchemy/admin/elements/trash.js.erb +++ b/app/views/alchemy/admin/elements/trash.js.erb @@ -1,11 +1,14 @@ $('#element_<%= @element.id %>').hide(200, function() { $(this).remove(); Alchemy.growl('<%= j Alchemy.t("Element trashed") %>'); - $('#element_area .sortable_cell').sortable('refresh'); + $('#element_area .sortable-elements').sortable('refresh'); Alchemy.TrashWindow.refresh(); $('#element_trash_button .icon').addClass('full'); Alchemy.PreviewWindow.refresh(); <% @element.richtext_contents_ids.each do |id| %> tinymce.get('tinymce_<%= id %>').remove(); <% end %> + <% if @element.fixed? %> + Alchemy.FixedElements.removeTab(<%= @element.id %>); + <% end %> }); diff --git a/app/views/alchemy/admin/elements/update.js.erb b/app/views/alchemy/admin/elements/update.js.erb index 25a570b88f7..8b4f9c7c177 100644 --- a/app/views/alchemy/admin/elements/update.js.erb +++ b/app/views/alchemy/admin/elements/update.js.erb @@ -9,7 +9,7 @@ $el.trigger('SaveElement.Alchemy', {previewText: '<%= j sanitize(@element.preview_text) %>'}); Alchemy.growl('<%= Alchemy.t(:element_saved) %>'); Alchemy.PreviewWindow.refresh(function() { - Alchemy.ElementEditors.selectElementInPreview(<%= @element.id %>); + Alchemy.ElementEditors.focusElementPreview(<%= @element.id %>); }); <%- else -%> diff --git a/app/views/alchemy/admin/essence_files/assign.js.erb b/app/views/alchemy/admin/essence_files/assign.js.erb index 12c06841dc8..30e908403a7 100644 --- a/app/views/alchemy/admin/essence_files/assign.js.erb +++ b/app/views/alchemy/admin/essence_files/assign.js.erb @@ -1,8 +1,3 @@ -$('#<%= @content.dom_id %>').replaceWith('<%= j( - render "alchemy/essences/essence_file_editor", - formats: [:html], - content: @content, - options: @options -) %>'); +$('#<%= @content.dom_id %>').replaceWith('<%= j render(Alchemy::ContentEditor.new(@content)) %>'); Alchemy.closeCurrentDialog(); Alchemy.setElementDirty($('#element_<%= @content.element.id %>')); diff --git a/app/views/alchemy/admin/essence_files/edit.html.erb b/app/views/alchemy/admin/essence_files/edit.html.erb index cd96b705166..25fab7e877a 100644 --- a/app/views/alchemy/admin/essence_files/edit.html.erb +++ b/app/views/alchemy/admin/essence_files/edit.html.erb @@ -1,4 +1,4 @@ -<% css_classes = @content.settings_value(:css_classes, local_assigns.fetch(:options, {})) %> +<% css_classes = @content.settings[:css_classes] %> <%= alchemy_form_for [:admin, @essence_file] do |f| %> <%= f.input :link_text %> diff --git a/app/views/alchemy/admin/essence_pictures/assign.js.erb b/app/views/alchemy/admin/essence_pictures/assign.js.erb index 2eba7a9ef7d..c657c05ded1 100644 --- a/app/views/alchemy/admin/essence_pictures/assign.js.erb +++ b/app/views/alchemy/admin/essence_pictures/assign.js.erb @@ -1,17 +1,4 @@ $('#picture_to_assign_<%= @picture.id %> a').attr('href', '#').off('click'); -$('#<%= @content.dom_id -%>').replaceWith('<%= j( - render( - "alchemy/essences/essence_picture_editor", - content: @content, - options: @options - ) -) %>'); - -<% if @content.siblings.essence_pictures.count > 1 %> - -Alchemy.SortableContents('#<%= @content.element.id -%>_contents', '<%= form_authenticity_token %>'); - -<% end %> - +$('#<%= @content.dom_id -%>').replaceWith('<%= j render(Alchemy::ContentEditor.new(@content)) %>'); Alchemy.closeCurrentDialog(); Alchemy.setElementDirty('#element_<%= @content.element.id %>'); diff --git a/app/views/alchemy/admin/essence_pictures/crop.html.erb b/app/views/alchemy/admin/essence_pictures/crop.html.erb index e536ab6a0ab..bda33a14477 100644 --- a/app/views/alchemy/admin/essence_pictures/crop.html.erb +++ b/app/views/alchemy/admin/essence_pictures/crop.html.erb @@ -12,18 +12,19 @@
      <%= form_for( @essence_picture, - url: alchemy.admin_essence_picture_path(@essence_picture, options: @options.to_json), + url: alchemy.admin_essence_picture_path(@essence_picture), id: 'image_cropper_form', remote: true ) do |f| %> <%= f.hidden_field :crop_from %> <%= f.hidden_field :crop_size %> <%= hidden_field_tag :content_id, @content.id %> - <%= f.button "#{render_icon(:true)} #{Alchemy.t(:apply)}".html_safe, class: 'with_icon' %> - <%= link_to Alchemy.t('Reset Imagemask'), '#', { + <%= button_tag Alchemy.t('Reset Imagemask'), { onclick: 'Alchemy.ImageCropper.reset(); return false', - class: 'reset_mask' + class: 'reset_mask', + type: 'reset' } %> + <%= f.button Alchemy.t(:apply) %> <% end %>
      <% end %> @@ -33,7 +34,7 @@ $('#imageToCrop').load(function() { Alchemy.ImageCropper.init( <%= @initial_box.values.to_json %>, - <% if @essence_picture.can_be_cropped_to("#{@min_size[:width]}x#{@min_size[:height]}") %> + <% if @picture.can_be_cropped_to?("#{@min_size[:width]}x#{@min_size[:height]}") %> <%= @min_size.values.to_json %>, <% else %> <%= false %>, diff --git a/app/views/alchemy/admin/essence_pictures/destroy.js.erb b/app/views/alchemy/admin/essence_pictures/destroy.js.erb index 83ee2a55823..baa948df4a7 100644 --- a/app/views/alchemy/admin/essence_pictures/destroy.js.erb +++ b/app/views/alchemy/admin/essence_pictures/destroy.js.erb @@ -1,24 +1,5 @@ (function() { - var $picture_editor; - $('#essence_picture_<%= @content_id %>').remove(); - - <% if max_image_count.present? && @essence_pictures.count < max_image_count.to_i -%> - - $picture_editor = $('#element_<%= @element.id %>_contents'); - - if ($('div.add_picture', $picture_editor).length === 0) { - $('#element_<%= @element.id %>_contents').append('<%= j( - render "alchemy/admin/elements/add_picture", - element: @element, - options: @options - ) %>'); - } - - <% end -%> - - Alchemy.SortableContents('#element_<%= @element.id %>_contents', '<%= form_authenticity_token %>'); Alchemy.reloadPreview(); Alchemy.pleaseWaitOverlay(false); - })() diff --git a/app/views/alchemy/admin/essence_pictures/edit.html.erb b/app/views/alchemy/admin/essence_pictures/edit.html.erb index ccda9d1f5c9..35ec0dd901f 100644 --- a/app/views/alchemy/admin/essence_pictures/edit.html.erb +++ b/app/views/alchemy/admin/essence_pictures/edit.html.erb @@ -1,18 +1,20 @@ <%= alchemy_form_for @essence_picture, - url: alchemy.admin_essence_picture_path(@essence_picture, options: @options.to_json) do |f| %> + url: alchemy.admin_essence_picture_path(@essence_picture) do |f| %> <%= hidden_field_tag 'content_id', @content.id %> - <%= f.input :caption, as: @options[:caption_as_textarea] ? 'text' : 'string' %> + <%= f.input :caption, as: @content.settings[:caption_as_textarea] ? 'text' : 'string' %> <%= f.input :title %> <%= f.input :alt_tag %> - <%- if @options[:sizes].present? -%> + <%- if @content.settings[:sizes].present? && @content.settings[:srcset].blank? -%> <%= f.input :render_size, - collection: [[Alchemy.t('Layout default'), @options[:size]]] + @options[:sizes].to_a, + collection: [ + [Alchemy.t('Layout default'), ""] + ] + @content.settings[:sizes].to_a, include_blank: false, input_html: {class: 'alchemy_selectbox'} %> <%- end -%> - <%- if @options[:css_classes].present? -%> + <%- if @content.settings[:css_classes].present? -%> <%= f.input :css_class, - collection: @options[:css_classes], + collection: @content.settings[:css_classes], include_blank: Alchemy.t('None'), input_html: {class: 'alchemy_selectbox'} %> <%- else -%> diff --git a/app/views/alchemy/admin/essence_pictures/update.js.erb b/app/views/alchemy/admin/essence_pictures/update.js.erb index 022acaa2d1b..ff3081b723e 100644 --- a/app/views/alchemy/admin/essence_pictures/update.js.erb +++ b/app/views/alchemy/admin/essence_pictures/update.js.erb @@ -3,7 +3,7 @@ Alchemy.closeCurrentDialog(); Alchemy.setElementDirty('#element_<%= @content.element.id %>'); <% if @content.ingredient %> - $('.thumbnail_background img', '#<%= @content.dom_id %>').replaceWith('<%= essence_picture_thumbnail(@content, @options) %>'); + $('.thumbnail_background img', '#<%= @content.dom_id %>').replaceWith('<%= essence_picture_thumbnail(@content) %>'); Alchemy.ImageLoader('#<%= @content.dom_id %> .thumbnail_background'); <% end %> })(jQuery); diff --git a/app/views/alchemy/admin/languages/_form.html.erb b/app/views/alchemy/admin/languages/_form.html.erb index b65ee7a46e3..6d6c0f13743 100644 --- a/app/views/alchemy/admin/languages/_form.html.erb +++ b/app/views/alchemy/admin/languages/_form.html.erb @@ -1,14 +1,14 @@ -<%= alchemy_form_for [alchemy, :admin, @language] do |f| %> +<%= alchemy_form_for [alchemy, :admin, language] do |f| %> <%= f.input :name, autofocus: true %> <%= f.input :language_code, placeholder: Alchemy.t(:language_code_placeholder) %> <%= f.input :country_code, as: 'string', placeholder: Alchemy.t(:country_code_placeholder), hint: Alchemy.t(:country_code_foot_note) %> - <% if @language.errors[:locale].present? || @language.locale.present? %> + <% if language.errors[:locale].present? || language.locale.present? %> <%= f.input :locale, - collection: @language.matching_locales.presence || ::I18n.available_locales, - selected: @language.locale || @language.language_code || ::I18n.default_locale.to_s, + collection: language.matching_locales.presence || ::I18n.available_locales, + selected: language.locale || language.language_code || ::I18n.default_locale.to_s, input_html: {class: 'alchemy_selectbox'} %> <% end %> <%= f.input :frontpage_name %> @@ -19,6 +19,6 @@ input_html: {class: 'alchemy_selectbox'} %> <%= f.input :public %> <%= f.input :default %> - <%= f.hidden_field :site_id, value: Alchemy::Site.current.id %> + <%= f.hidden_field :site_id %> <%= f.submit Alchemy.t(:save) %> <% end %> diff --git a/app/views/alchemy/admin/languages/_language.html.erb b/app/views/alchemy/admin/languages/_language.html.erb index 000d09d9785..5a33f3dc39c 100644 --- a/app/views/alchemy/admin/languages/_language.html.erb +++ b/app/views/alchemy/admin/languages/_language.html.erb @@ -21,28 +21,23 @@ <%= Alchemy::PageLayout.human_layout_name(language.page_layout) %>
      diff --git a/app/views/alchemy/admin/languages/_table.html.erb b/app/views/alchemy/admin/languages/_table.html.erb index 9561dbf5507..df2984bff21 100644 --- a/app/views/alchemy/admin/languages/_table.html.erb +++ b/app/views/alchemy/admin/languages/_table.html.erb @@ -3,13 +3,13 @@
      <%= Alchemy.t('Who else is online') %>
      <%= Alchemy.t('no users') %>
      <%= Alchemy.t('no users') %>
      - <%= language.public? ? render_icon('true') : '' %> + <%= language.public? ? render_icon(:check) : nil %> - <%= language.default? ? render_icon('true') : '' %> + <%= language.default? ? render_icon(:check) : nil %> <%- if can?(:destroy, language) -%> <%= delete_button resource_path(language) %> <%- end -%> <%- if can?(:edit, language) -%> - <%= link_to_dialog( - '', + <%= link_to_dialog render_icon(:edit), alchemy.edit_admin_language_path(language), { title: Alchemy.t("Edit"), size: "430x415" }, - { - class: "icon edit", - title: Alchemy.t("Edit") - } - ) -%> + title: Alchemy.t("Edit") %> <%- end -%>
      - <%= sort_link @query, :name, hide_indicator: true %> + <%= sort_link @query, :name %> - <%= sort_link @query, :language_code, hide_indicator: true %> + <%= sort_link @query, :language_code %> - <%= sort_link @query, :country_code, hide_indicator: true %> + <%= sort_link @query, :country_code %> <%= Alchemy::Language.human_attribute_name(:code) %> @@ -36,7 +36,7 @@ <%= render_resources %>
      -<%- elsif params[:q].present? -%> +<%- elsif search_filter_params[:q].present? -%>

      <%= Alchemy.t('Nothing found') %>

      <%- end -%> diff --git a/app/views/alchemy/admin/languages/edit.html.erb b/app/views/alchemy/admin/languages/edit.html.erb new file mode 100644 index 00000000000..e56de1199db --- /dev/null +++ b/app/views/alchemy/admin/languages/edit.html.erb @@ -0,0 +1 @@ +<%= render 'form', language: @language %> diff --git a/app/views/alchemy/admin/languages/index.html.erb b/app/views/alchemy/admin/languages/index.html.erb index a92492e9f44..220acc3c4e4 100644 --- a/app/views/alchemy/admin/languages/index.html.erb +++ b/app/views/alchemy/admin/languages/index.html.erb @@ -3,7 +3,8 @@ <% content_for :toolbar do %>
      <%= render 'alchemy/admin/partials/site_select' %> - <%= toolbar_button icon: 'create', + <%= toolbar_button( + icon: :plus, label: label_title, url: new_resource_path, title: label_title, @@ -12,12 +13,30 @@ title: label_title, size: "430x415" }, - if_permitted_to: [:create, Alchemy::Language] %> + if_permitted_to: [:create, Alchemy::Language] + ) if @languages.any? %>
      <%= render 'alchemy/admin/partials/search_form' %> <% end %>
      - <%= resources_header %> - <%= render 'table' %> + <% if @languages.any? %> + <%= render 'alchemy/admin/resources/table_header' %> + <%= render 'table' %> + <% elsif search_filter_params[:q].present? %> + <%= render_message { Alchemy.t('Nothing found') } %> + <% else %> +
      + <%= render_message do %> + <%== Alchemy.t('alchemy/language', scope: :no_resource_found) %> + <% end %> + <%= render 'form', language: Alchemy::Language.new( + Alchemy::Config.get(:default_language).merge( + site: Alchemy::Site.current, + default: true, + public: true + ) + ) %> +
      + <% end %>
      diff --git a/app/views/alchemy/admin/languages/new.html.erb b/app/views/alchemy/admin/languages/new.html.erb new file mode 100644 index 00000000000..e56de1199db --- /dev/null +++ b/app/views/alchemy/admin/languages/new.html.erb @@ -0,0 +1 @@ +<%= render 'form', language: @language %> diff --git a/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb b/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb index 68cf1896783..8917817488f 100644 --- a/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +++ b/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb @@ -4,14 +4,14 @@ <% if layoutpage.definition.blank? %> <%= page_layout_missing_warning %> <% else %> - <%= render_icon(:page) %> + <% end %>
      <%- if can?(:configure, layoutpage) -%>
      <%= link_to_dialog( - render_icon('configure_page'), + render_icon(:cog), alchemy.edit_admin_layoutpage_path(layoutpage), { title: Alchemy.t(:edit_page_properties), @@ -24,7 +24,7 @@ <%- if can?(:copy, layoutpage) -%>
      <%= link_to( - render_icon("copy_page"), + render_icon(:copy), alchemy.insert_admin_clipboard_path( remarkable_type: layoutpage.class.name.demodulize.underscore.pluralize, remarkable_id: layoutpage.id @@ -38,7 +38,7 @@ <%- if can?(:destroy, layoutpage) -%>
      <%= link_to_confirm_dialog( - render_icon('delete_page'), + render_icon(:minus), Alchemy.t(:confirm_to_delete_page), url_for( controller: 'pages', @@ -50,7 +50,7 @@
      <%- end -%>
      -
      +
      <%= link_to( layoutpage.name, alchemy.edit_admin_page_path(layoutpage), diff --git a/app/views/alchemy/admin/layoutpages/edit.html.erb b/app/views/alchemy/admin/layoutpages/edit.html.erb index b861b397f50..95ae5f785a1 100644 --- a/app/views/alchemy/admin/layoutpages/edit.html.erb +++ b/app/views/alchemy/admin/layoutpages/edit.html.erb @@ -5,11 +5,9 @@ include_blank: Alchemy.t('Please choose'), input_html: {class: 'alchemy_selectbox'} %> <%= f.input :name, autofocus: true %> - <% if @page.taggable? %> -
      - <%= f.label :tag_list %> - <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> -
      - <% end %> +
      + <%= f.label :tag_list %> + <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> +
      <%= f.submit Alchemy.t(:save) %> <% end %> diff --git a/app/views/alchemy/admin/layoutpages/index.html.erb b/app/views/alchemy/admin/layoutpages/index.html.erb index fcff34175b2..d7dbe6dcea2 100644 --- a/app/views/alchemy/admin/layoutpages/index.html.erb +++ b/app/views/alchemy/admin/layoutpages/index.html.erb @@ -3,8 +3,8 @@ <%= render 'alchemy/admin/partials/site_select' %> <%= render 'alchemy/admin/partials/language_tree_select' %> <%= toolbar_button( - icon: 'add_page', - url: alchemy.new_admin_page_path(parent_id: @layout_root.id, layoutpage: true), + icon: :plus, + url: alchemy.new_admin_page_path(language: @current_language, layoutpage: true), hotkey: 'alt+n', dialog_options: { title: Alchemy.t('Add global page'), @@ -16,19 +16,24 @@ if_permitted_to: [:create, Alchemy::Page] ) %> <%= toolbar_button( - icon: "clipboard#{clipboard_empty?('pages') ? '' : ' full'}", + icon: clipboard_empty?('pages') ? 'clipboard' : 'paste', url: alchemy.admin_clipboard_path(remarkable_type: 'pages'), dialog_options: { title: Alchemy.t('Clipboard'), size: '380x270' }, title: Alchemy.t('Show clipboard'), - id: "clipboard_button", - label: Alchemy.t('Show clipboard') + link_options: {id: "clipboard_button"}, + label: Alchemy.t('Show clipboard'), + if_permitted_to: [:create, Alchemy::Page] ) %>
      <% 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/leave.html.erb b/app/views/alchemy/admin/leave.html.erb index 26f96a271ce..78044f025cb 100644 --- a/app/views/alchemy/admin/leave.html.erb +++ b/app/views/alchemy/admin/leave.html.erb @@ -3,9 +3,9 @@ <% end %>

      - <%= link_to Alchemy.t('stay logged in'), alchemy.root_path, class: 'button' %> + <%= link_to Alchemy.t('stay logged in'), alchemy.root_path, class: 'button secondary' %>

      -<%= form_tag Alchemy.logout_path, method: 'delete', class: 'buttons' do %> +<%= form_tag Alchemy.logout_path, method: Alchemy.logout_method, class: 'buttons' do %> <%= button_tag Alchemy.t(:logout), autofocus: true %> <% end %> diff --git a/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb b/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb index 216d012e9af..9a1dfc516e4 100644 --- a/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +++ b/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb @@ -5,7 +5,7 @@ edit_admin_legacy_page_url_path(legacy_page_url, page_id: @page.id), {size: '400x125', title: Alchemy.t('Edit link')}, {title: Alchemy.t(:edit)} %> - <%= link_to_confirm_dialog render_icon(:destroy), + <%= link_to_confirm_dialog render_icon(:minus), Alchemy.t('Are you sure?'), admin_legacy_page_url_path(legacy_page_url, page_id: @page.id), {title: Alchemy.t(:remove)} %> diff --git a/app/views/alchemy/admin/legacy_page_urls/_new.html.erb b/app/views/alchemy/admin/legacy_page_urls/_new.html.erb index 2fa6935105c..8375948dbb2 100644 --- a/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +++ b/app/views/alchemy/admin/legacy_page_urls/_new.html.erb @@ -12,7 +12,7 @@
      diff --git a/app/views/alchemy/admin/nodes/_form.html.erb b/app/views/alchemy/admin/nodes/_form.html.erb new file mode 100644 index 00000000000..e8c1602b28e --- /dev/null +++ b/app/views/alchemy/admin/nodes/_form.html.erb @@ -0,0 +1,48 @@ +<%= alchemy_form_for([:admin, node]) do |f| %> + <% 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 %> + <% 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 :language_id %> + <%= f.submit button_label %> +<% end %> + + diff --git a/app/views/alchemy/admin/nodes/_node.html.erb b/app/views/alchemy/admin/nodes/_node.html.erb new file mode 100644 index 00000000000..143473bc0bf --- /dev/null +++ b/app/views/alchemy/admin/nodes/_node.html.erb @@ -0,0 +1,73 @@ +<%= content_tag :li, class: 'menu-item', data: { id: node.id, parent_id: node.parent_id, folded: node.folded? } do %> + <%= content_tag :div, class: [ + 'sitemap_node', + node.external? ? 'external' : 'internal', + "sitemap_node-level_#{node.depth}" + ] do %> + +   + + + <% if can?(:edit, node) %> + <%= link_to_dialog( + render_icon(:edit), + alchemy.edit_admin_node_path(node), + { + title: node.root? ? Alchemy.t(:edit_menu) : Alchemy.t(:edit_node), + size: node.root? ? '450x120' : '450x360' + }, + title: node.root? ? Alchemy.t(:edit_menu) : Alchemy.t(:edit_node) + ) %> + <% end %> + <% if can?(:destroy, node) %> + <%= link_to_confirm_dialog( + render_icon(:minus), + node.root? ? Alchemy.t(:confirm_to_delete_menu) : Alchemy.t(:confirm_to_delete_node), + url_for( + controller: 'nodes', + action: 'destroy', + id: node.id + ), + { + title: node.root? ? Alchemy.t(:delete_menu) : Alchemy.t(:delete_node) + } + ) %> + <% end %> + <% if can?(:create, Alchemy::Node) %> + <%= link_to_dialog( + render_icon(:plus), + alchemy.new_admin_node_path(parent_id: node.id), + { + title: Alchemy.t(:create_node), + size: '450x360', + overflow: true + }, + title: Alchemy.t(:create_node) + ) %> + <% end %> + +
      + <%= node.name || ' '.html_safe %> + + <% if node.page %> + +   + <%= link_to [:edit, :admin, node.page], title: Alchemy.t(:edit_page) do %> + <%= node.page.name %> + <% end %> + <% end %> + + <% if node.url %> + + <%= link_to node.url, node.url, target: '_blank', title: node.url %> + <% if node.external? %> + + <% end %> + + <% end %> +
      + <% end %> + <%= content_tag :ul, class: "children #{' folded' if node.folded?}", data: { node_id: node.id } do %> + <%= render partial: 'node', collection: node.children.includes(:page, :children) %> + <% end %> +<% end %> diff --git a/app/views/alchemy/admin/nodes/edit.html.erb b/app/views/alchemy/admin/nodes/edit.html.erb new file mode 100644 index 00000000000..ca1a427c9e2 --- /dev/null +++ b/app/views/alchemy/admin/nodes/edit.html.erb @@ -0,0 +1 @@ +<%= render 'form', node: @node, button_label: Alchemy.t(:save) %> \ No newline at end of file diff --git a/app/views/alchemy/admin/nodes/index.html.erb b/app/views/alchemy/admin/nodes/index.html.erb new file mode 100644 index 00000000000..41259a463be --- /dev/null +++ b/app/views/alchemy/admin/nodes/index.html.erb @@ -0,0 +1,47 @@ +<% content_for(:title) do %> + <%= Alchemy.t(:menus, scope: 'modules') %> +<% end %> + +<% content_for(:toolbar) do %> +
      + <%= render 'alchemy/admin/partials/site_select' %> + <%= render 'alchemy/admin/partials/language_tree_select' %> + <%= toolbar_button( + icon: 'plus', + label: Alchemy.t(:create_menu), + url: alchemy.new_admin_node_path, + hotkey: 'alt+n', + dialog_options: { + title: Alchemy.t(:create_menu), + size: '450x120' + }, + if_permitted_to: [:create, Alchemy::Node] + ) if @root_nodes.any? %> +
      +<% end %> + +
      +

      + <% if @root_nodes.any? %> + <% @root_nodes.each do |root_node| %> +
        + <%= render 'node', node: root_node %> +
      + <% end %> + <% else %> +
      + <%= render_message do %> + <%== Alchemy.t('alchemy/node', scope: :no_resource_found) %> + <% end %> + <%= render 'form', node: Alchemy::Node.new( + site: Alchemy::Site.current, + language: Alchemy::Language.current + ), + button_label: Alchemy.t(:create) %> +
      + <% end %> +
      + + diff --git a/app/views/alchemy/admin/nodes/new.html.erb b/app/views/alchemy/admin/nodes/new.html.erb new file mode 100644 index 00000000000..6c4bf359e7f --- /dev/null +++ b/app/views/alchemy/admin/nodes/new.html.erb @@ -0,0 +1 @@ +<%= render 'form', node: @node, button_label: Alchemy.t(:create) %> \ No newline at end of file diff --git a/app/views/alchemy/admin/pages/_anchor_link.html.erb b/app/views/alchemy/admin/pages/_anchor_link.html.erb new file mode 100644 index 00000000000..92b4aea0bdb --- /dev/null +++ b/app/views/alchemy/admin/pages/_anchor_link.html.erb @@ -0,0 +1,22 @@ +<%= form_tag do %> + <%= render_message do %> +

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

      + <% end %> +
      + + <%= select_tag(:anchor_link, + options_for_select([[Alchemy.t('Please choose'), '']]), + class: 'alchemy_selectbox') %> +
      +
      + + <%= text_field_tag "anchor_link_title", '', class: 'link_title' %> +
      +
      + <%= link_to Alchemy.t(:apply), '', class: 'create-link button', 'data-link-type' => 'anchor' %> +
      +<% end %> 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 0188b90615a..482099da009 100644 --- a/app/views/alchemy/admin/pages/_create_language_form.html.erb +++ b/app/views/alchemy/admin/pages/_create_language_form.html.erb @@ -1,7 +1,6 @@ -<% if root = Alchemy::Page.rootpage %> -
      +
      <%= render_message do %> -

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

      +

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

      <% end %> <%- if @language -%> @@ -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/_external_link.html.erb b/app/views/alchemy/admin/pages/_external_link.html.erb index 656320d1fdf..8f8fb7fbeb9 100644 --- a/app/views/alchemy/admin/pages/_external_link.html.erb +++ b/app/views/alchemy/admin/pages/_external_link.html.erb @@ -1,6 +1,6 @@ <%= form_tag do %> <%= render_message do %> -

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

      +

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

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

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

      <% end %> diff --git a/app/views/alchemy/admin/pages/_file_link.html.erb b/app/views/alchemy/admin/pages/_file_link.html.erb index 930cf64df8a..d7ff4661932 100644 --- a/app/views/alchemy/admin/pages/_file_link.html.erb +++ b/app/views/alchemy/admin/pages/_file_link.html.erb @@ -1,6 +1,6 @@ <%= form_tag do %> <%= render_message do %> -

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

      +

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

      <% end %>
      -

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

      <%= 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} %> -

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

      @@ -39,12 +36,10 @@ as: 'text', hint: Alchemy.t('pages.update.comma_seperated') %> - <% if @page.taggable? %> -
      - <%= f.label :tag_list %> - <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> -
      - <% end %> +
      + <%= f.label :tag_list %> + <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> +
      <%= f.submit Alchemy.t(:save) %> <% end %> diff --git a/app/views/alchemy/admin/pages/_internal_link.html.erb b/app/views/alchemy/admin/pages/_internal_link.html.erb index c2abcbf6688..89ae609629c 100644 --- a/app/views/alchemy/admin/pages/_internal_link.html.erb +++ b/app/views/alchemy/admin/pages/_internal_link.html.erb @@ -1,21 +1,19 @@ <%= form_tag do %> <%= render_message do %> -

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

      +

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

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

      -

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

      <% end %> -
      - <% if @page_root %> - <%= render 'sitemap', page_partial: 'page_for_links', full: true %> - <% end %> +
      + +
      -
      - <%= hidden_field_tag(:internal_urlname) %> - <%= hidden_field_tag(:page_anchor) %> <%= link_to Alchemy.t(:apply), '', class: 'create-link button', 'data-link-type' => 'internal' %>
      <% end %> diff --git a/app/views/alchemy/admin/pages/_locked_page.html.erb b/app/views/alchemy/admin/pages/_locked_page.html.erb index 87bf6d5da43..db99caed43c 100644 --- a/app/views/alchemy/admin/pages/_locked_page.html.erb +++ b/app/views/alchemy/admin/pages/_locked_page.html.erb @@ -7,7 +7,7 @@ <% end %> <%= form_tag(alchemy.unlock_admin_page_path(locked_page), remote: true) do %> <% 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 b44f5ed2761..d0b73db65dd 100644 --- a/app/views/alchemy/admin/pages/_new_page_form.html.erb +++ b/app/views/alchemy/admin/pages/_new_page_form.html.erb @@ -1,11 +1,13 @@ <%= 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, label: Alchemy.t(:page_type), - include_blank: Alchemy.t('Please choose'), + include_blank: @page_layouts.length == 1 ? nil : Alchemy.t('Please choose'), required: true, + selected: @page_layouts.length == 1 ? @page_layouts.first : nil, input_html: {class: 'alchemy_selectbox'} %> <%= f.input :name %> <%= f.submit Alchemy.t(:create) %> diff --git a/app/views/alchemy/admin/pages/_page.html.erb b/app/views/alchemy/admin/pages/_page.html.erb index 91d28dc5cfa..c43cbbb4d2f 100644 --- a/app/views/alchemy/admin/pages/_page.html.erb +++ b/app/views/alchemy/admin/pages/_page.html.erb @@ -1,37 +1,45 @@ -
    • +
    • <% unless @sorting %> {{#unless root_or_leaf}} <%= link_to( - '', alchemy.fold_admin_page_path(page), remote: true, method: :post, - class: "page_folder {{#if folded}}folded{{else}}collapsed{{/if}}", + class: "page_folder", title: "{{#if folded}}#{Alchemy.t('Show childpages')}{{else}}#{Alchemy.t('Hide childpages')}{{/if}}", id: "fold_button_{{id}}" - ) - %> + ) do %> + {{#if folded}} + + {{else}} + + {{/if}} + <% end %> {{/unless}} <% end %> {{#if definition_missing}} <%= page_layout_missing_warning %> {{else}} {{#if permissions.edit_content}} -
      - {{#if locked}} + {{#if locked}} + + {{locked_notice}} - {{/if}} -
      + + {{else}} + + {{/if}} {{else}} -
      + + <%= Alchemy.t('Your user role does not allow you to edit this page') %> -
      + {{/if}} {{/if}}
      @@ -40,7 +48,7 @@ {{#if permissions.info}}
      <%= link_to_dialog( - render_icon(:info), + render_icon('info-circle'), alchemy.info_admin_page_path(page), { title: Alchemy.t(:page_infos), @@ -50,7 +58,7 @@ {{else}}
      - <%= render_icon(:info) %> + <%= render_icon('info-circle') %> <%= Alchemy.t('Your user role does not allow you to edit this page') %> @@ -59,17 +67,17 @@ {{#if permissions.configure}}
      <%= link_to_dialog( - render_icon(:configure_page), + render_icon(:cog), alchemy.configure_admin_page_path(page), { title: Alchemy.t(:edit_page_properties), - size: page.redirects_to_external? ? '450x330' : '450x680' + size: '450x680' } ) -%> {{else}}
      - <%= render_icon(:configure_page) %> + <%= render_icon(:cog) %> <%= Alchemy.t('Your user role does not allow you to edit this page') %> @@ -78,7 +86,7 @@ {{#if permissions.copy}}
      <%= link_to( - render_icon(:copy_page), + render_icon(:copy), alchemy.insert_admin_clipboard_path( remarkable_type: page.class.name.demodulize.underscore.pluralize, remarkable_id: '__ID__', @@ -89,7 +97,7 @@ {{else}}
      - <%= render_icon(:copy_page) %> + <%= render_icon(:copy) %> <%= Alchemy.t('Your user role does not allow you to edit this page') %> @@ -98,7 +106,7 @@ {{#if permissions.destroy}}
      <%= link_to_confirm_dialog( - render_icon(:delete_page), + render_icon(:minus), Alchemy.t(:confirm_to_delete_page), url_for( controller: 'pages', @@ -109,7 +117,7 @@ {{else}}
      - <%= render_icon(:delete_page) %> + <%= render_icon(:minus) %> <%= Alchemy.t('Your user role does not allow you to edit this page') %> @@ -118,7 +126,7 @@ {{#if permissions.create}}
      <%= link_to_dialog( - render_icon(:add_page), + render_icon(:plus), alchemy.new_admin_page_path(parent_id: '__ID__').gsub('__ID__', '{{id}}'), { title: Alchemy.t(:create_page), @@ -129,7 +137,7 @@ {{else}}
      - <%= render_icon(:add_page) %> + <%= render_icon(:plus) %> <%= Alchemy.t('Your user role does not allow you to edit this page') %> @@ -139,39 +147,28 @@
      - + {{status_titles.public}} - - {{status_titles.visible}} - - - + {{status_titles.restricted}}
      - {{#if redirects_to_external}} -
      - » <%= Alchemy.t('Redirects to') %>: - {{ external_urlname }} -
      - {{/if}} +
      + {{ url_path }} +
      - {{#if redirects_to_external}} - {{ name }} + {{#if permissions.edit_content}} + <%= link_to_unless( + @sorting, + '{{name}}', + alchemy.edit_admin_page_path(page), + title: Alchemy.t(:edit_page), + class: "sitemap_pagename_link" + ) { content_tag('span', '{{name}}', class: "sitemap_pagename_link") } -%> {{else}} - {{#if permissions.edit_content}} - <%= link_to_unless( - @sorting, - '{{name}}', - alchemy.edit_admin_page_path(page), - title: Alchemy.t(:edit_page), - class: "sitemap_pagename_link" - ) { content_tag('span', '{{name}}', class: "sitemap_pagename_link") } -%> - {{else}} - <%= content_tag('span', '{{name}}', class: "sitemap_pagename_link") %> - {{/if}} + <%= content_tag('span', '{{name}}', class: "sitemap_pagename_link") %> {{/if}}
      diff --git a/app/views/alchemy/admin/pages/_page_for_links.html.erb b/app/views/alchemy/admin/pages/_page_for_links.html.erb deleted file mode 100644 index 7dd39b10f40..00000000000 --- a/app/views/alchemy/admin/pages/_page_for_links.html.erb +++ /dev/null @@ -1,53 +0,0 @@ -
    • -
      -
      - <%= render_icon(:page) %> -
      -
      - {{#unless redirects_to_external}} -
      - <%= link_to render_icon(:element_window), - alchemy.list_admin_elements_path(page_id: '__ID__').gsub('__ID__', '{{id}}'), - remote: true, - data: { - 'page-id' => '{{id}}', - url: @url_prefix + '{{urlname}}' - }, - class: "show_elements_to_link" - %> - -
      - {{/unless}} -
      -
      - - - - - - - - - -
      -
      {{urlname}}"> - {{#if redirects_to_external}} - {{ name }} - {{else}} - <%= link_to('{{ name }}', '', { - data: { - 'page-id' => '{{id}}', - url: @url_prefix + '{{urlname}}' - }, - title: Alchemy.t('page_for_links.choose_page', name: '{{name}}'), - class: "sitemap_pagename_link" - }) %> - {{/if}} -
      -
      - {{#if children}} -
        - {{> list}} -
      - {{/if}} -
    • diff --git a/app/views/alchemy/admin/pages/_page_infos.html.erb b/app/views/alchemy/admin/pages/_page_infos.html.erb index 306d84a0836..81a5318b61c 100644 --- a/app/views/alchemy/admin/pages/_page_infos.html.erb +++ b/app/views/alchemy/admin/pages/_page_infos.html.erb @@ -1,12 +1,8 @@ - <%= render_icon page.public? ? :public : :not_public %> + <%= render_icon(:compass, transform: 'shrink-2', class: @page.public? ? nil : 'disabled') %> <%= page.status_title(:public) %> - <%= render_icon page.visible? ? :visible : :not_visible %> - <%= page.status_title(:visible) %> - - - <%= render_icon page.restricted? ? :restricted : :not_restricted %> + <%= render_icon(:lock, transform: 'shrink-2', class: @page.restricted? ? nil : 'disabled') %> <%= page.status_title(:restricted) %> diff --git a/app/views/alchemy/admin/pages/_publication_fields.html.erb b/app/views/alchemy/admin/pages/_publication_fields.html.erb index 72edfeeff02..f310704414d 100644 --- a/app/views/alchemy/admin/pages/_publication_fields.html.erb +++ b/app/views/alchemy/admin/pages/_publication_fields.html.erb @@ -1,6 +1,4 @@ -<% checkbox = check_box_tag :page_public, nil, @page.public?, name: nil, - data: {date_format: I18n.t(:datetimepicker, scope: 'time.formats')}, - disabled: @page.attribute_fixed?(:public_on) %> +<% checkbox = check_box_tag :page_public, nil, @page.public?, name: nil, disabled: @page.attribute_fixed?(:public_on) %>
      -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/alchemy/admin/pages/_sitemap.html.erb b/app/views/alchemy/admin/pages/_sitemap.html.erb index 1b679d23a76..8df5cdf2d95 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) %> +

      +

      @@ -24,13 +30,6 @@ ,ready: function () { Alchemy.PageSorter(); } - <% elsif action_name == 'link' %> - ,spinner: new Alchemy.Spinner('small'), - ready: function () { - if (Alchemy.currentDialog()) { - Alchemy.currentDialog().attachTreeEvents(); - } - } <% end %> }); }); diff --git a/app/views/alchemy/admin/pages/configure_external.html.erb b/app/views/alchemy/admin/pages/configure_external.html.erb deleted file mode 100644 index 583cae9b1b0..00000000000 --- a/app/views/alchemy/admin/pages/configure_external.html.erb +++ /dev/null @@ -1,33 +0,0 @@ -<%= alchemy_form_for [:admin, @page] do |f| %> - <%= f.input :page_layout, - collection: @page_layouts, - label: Alchemy.t(:page_type), - include_blank: Alchemy.t('Please choose'), - input_html: {class: 'alchemy_selectbox'} %> -
      - -
      - - <% if configuration(:sitemap)['show_flag'] %> - - <% end %> -
      -
      -

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

      - <%= f.input :name, autofocus: true %> - <%= f.input :urlname, as: 'string' %> - <%= f.input :title %> - <% if @page.taggable? %> -
      - <%= f.label :tag_list %> - <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> -
      - <% end %> - <%= f.submit Alchemy.t(:save) %> -<% end %> diff --git a/app/views/alchemy/admin/pages/edit.html.erb b/app/views/alchemy/admin/pages/edit.html.erb index fa66cf06a2f..d9801d114a1 100644 --- a/app/views/alchemy/admin/pages/edit.html.erb +++ b/app/views/alchemy/admin/pages/edit.html.erb @@ -7,25 +7,15 @@
      <%= form_tag alchemy.unlock_admin_page_path(@page, redirect_to: @layoutpage ? alchemy.admin_layoutpages_path : alchemy.admin_pages_path), id: 'unlock_page_form' do %> <% end %>
      - <% unless @page.layoutpage? %> -
      - <%= form_tag alchemy.visit_admin_page_path(@page), id: 'visit_page_form' do %> - - - <% end %> -
      - <% end %>
      <%= link_to_dialog( - render_icon('info'), + render_icon('info-circle'), alchemy.info_admin_page_path(@page), { title: Alchemy.t(:page_infos), @@ -41,7 +31,7 @@
      <% if @page.layoutpage? %> <%= link_to_dialog( - render_icon('settings'), + render_icon(:cog), alchemy.edit_admin_layoutpage_path(@page), { title: Alchemy.t(:edit_page_properties), @@ -53,7 +43,7 @@ ) %> <% else %> <%= link_to_dialog( - render_icon('settings'), + render_icon(:cog), alchemy.configure_admin_page_path(@page), { title: Alchemy.t(:edit_page_properties), @@ -70,12 +60,22 @@
      <%= form_tag alchemy.publish_admin_page_path(@page), id: 'publish_page_form' do %> <%= button_tag class: 'icon_button', title: Alchemy.t(:explain_publishing) do %> - <%= render_icon('publish') %> + <%= render_icon('cloud-upload-alt') %> <% end %> <% end %>
      <% end %> + <% unless @page.layoutpage? %> +
      + <%= form_tag alchemy.visit_admin_page_path(@page), id: 'visit_page_form' do %> + <%= button_tag class: 'icon_button', disabled: !@page.public? do %> + <%= render_icon('external-link-alt') %> + <% end %> + + <% end %> +
      + <% end %> <% if @page.has_hint? %>
      <%= render_hint_for(@page) %> @@ -89,7 +89,7 @@
      - <%= link_to render_icon(:reload), '#', { + <%= link_to render_icon(:redo), nil, { title: Alchemy.t('Reload Preview'), class: 'icon_button', id: 'reload_preview_button' @@ -99,7 +99,7 @@
      - <%= link_to render_icon(:element_window), '', { + <%= link_to render_icon(:bars), nil, { title: Alchemy.t(:hide_elements), class: 'icon_button' } %> @@ -142,7 +142,7 @@ title: '<%= Alchemy.t("New Element") %>', label: '<%= Alchemy.t("New Element") %>', hotkey: 'alt+n', - iconClass: 'new_element', + iconClass: 'plus', onClick: function() { Alchemy.openDialog('<%= alchemy.new_admin_element_path(page_id: @page.id) %>', { title: '<%= Alchemy.t("New Element") %>', @@ -153,7 +153,7 @@ { title: '<%= Alchemy.t("Clipboard") %>', label: '<%= Alchemy.t("Show clipboard") %>', - iconClass: 'clipboard<%= clipboard_empty?("elements") ? "" : " full" %>', + iconClass: '<%= clipboard_empty?("elements") ? "clipboard" : "paste" %>', buttonId: 'clipboard_button', onClick: function() { Alchemy.openDialog('<%= alchemy.admin_clipboard_path(remarkable_type: "elements") %>', { @@ -165,7 +165,7 @@ { title: '<%= Alchemy.t("Show trash") %>', label: '<%= Alchemy.t("Show trash") %>', - iconClass: 'trash<%= trash_empty?("elements") ? "" : " full" %>', + iconClass: 'trash-alt<%= trash_empty?("elements") ? "" : " full" %>', buttonId: 'element_trash_button', onClick: function() { Alchemy.TrashWindow.open(<%= @page.id %>, '<%= Alchemy.t("Trash") %>'); @@ -178,14 +178,19 @@ Alchemy.ElementEditors.init(); Alchemy.SelectBox('.element-editor'); Alchemy.Tinymce.init(<%= @page.richtext_contents_ids.to_json %>); - $('#cells').tabs().tabs('paging', { follow: true, followOnSelect: true } ); + $('#fixed-elements').tabs().tabs('paging', { + follow: true, + followOnSelect: true, + prevButton: '', + nextButton: '' + }); Alchemy.ElementDirtyObserver('#element_area'); if (window.location.hash) { $(window.location.hash).trigger('FocusElementEditor.Alchemy'); } }); - 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); @@ -195,17 +200,13 @@ $('#top_menu').css('z-index', 30); }); - $('select#preview_size').on('change', function(e) { + $('select#preview_size').on('change', function() { var width = this.value; - if (this.value === 'auto') { - width = Alchemy.PreviewWindow.currentWidth; + if (width === 'auto') { + Alchemy.PreviewWindow.currentWindow.css('width', ''); + } else { + Alchemy.PreviewWindow.resize(width); } - Alchemy.PreviewWindow.currentWindow.css('width', width); - }); - - $(window).resize(function() { - Alchemy.PreviewWindow.resize(); - Alchemy.ElementsWindow.resize(); }); }); diff --git a/app/views/alchemy/admin/pages/index.html.erb b/app/views/alchemy/admin/pages/index.html.erb index 8d1914f0809..ac016355475 100644 --- a/app/views/alchemy/admin/pages/index.html.erb +++ b/app/views/alchemy/admin/pages/index.html.erb @@ -1,3 +1,7 @@ +<% content_for :javascript_includes do %> + +<% end %> + <% content_for :toolbar do %>
      <%= render 'alchemy/admin/partials/site_select' %> @@ -5,7 +9,7 @@ <% if can?(:flush, Alchemy::Page) %>
      <%= link_to( - render_icon('flush'), + render_icon(:eraser), alchemy.flush_admin_pages_path, :remote => true, :method => :post, @@ -18,10 +22,10 @@ <% if can?(:sort, Alchemy::Page) %>
      <%= link_to( - render_icon(:sort), + render_icon(:random), alchemy.sort_admin_pages_path, method: :get, - class: 'icon_button please_wait', + class: 'icon_button', title: Alchemy.t('Sort pages') ) %> @@ -29,7 +33,7 @@ <% end %>
      <%= link_to_dialog( - render_icon("clipboard#{clipboard_empty?('pages') ? '' : ' full'}"), + render_icon(clipboard_empty?('pages') ? :clipboard : :paste), alchemy.admin_clipboard_path(:remarkable_type => 'pages'), { :title => Alchemy.t('Clipboard'), @@ -41,34 +45,26 @@
      -
      - <%= text_field_tag 'filter', '', - class: 'thin_border js_filter_field search_input_field', - placeholder: Alchemy.t(:search), - id: nil %> - <%= render_icon :search %> - <%= link_to('', '#', { - class: "js_filter_field_clear", - title: Alchemy.t(:click_to_show_all) - }) %> +
      +
      + + <%= link_to(render_icon(:times, size: 'xs'), '#', { + class: "search_field_clear", + title: Alchemy.t(:click_to_show_all) + }) %> +
      <% end %>
      <% if @page_root %> - - - -

      +

      <%= render 'sitemap', page_partial: 'page', full: !!@sorting %> diff --git a/app/views/alchemy/admin/pages/info.html.erb b/app/views/alchemy/admin/pages/info.html.erb index 979888246d6..b665bb15451 100644 --- a/app/views/alchemy/admin/pages/info.html.erb +++ b/app/views/alchemy/admin/pages/info.html.erb @@ -14,38 +14,29 @@

      <%= @page.layout_display_name %>

      - <% if @page.redirects_to_external? %> -

      <%= @page.urlname %>

      - <% else %> -

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

      - <% end %>

      - <%= render_icon @page.public? ? :public : :not_public %> + <%= render_icon(:compass, transform: 'shrink-2', class: @page.public? ? nil : 'disabled') %> <%= @page.status_title(:public) %> - <%= render_icon @page.visible? ? :visible : :not_visible %> - <%= @page.status_title(:visible) %> - - - <%= render_icon @page.restricted? ? :restricted : :not_restricted %> + <%= render_icon(:lock, transform: 'shrink-2', class: @page.restricted? ? nil : 'disabled') %> <%= @page.status_title(:restricted) %>

      -

      <%= Alchemy.t(:from_at) % {by: @page.creator_name, at: l(@page.created_at, format: :page_status)} %>

      +

      <%= Alchemy.t(:from_at) % {by: @page.creator_name, at: l(@page.created_at, format: :'alchemy.page_status')} %>

      -

      <%= Alchemy.t(:from_at) % {by: @page.updater_name, at: l(@page.updated_at, format: :page_status)} %>

      +

      <%= Alchemy.t(:from_at) % {by: @page.updater_name, at: l(@page.updated_at, format: :'alchemy.page_status')} %>

      <% if @page.locked? %>
      diff --git a/app/views/alchemy/admin/pages/link.html.erb b/app/views/alchemy/admin/pages/link.html.erb index 522a576923f..f8d894851c2 100644 --- a/app/views/alchemy/admin/pages/link.html.erb +++ b/app/views/alchemy/admin/pages/link.html.erb @@ -1,12 +1,16 @@
      + diff --git a/app/views/alchemy/admin/pages/sort.html.erb b/app/views/alchemy/admin/pages/sort.html.erb index 3b3867505fe..f011458feaf 100644 --- a/app/views/alchemy/admin/pages/sort.html.erb +++ b/app/views/alchemy/admin/pages/sort.html.erb @@ -1,7 +1,7 @@ <% content_for :toolbar do %>
      <%= link_to alchemy.admin_pages_path, class: 'icon_button' do %> - <%= render_icon :back %> + <%= render_icon 'angle-double-left' %> <% end %>
      diff --git a/app/views/alchemy/admin/pages/unlock.js.erb b/app/views/alchemy/admin/pages/unlock.js.erb index 69b10b6297c..9c7761b3e09 100644 --- a/app/views/alchemy/admin/pages/unlock.js.erb +++ b/app/views/alchemy/admin/pages/unlock.js.erb @@ -1,8 +1,13 @@ -(function($) { - $('#locked_page_<%= @page.id -%>').remove(); - $('#page_<%= @page.id -%> .sitemap_page').removeClass('locked'); - <%- if @pages_locked_by_user.blank? -%> - $('#locked_pages label').hide(); - <%- end -%> - 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/pages/update.js.erb b/app/views/alchemy/admin/pages/update.js.erb index f803369661c..89d42932f57 100644 --- a/app/views/alchemy/admin/pages/update.js.erb +++ b/app/views/alchemy/admin/pages/update.js.erb @@ -15,7 +15,7 @@ var page_html = "<%= j render('page', page: @page) %>"; var compiler = Handlebars.compile(page_html); - var tree = JSON.parse('<%== @tree.to_json %>'); + var tree = <%== @tree.to_json %>; var html = compiler(tree.pages[0]); $('#page_<%= @page.id %>').replaceWith(html); diff --git a/app/views/alchemy/admin/partials/_flash.html.erb b/app/views/alchemy/admin/partials/_flash.html.erb index bc2002e2cf8..7a94803849c 100644 --- a/app/views/alchemy/admin/partials/_flash.html.erb +++ b/app/views/alchemy/admin/partials/_flash.html.erb @@ -1,4 +1,4 @@
      - <%= render_icon(flash_type.to_s) %> + <%= render_icon message_icon_class(flash_type) %> <%= message %>
      diff --git a/app/views/alchemy/admin/partials/_language_tree_select.html.erb b/app/views/alchemy/admin/partials/_language_tree_select.html.erb index 02672a79017..83e62f6160e 100644 --- a/app/views/alchemy/admin/partials/_language_tree_select.html.erb +++ b/app/views/alchemy/admin/partials/_language_tree_select.html.erb @@ -1,9 +1,13 @@ -<%- if multi_language? -%> +<% languages = Alchemy::Language.on_current_site %> +<% if can?(:switch_language, Alchemy::Page) && languages.many? %>
      - <%= form_tag switch_language_admin_pages_path, method: 'get' do %> + <%= form_tag switch_admin_languages_path, method: 'get' do %> <%= select_tag( 'language_id', - options_for_select(@languages.map { |l| [l.name, l.id] }, Alchemy::Language.current.id), + options_for_select( + languages.map { |l| [l.name, l.id] }, + Alchemy::Language.current.id + ), class: 'alchemy_selectbox short', data: {'auto-submit' => true} ) %> diff --git a/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb b/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb index 2ceb5798c0a..32c109a0f39 100644 --- a/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +++ b/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb @@ -1,12 +1,16 @@ <% if can? *navigate_module(navigation) %> - <%= content_tag :div, class: main_navigation_css_classes(navigation) do %> + <%= content_tag :div, class: main_navigation_css_classes(navigation), data: navigation["data"] do %> <%= link_to url_for_module(alchemy_module) do %> <% if navigation["image"] %> <%= image_tag(navigation["image"]) %> + <% elsif navigation["inline_image"] %> + + <%== navigation["inline_image"] %> + <% elsif navigation["icon"] %> - "> + fa-lg fa-fw"> <% else %> - + <% end %> <% end %> diff --git a/app/views/alchemy/admin/partials/_remote_search_form.html.erb b/app/views/alchemy/admin/partials/_remote_search_form.html.erb index b59d6b7b26b..5afbceb0ab2 100644 --- a/app/views/alchemy/admin/partials/_remote_search_form.html.erb +++ b/app/views/alchemy/admin/partials/_remote_search_form.html.erb @@ -1,27 +1,28 @@ -<%= search_form_for @query, url: url_for( - action: 'index', - options: @options.to_json, - size: @size +<%= search_form_for @query, url: url_for({ + action: 'index', + size: @size, + }.merge(search_filter_params.except(:q)) ), remote: true, html: {class: 'search_form', id: nil} do |f| %> - <%= hidden_field_tag("element_id", @element.blank? ? "" : @element.id) %> - <%= hidden_field_tag("content_id", @content.blank? ? "" : @content.id) %> + <%= hidden_field_tag("element_id", @element.blank? ? "" : @element.id, id: nil) %> + <%= hidden_field_tag("content_id", @content.blank? ? "" : @content.id, id: nil) %>
      - <%= render_icon('search') %> + <%= f.search_field resource_handler.search_field_name, placeholder: Alchemy.t(:search), class: 'search_input_field', id: nil %> - <%= link_to '', url_for( + <%= link_to render_icon(:times, size: 'xs'), url_for({ action: 'index', element_id: @element.blank? ? '' : @element.id, content_id: @content.blank? ? '' : @content.id, - options: @options.to_json, size: @size, overlay: true - ), + }.merge(search_filter_params.except(:q))), remote: true, class: 'search_field_clear', title: Alchemy.t(:click_to_show_all), - style: params[:q].blank? ? 'display: none' : 'display: block' %> + style: search_filter_params[:q].blank? ? 'display: none' : 'display: block' %>
      <% end %> diff --git a/app/views/alchemy/admin/partials/_routes.html.erb b/app/views/alchemy/admin/partials/_routes.html.erb index 7d7e5714d8f..b35cb084753 100644 --- a/app/views/alchemy/admin/partials/_routes.html.erb +++ b/app/views/alchemy/admin/partials/_routes.html.erb @@ -15,11 +15,18 @@ return '<%= alchemy.fold_admin_element_path(id: 1) %>'.replace(/1/, id); }, - list_admin_elements_path: '<%= alchemy.list_admin_elements_path %>', - order_admin_contents_path: '<%= alchemy.order_admin_contents_path %>', + toggle_folded_api_node_path: function(id) { + return '<%= alchemy.toggle_folded_api_node_path(id: 1) %>'.replace(/1/, id); + }, + + move_api_node_path: function(id) { + return '<%= alchemy.move_api_node_path(id: 1) %>'.replace(/1/, id); + }, + order_admin_elements_path: '<%= alchemy.order_admin_elements_path %>', order_admin_pages_path: '<%= alchemy.order_admin_pages_path %>', - link_admin_pages_path: '<%= alchemy.link_admin_pages_path %>' - + link_admin_pages_path: '<%= alchemy.link_admin_pages_path %>', + api_pages_path: '<%= alchemy.api_pages_path %>', + api_elements_path: '<%= alchemy.api_elements_path %>' }; diff --git a/app/views/alchemy/admin/partials/_search_form.html.erb b/app/views/alchemy/admin/partials/_search_form.html.erb index 49e3f73e18e..8cb460f8049 100644 --- a/app/views/alchemy/admin/partials/_search_form.html.erb +++ b/app/views/alchemy/admin/partials/_search_form.html.erb @@ -1,21 +1,23 @@ -<%- url ||= resource_url_proxy.url_for(action: 'index') -%> +<%- url ||= resource_url_proxy.url_for({ action: 'index' }.merge(search_filter_params.except(:q, :page))) -%> <%= search_form_for @query, url: url, class: 'search_form' do |f| %>
      - <%= render_icon('search') %> + <%= f.search_field resource_handler.search_field_name, class: 'search_input_field', placeholder: Alchemy.t(:search) %> <% local_assigns.fetch(:additional_query_fields, []).each do |field| %> - <%= f.hidden_field field %> + <%= f.hidden_field field, id: nil %> <% end %> - <%= link_to '', url, + <%= link_to render_icon(:times, size: 'xs'), url, class: 'search_field_clear', id: 'search_field_clear', title: Alchemy.t(:click_to_show_all), - style: params.fetch(:q, {}).fetch(resource_handler.search_field_name, '').present? ? 'display: block' : 'display: none' %> + style: search_filter_params.fetch(:q, {}).fetch(resource_handler.search_field_name, '').present? ? 'display: block' : 'display: none' %> <% local_assigns.fetch(:additional_params, []).each do |additional_param| %> - <%= hidden_field_tag additional_param, params[additional_param] %> + <%= hidden_field_tag additional_param, search_filter_params[additional_param], id: nil %> <% end %>
      <% end %> diff --git a/app/views/alchemy/admin/pictures/_archive.html.erb b/app/views/alchemy/admin/pictures/_archive.html.erb index aaeb3956f1f..36863e935db 100644 --- a/app/views/alchemy/admin/pictures/_archive.html.erb +++ b/app/views/alchemy/admin/pictures/_archive.html.erb @@ -1,53 +1,56 @@
      - <%= render :partial => 'filter_bar' %> -
      - <%= render :partial => 'tag_list' %> -
      + <%= render 'filter_bar' %> + + <% if Alchemy::Picture.tag_counts.any? %> +
      + <%= render 'tag_list' %> +
      + <% end %>
      -<%= form_tag delete_multiple_admin_pictures_path, :method => :delete do %> + +<%= form_tag delete_multiple_admin_pictures_path, method: :delete do %>
      -

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

      +

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

      <%= link_to( - render_icon('edit') + Alchemy.t("Edit"), + render_icon(:edit, size: 'xs') + Alchemy.t("Edit"), edit_multiple_admin_pictures_path, - :class => 'button with_icon', - :title => Alchemy.t('Edit multiple pictures'), - :id => 'edit_multiple_pictures', - :style => 'float: none' + class: 'button with_icon', + title: Alchemy.t('Edit multiple pictures'), + id: 'edit_multiple_pictures', + style: 'float: none' ) %> - <%= button_tag render_icon('destroy') + Alchemy.t("Delete"), 'data-alchemy-confirm' => { - :title => Alchemy.t(:please_confirm), - :message => Alchemy.t(:confirm_to_delete_images_from_server), - :ok_label => Alchemy.t("Yes"), - :cancel_label => Alchemy.t("No") - }.to_json, :class => 'button with_icon' %> + <%= button_tag render_icon(:minus, size: 'xs') + Alchemy.t("Delete"), 'data-alchemy-confirm' => { + title: Alchemy.t(:please_confirm), + message: Alchemy.t(:confirm_to_delete_images_from_server), + ok_label: Alchemy.t("Yes"), + cancel_label: Alchemy.t("No") + }.to_json, class: 'button with_icon' %>  <%= Alchemy.t(:or) %>  <%= link_to( - render_icon('delete-small') + Alchemy.t("Clear selection"), + render_icon(:times, size: 'xs') + Alchemy.t("Clear selection"), admin_pictures_path( - :q => params[:q], - :tagged_with => params[:tagged_with], - :size => params[:size], - :filter => params[:filter] + q: search_filter_params[:q], + tagged_with: search_filter_params[:tagged_with], + size: @size, + filter: search_filter_params[:filter] ), - :class => 'button with_icon', - :style => 'float: none' + class: 'secondary button with_icon', + style: 'float: none' ) %>
      - <% if @pictures.blank? and @recent_pictures.blank? and params[:q].blank? %> -
      - <%= render_icon('info') %> + <% if @pictures.blank? and @recent_pictures.blank? and search_filter_params[:q].blank? %> + <%= render_message do %> <%= Alchemy.t(:no_images_in_archive) %> -
      + <% end %> <% elsif @pictures.blank? and @recent_pictures.blank? %> -
      - <%= render_icon('info') %> + <%= render_message do %> <%= Alchemy.t(:no_search_results) %> -
      + <% end %> <% else %> - <%= render :partial => 'picture', :collection => @pictures %> - <%= paginate @pictures, theme: 'alchemy' %> + <%= render partial: 'picture', collection: @pictures %> <% end %>
      <% end %> + +<%= paginate @pictures, theme: 'alchemy' %> diff --git a/app/views/alchemy/admin/pictures/_archive_overlay.html.erb b/app/views/alchemy/admin/pictures/_archive_overlay.html.erb index a5fa07399bf..3a7d6417b45 100644 --- a/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +++ b/app/views/alchemy/admin/pictures/_archive_overlay.html.erb @@ -1,7 +1,7 @@
      <%= render 'filter_and_size_bar' %>
      -
      +
      <%= render 'filter_bar' %>
      diff --git a/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb b/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb index 565f13186e8..68a4bc79002 100644 --- a/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +++ b/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb @@ -6,26 +6,24 @@ file_attribute: 'image_file', in_dialog: true, redirect_url: alchemy.admin_pictures_path( - size: params[:size], + size: search_filter_params[:size], filter: 'last_upload', content_id: @content.try(:id), - element_id: @element.try(:id), - options: @options + element_id: @element.try(:id) ) %>
      <% end %>
      <%= link_to( - render_icon('zoom-out'), + render_icon('search-minus'), alchemy.admin_pictures_path({ size: "small", content_id: @content, element_id: @element, - q: params[:q], - options: @options.to_json, - filter: params[:filter], - tagged_with: params[:tagged_with] + q: search_filter_params[:q], + filter: search_filter_params[:filter], + tagged_with: search_filter_params[:tagged_with] }), remote: true, title: Alchemy.t(:small_thumbnails), @@ -34,15 +32,14 @@
      <%= link_to( - render_icon('zoom-equal'), + render_icon('search'), alchemy.admin_pictures_path({ size: "medium", content_id: @content, element_id: @element, - q: params[:q], - options: @options.to_json, - filter: params[:filter], - tagged_with: params[:tagged_with] + q: search_filter_params[:q], + filter: search_filter_params[:filter], + tagged_with: search_filter_params[:tagged_with] }), remote: true, title: Alchemy.t(:medium_thumbnails), @@ -51,15 +48,14 @@
      <%= link_to( - render_icon('zoom-in'), + render_icon('search-plus'), alchemy.admin_pictures_path({ size: "large", content_id: @content, element_id: @element, - q: params[:q], - options: @options.to_json, - filter: params[:filter], - tagged_with: params[:tagged_with] + q: search_filter_params[:q], + filter: search_filter_params[:filter], + tagged_with: search_filter_params[:tagged_with] }), remote: true, title: Alchemy.t(:big_thumbnails), diff --git a/app/views/alchemy/admin/pictures/_filter_bar.html.erb b/app/views/alchemy/admin/pictures/_filter_bar.html.erb index d952f88d3d6..42f24de1155 100644 --- a/app/views/alchemy/admin/pictures/_filter_bar.html.erb +++ b/app/views/alchemy/admin/pictures/_filter_bar.html.erb @@ -1,7 +1,5 @@ -<% params_to_keep = [:size, :tagged_with, :query, :element_id, :options, :content_id, :_] %> -
      -

      <%= Alchemy.t('Filter') %>

      +

      <%= Alchemy.t('Filter') %>

      <%= select_tag( 'picture_filter', options_for_select([ @@ -9,7 +7,7 @@ [Alchemy.t(:last_upload_only), 'last_upload'], [Alchemy.t(:recently_uploaded_only), 'recent'], [Alchemy.t(:without_tag), 'without_tag'] - ], params[:filter]), + ], search_filter_params[:filter]), :data => { :remote => !!request.xhr? }, :class => 'alchemy_selectbox' ) %> @@ -19,13 +17,12 @@ $(function() { $('#picture_filter').on('change', function(e) { var $this = $(this); - var url = '<%= alchemy.admin_pictures_path(merge_params_only(params_to_keep)).html_safe %>'; + var url = '<%= alchemy.admin_pictures_path(search_filter_params.except(:filter).to_h) %>'; if ($this.data('remote') === true) { $.get(url, {filter: $this.val()}, null, 'script'); } else { - Alchemy.pleaseWaitOverlay(); delimiter = url.match(/\?/) ? '&' : '?'; - window.location = url + delimiter + 'filter=' + $this.val(); + Turbolinks.visit(url + delimiter + 'filter=' + $this.val()); } return false; }); diff --git a/app/views/alchemy/admin/pictures/_form.html.erb b/app/views/alchemy/admin/pictures/_form.html.erb index 075837b5715..fb3758b4727 100644 --- a/app/views/alchemy/admin/pictures/_form.html.erb +++ b/app/views/alchemy/admin/pictures/_form.html.erb @@ -1,14 +1,13 @@ <%= alchemy_form_for [:admin, @picture] do |f| %> -

      <%= Alchemy.t('Edit image') %>

      <%= f.input :name %>
      <%= f.label :tag_list %> <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %> <%= Alchemy.t('Please seperate the tags with commata') %>
      - <%= hidden_field_tag :q, params[:q] %> - <%= hidden_field_tag :size, params[:size] %> - <%= hidden_field_tag :tagged_with, params[:tagged_with] %> - <%= hidden_field_tag :filter, params[:filter] %> + <%= hidden_field_tag :q, search_filter_params[:q] %> + <%= hidden_field_tag :size, @size %> + <%= hidden_field_tag :tagged_with, search_filter_params[:tagged_with] %> + <%= hidden_field_tag :filter, search_filter_params[:filter] %> <%= f.submit Alchemy.t(:save) %> <% end %> diff --git a/app/views/alchemy/admin/pictures/_infos.html.erb b/app/views/alchemy/admin/pictures/_infos.html.erb index 6a32e77a66f..e8b53cc474b 100644 --- a/app/views/alchemy/admin/pictures/_infos.html.erb +++ b/app/views/alchemy/admin/pictures/_infos.html.erb @@ -1,6 +1,5 @@
      -

      <%= Alchemy.t('Picture infos') %>

      <%= @picture.image_file_name %>

      @@ -17,14 +16,19 @@
      -

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

      +

      + <%= Alchemy.t(:this_picture_is_used_on_these_pages) %> +

      - <% if @pages.any? %> -
        - <% @pages.each do |page, essence_pictures| %> + <% if @assignments.any? %> +
          + <% @assignments.group_by(&:page).each do |page, essence_pictures| %> <% if page %>
        • -

          <%= link_to page.name, edit_admin_page_path(page) %>

          +

          + <%= render_icon 'file-alt' %> +

          <%= link_to page.name, edit_admin_page_path(page) %>

          +

            <% essence_pictures.group_by(&:element).each do |element, essence_pictures| %>
          • @@ -33,14 +37,22 @@ <% pictures = essence_pictures.collect do |e| e.content.name_for_label end.to_sentence %> - <%== Alchemy.t(:pictures_in_page, page: page_link, pictures: pictures) %> + <% if element.public? %> + <%= render_icon('window-maximize', style: 'regular') %> + <% else %> + <%= render_icon('window-close') %> + <% end %> +

            + <%== Alchemy.t(:pictures_in_page, page: page_link, pictures: pictures) %> + <%= Alchemy::Element.human_attribute_name(:trashed) if element.trashed? %> +

          • <% end %>
        • <% end %> <% end %> -
      + <% else %> <%= render_message do %> <%= Alchemy.t(:picture_not_in_use_yet) %> diff --git a/app/views/alchemy/admin/pictures/_overlay_picture_list.html.erb b/app/views/alchemy/admin/pictures/_overlay_picture_list.html.erb index 1ea5970953b..9847e059a45 100644 --- a/app/views/alchemy/admin/pictures/_overlay_picture_list.html.erb +++ b/app/views/alchemy/admin/pictures/_overlay_picture_list.html.erb @@ -1,11 +1,10 @@ <% if @pictures.empty? %> -
      - <%= render_icon('info') %> + <%= render_message do %> <%= Alchemy.t(:no_images_in_archive) %> -
      + <% end %> <% else %> <%= render partial: 'picture_to_assign', collection: @pictures, - locals: {options: @options, size: @size} %> - <%= paginate @pictures, theme: 'alchemy', remote: true %> + locals: {size: @size} %> + <%= paginate @pictures, theme: 'alchemy', remote: true, hide_per_page_select: true %> <% end %> diff --git a/app/views/alchemy/admin/pictures/_picture.html.erb b/app/views/alchemy/admin/pictures/_picture.html.erb index 97c38e9d7ff..ea8753679cf 100644 --- a/app/views/alchemy/admin/pictures/_picture.html.erb +++ b/app/views/alchemy/admin/pictures/_picture.html.erb @@ -5,15 +5,15 @@ <% if picture.deletable? && can?(:destroy, picture) %> <%= link_to_confirm_dialog( - "", + render_icon(:minus), Alchemy.t(:confirm_to_delete_image_from_server), alchemy.admin_picture_path( id: picture, - q: params[:q], + q: search_filter_params[:q], page: params[:page], - tagged_with: params[:tagged_with], - size: params[:size], - filter: params[:filter] + tagged_with: search_filter_params[:tagged_with], + size: @size, + filter: search_filter_params[:filter] ), { title: Alchemy.t('Delete image') @@ -22,7 +22,7 @@ <% end %> <% image = image_tag( - picture.url(size: preview_size(@size), flatten: true), + picture.url(size: preview_size(@size), flatten: true) || "alchemy/missing-image.svg", alt: picture.name, title: Alchemy.t(:zoom_image) ) %> @@ -31,11 +31,11 @@ image, alchemy.admin_picture_path( id: picture, - q: params[:q], + q: search_filter_params[:q], page: params[:page], - tagged_with: params[:tagged_with], - size: params[:size], - filter: params[:filter] + tagged_with: search_filter_params[:tagged_with], + size: @size, + filter: search_filter_params[:filter] ), class: 'thumbnail_background' ) %> diff --git a/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb b/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb index 065fe647a11..ddc92343d03 100644 --- a/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +++ b/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb @@ -1,14 +1,16 @@
      - <% action_url = create_or_assign_url(picture_to_assign, @options.to_json) %> <%= link_to( image_tag( - picture_to_assign.url(size: preview_size(size), flatten: true), + picture_to_assign.url(size: preview_size(size), flatten: true) || "alchemy/missing-image.svg", alt: picture_to_assign.name ), - action_url, + alchemy.assign_admin_essence_pictures_path( + picture_id: picture_to_assign.id, + content_id: @content + ), remote: true, onclick: '$(self).attr("href", "#").off("click"); return false', - method: @content.blank? ? 'post' : 'put', + method: 'put', title: Alchemy.t(:assign_image), class: 'thumbnail_background' ) %> diff --git a/app/views/alchemy/admin/pictures/_tag_list.html.erb b/app/views/alchemy/admin/pictures/_tag_list.html.erb index bc886041003..6bc99669a39 100644 --- a/app/views/alchemy/admin/pictures/_tag_list.html.erb +++ b/app/views/alchemy/admin/pictures/_tag_list.html.erb @@ -1,15 +1,15 @@ <% if Alchemy::Picture.tag_counts.any? %> -

      <%= Alchemy.t("Filter by tag") %>

      +

      <%= Alchemy.t("Filter by tag") %>

      <%= js_filter_field '.tag-list li' %>
        <%= render_tag_list('Alchemy::Picture') %>
      - <% if params[:tagged_with].present? %> + <% if search_filter_params[:tagged_with].present? %> <%= link_to( - render_icon('delete-small') + Alchemy.t('Remove tag filter'), - url_for(tag_list_params.delete_if { |k, v| k == "tagged_with" }.merge(action: 'index')), + render_icon(:times, size: 'xs') + Alchemy.t('Remove tag filter'), + url_for(search_filter_params.except(:tagged_with)), remote: request.xhr?, - class: 'button small with_icon please_wait' + class: 'secondary button small with_icon' ) %> <% end %> <% end %> diff --git a/app/views/alchemy/admin/pictures/edit_multiple.html.erb b/app/views/alchemy/admin/pictures/edit_multiple.html.erb index 0e51fa964fa..0c2d07d9555 100644 --- a/app/views/alchemy/admin/pictures/edit_multiple.html.erb +++ b/app/views/alchemy/admin/pictures/edit_multiple.html.erb @@ -1,17 +1,16 @@ <%= form_tag update_multiple_admin_pictures_path do %> - <%= hidden_field_tag :q, params[:q] %> - <%= hidden_field_tag :size, params[:size] %> - <%= hidden_field_tag :tagged_with, params[:tagged_with] %> - <%= hidden_field_tag :filter, params[:filter] %> + <%= hidden_field_tag :q, search_filter_params[:q] %> + <%= hidden_field_tag :size, @size %> + <%= hidden_field_tag :tagged_with, search_filter_params[:tagged_with] %> + <%= hidden_field_tag :filter, search_filter_params[:filter] %> <% @pictures.pluck(:id).each do |id| %> <%= hidden_field_tag "picture_ids[]", id %> <% end %> -
      - <%= render_icon :info %> -

      <%= Alchemy.t('You are about to edit many pictures at once') % {length: @pictures.size} %>

      -
      + <%= render_message do %> + <%= Alchemy.t('You are about to edit many pictures at once') % {length: @pictures.size} %> + <% end %>
      <%= label_tag :pictures_name, Alchemy.t('Name'), class: 'control-label' %> diff --git a/app/views/alchemy/admin/pictures/flush.js.erb b/app/views/alchemy/admin/pictures/flush.js.erb deleted file mode 100644 index 2aae5e9533f..00000000000 --- a/app/views/alchemy/admin/pictures/flush.js.erb +++ /dev/null @@ -1,2 +0,0 @@ -Alchemy.growl('<%= @notice %>'); -Alchemy.pleaseWaitOverlay(false); diff --git a/app/views/alchemy/admin/pictures/index.html.erb b/app/views/alchemy/admin/pictures/index.html.erb index b38bbf2ae4d..6f3808f0728 100644 --- a/app/views/alchemy/admin/pictures/index.html.erb +++ b/app/views/alchemy/admin/pictures/index.html.erb @@ -5,7 +5,7 @@ object: Alchemy::Picture.new, file_attribute: 'image_file', redirect_url: alchemy.admin_pictures_path( - size: params[:size], + size: @size, filter: 'last_upload' ) %>
      @@ -13,26 +13,41 @@
      <%= link_to( - render_icon('zoom-out'), - alchemy.admin_pictures_path(size: "small", q: params[:q]), + render_icon('search-minus'), + 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 please_wait" + class: "icon_button" ) %>
      <%= link_to( - render_icon('zoom-equal'), - alchemy.admin_pictures_path(size: "medium", q: params[:q]), + render_icon('search'), + 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 please_wait" + class: "icon_button" ) %>
      <%= link_to( - render_icon('zoom-in'), - alchemy.admin_pictures_path(size: "large", q: params[:q]), + render_icon('search-plus'), + 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 please_wait" + class: "icon_button" ) %>
      @@ -41,8 +56,8 @@
      <%= link_to( - render_icon('select_all'), - '', + render_icon('check-square', style: 'regular'), + nil, id: 'select_all_pictures', class: 'icon_button', 'data-alchemy-hotkey' => 'alt+a' @@ -55,12 +70,14 @@ additional_params: [:filter, :tagged_with, :size] %> <% end %> -
      -

      - <%= @pictures.total_count %> - <%= Alchemy::Picture.model_name.human(count: @pictures.total_count) %> - <%= Alchemy.t("picture_library.filter.#{params[:filter]}") if params[:filter].present? %> -

      +
      +
      +

      + <%= @pictures.total_count %> + <%= Alchemy::Picture.model_name.human(count: @pictures.total_count) %> + <%= Alchemy.t("picture_library.filter.#{search_filter_params[:filter]}") if search_filter_params[:filter].present? %> +

      +
      <%= render 'archive' %>
      diff --git a/app/views/alchemy/admin/pictures/index.js.erb b/app/views/alchemy/admin/pictures/index.js.erb index ffa5e7176ed..3f9bc0becc7 100644 --- a/app/views/alchemy/admin/pictures/index.js.erb +++ b/app/views/alchemy/admin/pictures/index.js.erb @@ -1,3 +1,2 @@ -Alchemy.pleaseWaitOverlay(false); $('#archive_all').html('<%= j render("archive") %>'); $('#filter_bar').replaceWith('<%= j render("filter_bar") %>'); diff --git a/app/views/alchemy/admin/pictures/show.html.erb b/app/views/alchemy/admin/pictures/show.html.erb index 2ea499eb92c..5a56c08ece3 100644 --- a/app/views/alchemy/admin/pictures/show.html.erb +++ b/app/views/alchemy/admin/pictures/show.html.erb @@ -1,34 +1,34 @@
      - <%= image_tag @picture.url(format: @picture.image_file_format) %> + <%= image_tag @picture.url || "alchemy/missing-image.svg" %>
      <% if @previous %> <%= link_to alchemy.admin_picture_path( id: @previous, - q: params[:q], + q: search_filter_params[:q], page: params[:page], - tagged_with: params[:tagged_with], - size: params[:size], - filter: params[:filter] + tagged_with: search_filter_params[:tagged_with], + size: @size, + filter: search_filter_params[:filter] ), class: "previous-picture", remote: true do %> - + <% end %> <% end %> <% if @next %> <%= link_to alchemy.admin_picture_path( id: @next, - q: params[:q], + q: search_filter_params[:q], page: params[:page], - tagged_with: params[:tagged_with], - size: params[:size], - filter: params[:filter] + tagged_with: search_filter_params[:tagged_with], + size: @size, + filter: search_filter_params[:filter] ), class: "next-picture", remote: true do %> - + <% end %> <% end %>
      @@ -39,5 +39,5 @@
      - +
      diff --git a/app/views/alchemy/admin/resources/_filter_bar.html.erb b/app/views/alchemy/admin/resources/_filter_bar.html.erb index b3c805fd42f..b0c87e45ddd 100644 --- a/app/views/alchemy/admin/resources/_filter_bar.html.erb +++ b/app/views/alchemy/admin/resources/_filter_bar.html.erb @@ -1,11 +1,9 @@ -<% params_to_keep = [:tagged_with, :q] %> -
      -

      <%= Alchemy.t('Filter') %>

      +

      <%= Alchemy.t('Filter') %>

      <%= select_tag( 'resource_filter', options_for_select( - resource_filter_select, params[:filter] + resource_filter_select, search_filter_params[:filter] ), include_blank: Alchemy.t(:all, scope: ['resources', resource_name, 'filters']), data: { remote: !!request.xhr? }, @@ -17,13 +15,12 @@ $(function() { $('#resource_filter').on('change', function(e) { var $this = $(this); - var url = '<%= resources_path(resource_handler.namespaced_resources_name, merge_params_only(params_to_keep)).html_safe %>'; + var url = '<%= resources_path(resource_handler.namespaced_resources_name, search_filter_params.except(:filter).to_h) %>'; if ($this.data('remote') === true) { $.get(url, {filter: $this.val()}, null, 'script'); } else { - Alchemy.pleaseWaitOverlay(); delimiter = url.match(/\?/) ? '&' : '?'; - window.location = url + delimiter + 'filter=' + encodeURIComponent($this.val()); + Turbolinks.visit(url + delimiter + 'filter=' + encodeURIComponent($this.val())); } return false; }); diff --git a/app/views/alchemy/admin/resources/_form.html.erb b/app/views/alchemy/admin/resources/_form.html.erb index 080cfd8ccb7..526e964e719 100644 --- a/app/views/alchemy/admin/resources/_form.html.erb +++ b/app/views/alchemy/admin/resources/_form.html.erb @@ -1,4 +1,4 @@ -<%= alchemy_form_for resource_instance_variable, url: resource_path(resource_instance_variable, current_location_params) do |f| %> +<%= alchemy_form_for resource_instance_variable, url: resource_path(resource_instance_variable, search_filter_params) do |f| %> <% resource_handler.editable_attributes.each do |attribute| %> <% if relation = attribute[:relation] %> <%= f.association relation[:name].to_sym, @@ -10,7 +10,7 @@ <% end %> <% end %> <% if f.object.respond_to?(:tag_list) %> -
      +
      <%= f.label :tag_list %> <%= render 'alchemy/admin/partials/autocomplete_tag_list', f: f %>
      diff --git a/app/views/alchemy/admin/resources/_per_page_select.html.erb b/app/views/alchemy/admin/resources/_per_page_select.html.erb new file mode 100644 index 00000000000..97864993914 --- /dev/null +++ b/app/views/alchemy/admin/resources/_per_page_select.html.erb @@ -0,0 +1,20 @@ +<%= form_tag url_for, method: :get, class: 'per-page-select-form' do |f| %> + <% search_filter_params.reject { |k, _| k == 'page' || k == 'per_page' }.each do |key, value| %> + <% if value.is_a? Hash %> + <% value.each do |k, v| %> + <%= hidden_field_tag "#{key}[#{k}]", v, id: nil %> + <% end %> + <% else %> + <%= hidden_field_tag key, value, id: nil %> + <% end %> + <% end %> + +<% end %> diff --git a/app/views/alchemy/admin/resources/_resource.html.erb b/app/views/alchemy/admin/resources/_resource.html.erb index 889befaf761..d0fdd7d1799 100644 --- a/app/views/alchemy/admin/resources/_resource.html.erb +++ b/app/views/alchemy/admin/resources/_resource.html.erb @@ -1,30 +1,25 @@
      <% if attribute[:type] == :boolean %> - <%= render_icon !!resource.send(attribute[:name]) %> + <%= resource.public_send(attribute[:name]) ? render_icon(:check) : nil %> <% else %> <%= render_attribute(resource, attribute) %> <% end %> - <% if can?(:destroy, resource) %> - <%= delete_button resource_path(resource, current_location_params) %> - <% end %> - <% if can?(:edit, resource) %> - <%= link_to_dialog( - '', - edit_resource_path(resource, current_location_params), + <% if can?(:destroy, resource) %> + <%= delete_button resource_path(resource, search_filter_params) %> + <% end %> + <% if can?(:edit, resource) %> + <%= link_to_dialog render_icon(:edit), + edit_resource_path(resource, search_filter_params), { title: Alchemy.t('Edit'), size: resource_window_size }, - { - class: 'icon edit', - title: Alchemy.t('Edit') - } - ) %> - <% end %> + title: Alchemy.t('Edit') %> + <% end %>
      - <% resource_handler.attributes.each do |attribute| %> + <% resource_handler.sorted_attributes.each do |attribute| %> <% end %> @@ -17,7 +17,7 @@ <%= render_resources %>
      <%= sort_link [:resource_url_proxy, @query], sortable_resource_header_column(attribute), resource_handler.model.human_attribute_name(attribute[:name]), - hide_indicator: true %> + default_order: attribute[:type].to_s =~ /date|time/ ? 'desc' : 'asc' %>
      -<% elsif params[:q].present? %> +<% elsif search_filter_params[:q].present? %>

      <%= Alchemy.t('Nothing found') %>

      <% end %> diff --git a/app/views/alchemy/admin/resources/_table_header.html.erb b/app/views/alchemy/admin/resources/_table_header.html.erb new file mode 100644 index 00000000000..f475bb10dcc --- /dev/null +++ b/app/views/alchemy/admin/resources/_table_header.html.erb @@ -0,0 +1,6 @@ +
      +

      + <%= resources_instance_variable.total_count %> + <%= resource_model.model_name.human(count: resources_instance_variable.total_count) %> +

      +
      diff --git a/app/views/alchemy/admin/resources/_tag_list.html.erb b/app/views/alchemy/admin/resources/_tag_list.html.erb index 59f5d4db5d9..2d010060981 100644 --- a/app/views/alchemy/admin/resources/_tag_list.html.erb +++ b/app/views/alchemy/admin/resources/_tag_list.html.erb @@ -1,15 +1,15 @@ -
      -

      <%= Alchemy.t("Filter by tag") %>

      +
      +

      <%= Alchemy.t("Filter by tag") %>

      <%= js_filter_field '.tag-list li' %>
        <%= render_tag_list(resource_model.name) %>
      - <% if params[:tagged_with].present? %> + <% if search_filter_params[:tagged_with].present? %> <%= link_to( - render_icon('delete-small') + Alchemy.t('Remove tag filter'), - url_for(tag_list_params.delete_if { |k, v| k == "tagged_with" }.merge(action: 'index')), + render_icon(:times, size: 'xs') + Alchemy.t('Remove tag filter'), + url_for(search_filter_params.except(:tagged_with)), remote: request.xhr?, - class: 'button small with_icon please_wait' + class: 'secondary button small with_icon' ) %> <% end %>
      diff --git a/app/views/alchemy/admin/resources/edit.html.erb b/app/views/alchemy/admin/resources/edit.html.erb index 8353b17f5e6..2d3436368bd 100644 --- a/app/views/alchemy/admin/resources/edit.html.erb +++ b/app/views/alchemy/admin/resources/edit.html.erb @@ -1 +1 @@ -<%= render :partial => 'form' %> \ No newline at end of file +<%= render 'form' %> diff --git a/app/views/alchemy/admin/resources/index.html.erb b/app/views/alchemy/admin/resources/index.html.erb index 52e215800d9..21e74133350 100644 --- a/app/views/alchemy/admin/resources/index.html.erb +++ b/app/views/alchemy/admin/resources/index.html.erb @@ -1,33 +1,34 @@ <% label_title = Alchemy.t("Create #{resource_name}", default: Alchemy.t('Create')) %> -<% toolbar( - buttons: [ - { - icon: 'create', - label: label_title, - url: new_resource_path, +<% content_for(:toolbar) do %> + <%= toolbar_button( + icon: :plus, + label: label_title, + url: new_resource_path, + title: label_title, + hotkey: 'alt+n', + dialog_options: { title: label_title, - hotkey: 'alt+n', - dialog_options: { - title: label_title, - size: resource_window_size - }, - if_permitted_to: [:create, resource_model] + size: resource_window_size }, - { - icon: 'download', - url: resource_url_proxy.url_for(action: 'index', format: 'csv', q: params[:q], sort: params[:sort]), - label: Alchemy.t(:download_csv), - title: Alchemy.t(:download_csv), - dialog: false, - loading_indicator: false, - if_permitted_to: [:index, resource_model] - } - ] -) %> + if_permitted_to: [:create, resource_model] + ) %> + <%= toolbar_button( + icon: :download, + url: resource_url_proxy.url_for(action: 'index', format: 'csv', q: search_filter_params[:q], sort: params[:sort]), + label: Alchemy.t(:download_csv), + title: Alchemy.t(:download_csv), + dialog: false, + if_permitted_to: [:index, resource_model] + ) %> + <%= render 'alchemy/admin/partials/search_form', additional_params: [ + resource_has_filters ? :filter : nil, + resource_has_tags ? :tagged_with : nil + ].compact %> +<% end %>
      - <%= resources_header %> + <%= render 'alchemy/admin/resources/table_header' %> <%= render 'table' %> <% if resource_has_tags || resource_has_filters %> diff --git a/app/views/alchemy/admin/resources/new.html.erb b/app/views/alchemy/admin/resources/new.html.erb index 8353b17f5e6..2d3436368bd 100644 --- a/app/views/alchemy/admin/resources/new.html.erb +++ b/app/views/alchemy/admin/resources/new.html.erb @@ -1 +1 @@ -<%= render :partial => 'form' %> \ No newline at end of file +<%= render 'form' %> diff --git a/app/views/alchemy/admin/sites/_form.html.erb b/app/views/alchemy/admin/sites/_form.html.erb new file mode 100644 index 00000000000..d2c0a2da8bd --- /dev/null +++ b/app/views/alchemy/admin/sites/_form.html.erb @@ -0,0 +1,8 @@ +<%= alchemy_form_for site, url: resource_path(site, search_filter_params) do |f| %> + <%= f.input :host, hint: resource_handler.help_text_for(name: :host)&.html_safe %> + <%= f.input :name %> + <%= f.input :public %> + <%= f.input :aliases, hint: resource_handler.help_text_for(name: :aliases), input_html: {rows: 4} %> + <%= f.input :redirect_to_primary_host %> + <%= f.submit Alchemy.t(:save) %> +<% end %> diff --git a/app/views/alchemy/admin/sites/edit.html.erb b/app/views/alchemy/admin/sites/edit.html.erb new file mode 100644 index 00000000000..c927df21a67 --- /dev/null +++ b/app/views/alchemy/admin/sites/edit.html.erb @@ -0,0 +1 @@ +<%= render 'form', site: @site %> diff --git a/app/views/alchemy/admin/sites/index.html.erb b/app/views/alchemy/admin/sites/index.html.erb index 1d29af5545f..bb4c337d2cb 100644 --- a/app/views/alchemy/admin/sites/index.html.erb +++ b/app/views/alchemy/admin/sites/index.html.erb @@ -1,23 +1,35 @@ <% label_title = Alchemy.t("Create #{resource_name}", default: Alchemy.t('Create')) %> -<% toolbar( - buttons: [ - { - icon: 'create', +<% content_for :toolbar do %> +
      + <%= toolbar_button( + icon: :plus, label: label_title, url: new_resource_path, title: label_title, hotkey: 'alt+n', dialog_options: { title: label_title, - size: resource_window_size + size: "430x415" }, if_permitted_to: [:create, Alchemy::Site] - } - ] -) %> + ) if @sites.any? %> +
      + <%= render 'alchemy/admin/partials/search_form' %> +<% end %>
      - <%= resources_header %> - <%= render 'table' %> + <% if @sites.any? %> + <%= render 'alchemy/admin/resources/table_header' %> + <%= render 'table' %> + <% elsif search_filter_params[:q].present? %> + <%= render_message { Alchemy.t('Nothing found') } %> + <% else %> +
      + <%= render_message do %> + <%== Alchemy.t('alchemy/site', scope: :no_resource_found) %> + <% end %> + <%= render 'form', site: Alchemy::Site.new(Alchemy::Config.get(:default_site).merge(public: true)) %> +
      + <% end %>
      diff --git a/app/views/alchemy/admin/sites/new.html.erb b/app/views/alchemy/admin/sites/new.html.erb new file mode 100644 index 00000000000..c927df21a67 --- /dev/null +++ b/app/views/alchemy/admin/sites/new.html.erb @@ -0,0 +1 @@ +<%= render 'form', site: @site %> diff --git a/app/views/alchemy/admin/styleguide/index.html.erb b/app/views/alchemy/admin/styleguide/index.html.erb new file mode 100644 index 00000000000..c34ff2fb5cb --- /dev/null +++ b/app/views/alchemy/admin/styleguide/index.html.erb @@ -0,0 +1,238 @@ +<% content_for :title do %> + <%= "Styleguide" %> +<% end %> + +<% content_for :javascript_includes do %> + +<% end %> + +<% content_for(:toolbar) do %> + <%= toolbar_button( + icon: :info, + label: 'Info', + url: alchemy.dashboard_info_path, + title: 'Info', + hotkey: 'alt+i', + dialog_options: { + title: 'Info', + size: "430x400" + }, + if_permitted_to: [:index, :alchemy_admin_styleguide] + ) %> +<% end %> + +

      AlchemyCMS Styleguide

      + +

      Typography

      + +

      Heading 1

      +

      Heading 2

      +

      Heading 3

      +

      Heading 4

      +
      Heading 5
      + +

      This is a paragraph, with a link, a keyboard shortcut and an inline code snippet.

      + +

      Messages

      + +

      Info Message

      + +<%= render_message do %> +

      Lorem ipsum dolor sit amet, consectetur adipiscing elit.

      +<% end %> + +

      Warning Message

      + +<%= render_message :warning do %> +

      Lorem ipsum dolor sit amet, consectetur adipiscing elit.

      +<% end %> + +

      Error Message

      + +<%= render_message :error do %> +

      Lorem ipsum dolor sit amet, consectetur adipiscing elit.

      +<% end %> + +

      Forms

      + +
      + <%= render_message do %> +

      This form is inside a .panel component.

      + <% end %> + + +
      + + +
      + +
      + + +
      + +
      + +
      +
      +
      + +
      +
      + +
      +
      +
      +
      + +
      + + +
      +
      + + +
      +
      + + + Error message +
      +
      + + +
      + +
      + +
      + + +
      +
      + +
      + + +
      + +
      + + + With hint +
      + +
      + + +
      + +
      + + +
      + +
      + +

      Tables

      + +

      Normal Table

      + + + + + + + + + + + + + + + + + +
      Table Heading 1Table Heading 2Table Heading 3
      Even table cell 1Even table cell 2Even table cell 3
      Odd table cell 1Odd table cell 2Odd table cell 3
      + +

      Table with icons

      + + + + + + + + + + + + + + + + + + + + + + + +
      Table Heading 1Table Heading 2Table Heading 3
      + + Even table cell 1Even table cell 2Even table cell 3 + + + + + + +
      + + Odd table cell 1Odd table cell 2Odd table cell 3 + + + + + + +
      + +

      Lists

      + +

      Unordered List

      + +
        +
      • List item
      • +
      • List item
      • +
      • List item
      • +
      + +

      Ordered List

      + +
        +
      1. List item
      2. +
      3. List item
      4. +
      5. List item
      6. +
      + +<%= content_for :javascripts do %> + +<% end %> diff --git a/app/views/alchemy/admin/tags/_tag.html.erb b/app/views/alchemy/admin/tags/_tag.html.erb index 8bc4871bbc2..08d253b627c 100644 --- a/app/views/alchemy/admin/tags/_tag.html.erb +++ b/app/views/alchemy/admin/tags/_tag.html.erb @@ -6,25 +6,21 @@
      <%= tag.taggings.count %> - <% if can?(:destroy, Alchemy::Tag) %> + <% if can?(:destroy, Alchemy::Tag) %> <%= delete_button admin_tag_path(tag), { message: Alchemy.t(:do_you_really_want_to_delete_this_tag?), - icon: 'tag_delete', + icon: :minus, title: Alchemy.t(:delete_tag) } %> - <% end %> - <% if can?(:edit, Alchemy::Tag) %> - <%= link_to_dialog '', + <% end %> + <% if can?(:edit, Alchemy::Tag) %> + <%= link_to_dialog render_icon(:edit), edit_admin_tag_path(tag), { title: Alchemy.t(:edit_tag), size: '360x410' }, - { - title: Alchemy.t(:edit_tag), - class: 'icon tag_edit' - } - %> - <% end %> + title: Alchemy.t(:edit_tag) %> + <% end %>
      - - - + + + @@ -43,7 +39,7 @@ <%= render_message do %>

      <%= Alchemy.t('No Tags found') %>

      - <% if params[:q].blank? %> + <% if search_filter_params[:q].blank? %>

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

      <% end %> <% end %> diff --git a/app/views/alchemy/admin/trash/index.html.erb b/app/views/alchemy/admin/trash/index.html.erb index 31efed15c57..03ead7a70f7 100644 --- a/app/views/alchemy/admin/trash/index.html.erb +++ b/app/views/alchemy/admin/trash/index.html.erb @@ -1,21 +1,21 @@ <%- if @elements.blank? -%> -
      class="info"> - <%= render_icon('info') %> +<%= render_message do %> <%= Alchemy.t('Your trash is empty') %> -
      +<% end %> <%- else -%> <%= render_message do %> <%= Alchemy.t('Drag an element over to the element window to restore it') %> <% end %>
      <%- @elements.each do |element| -%> - <%= render partial: 'alchemy/admin/elements/element', - object: element, - locals: { - draggable: @allowed_elements.any? do |e| - e['name'] == element.name || e.fetch('nestable_elements', []).include?(element.name) - end - } %> + <% draggable = @allowed_elements.any? { |e| + !e['fixed'] && e['name'] == element.name || element.name.in?(e.fetch('nestable_elements', [])) + } %> + <%= content_tag :div, + data: {'element-id' => element.id, 'element-name' => element.name}, + class: ['element-editor', draggable ? 'draggable' : 'not-draggable'] do %> + <%= render 'alchemy/admin/elements/element_header', element: element %> + <% end %> <%- end -%>

      diff --git a/app/views/alchemy/admin/uploader/_button.html.erb b/app/views/alchemy/admin/uploader/_button.html.erb index 0f1f5881455..08449168418 100644 --- a/app/views/alchemy/admin/uploader/_button.html.erb +++ b/app/views/alchemy/admin/uploader/_button.html.erb @@ -1,11 +1,11 @@ <%= form_for [:admin, object], html: {multipart: true, class: 'upload-button'} do |f| %> <%= f.file_field file_attribute, class: 'fileupload fileupload--field', multiple: true, - name: "#{f.object_name}[#{file_attribute}]" %> - <%= hidden_field_tag "#{f.object_name}[upload_hash]", Time.current.hash %> + name: "#{f.object_name}[#{file_attribute}]", tabindex: '-1' %> + <%= hidden_field_tag("#{f.object_name}[upload_hash]", Time.current.hash) if object.respond_to?(:upload_hash) %> <%= f.label file_attribute, data: {alchemy_hotkey: 'alt+n'} do %>

      - <%= render_icon :upload %> + <%= render_icon :upload %>
      <%= sort_link(@query, :name, hide_indicator: true) %><%= ActsAsTaggableOn::Tag.human_attribute_name(:taggings_types) %><%= sort_link(@query, :taggings_count, hide_indicator: true) %><%= sort_link(@query, :name) %><%= Gutentag::Tag.human_attribute_name(:taggings_types) %><%= sort_link(@query, :taggings_count) %>