From 00685cda77580d2abb240808ab2e7e60ed86b82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20Norl=C3=A9n?= Date: Sun, 5 Jul 2020 10:58:11 +0200 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 8432864ffb2d2e5d25a28add59ae01d22113c4e9 Author: Thomas von Deyen Date: Thu Jun 25 13:40:41 2020 +0200 Bump version to 5.0.0.beta2 commit afd7319c7a78991e5596f5f6cb81183a173ac5d4 Merge: 5bdf08ac2 9565b37c3 Author: Thomas von Deyen Date: Tue Jun 23 14:06:21 2020 +0200 Merge pull request #1884 from mamhoff/allow-page-factory-with-host-app-locale Language Factory: Create default language in host app's locale commit 9565b37c309f0bba1b26694041f78436c4a30eeb Author: Martin Meyerhoff Date: Tue Jun 23 11:53:00 2020 +0200 Reload before checking for related EssenceNodes The most recent version of AwesomeNestedSet stops reloading `self` when saving. This leads to `self` having inaccurate values for `lft` and `rgt` in our spec, making `node.self_and_descendants` sometimes not find all children. Luckily our specs found this. commit 36ee4974ef447570731f593c3363ddd3f380b8d4 Author: Martin Meyerhoff Date: Tue Jun 23 10:46:07 2020 +0200 Create default language in host app's locale Our default language factory will create a page in German. That's not great for apps that do not have German as an `available_locale`, as Alchemy now validates the language_code of the language. This change adds a new trait "german" to satisfy our existing test suite, but creates by default a language in the host apps first available locale. commit 5bdf08ac277ea9e6b78222b6436c25e03832832c Merge: f637e0272 bb645dbd2 Author: Thomas von Deyen Date: Tue Jun 23 12:59:38 2020 +0200 Merge pull request #1883 from AlchemyCMS/depfu/update/factory_bot_rails-6.0.0 [ruby] Upgrade factory_bot_rails to version 6.0.0 commit bb645dbd2d95419264c4aa829b83f60faccac0ba Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Sat Jun 20 03:50:52 2020 +0000 Update factory_bot_rails to version 6.0.0 commit f637e0272b4057738d5229529e83462aa70adb13 Merge: 2dd2929d6 ee69d49c9 Author: Thomas von Deyen Date: Tue Jun 16 13:50:43 2020 +0200 Merge pull request #1879 from tvdeyen/picture-thumbnail-sizes-constant Extract picture thumbnail sizes in a constant commit 2dd2929d644a8e436fb4933185e56741ef3f7b22 Merge: 3072ac95d 0ea5aa33b Author: Thomas von Deyen Date: Tue Jun 16 12:30:13 2020 +0200 Merge pull request #1880 from tvdeyen/fix-picture-library-size-filters Respect filter and tagging params in picture archive size buttons commit ee69d49c9d2245fcfc032d428a6e856706000024 Author: Thomas von Deyen Date: Tue Jun 16 12:25:05 2020 +0200 Disable Style/TrailingCommaInArguments check This is conflicting with Rufo commit 0ea5aa33b90df12be09412271636d196f3b21e6d Author: Thomas von Deyen Date: Tue Jun 16 12:17:34 2020 +0200 Respect filter and tagging params in picture archive size buttons commit 42642151c55017c8b7850aef16c649c56d5a059c Author: Thomas von Deyen Date: Mon Jun 15 15:25:04 2020 +0200 Extract picture thumbnail sizes in a constant We want to read those value from other classes and it makes sense to have them formalized in a constant anyway. commit 3072ac95d292dda76e3844b5ebcae6f22a5277d0 Merge: 1e7c025fe a7479daec Author: Thomas von Deyen Date: Mon Jun 15 12:21:28 2020 +0200 Merge pull request #1878 from tvdeyen/preprocess-images Configurable Image Preprocessor commit a7479daecc7a19b99d6a6ec29eca4f21474f281c Author: Thomas von Deyen Date: Mon Jun 15 10:19:15 2020 +0200 Make image preprocessor configurable You can define your own preprocessor class # config/initializers/alchemy.rb Alchemy::Picture.preprocessor_class = My::ImagePreprocessor commit e251ae529e186e7f12f737f14f5ad86d727b056b Author: Thomas von Deyen Date: Mon Jun 15 09:43:14 2020 +0200 Extract picture preprocessing into service class commit 1e7c025fe13ab753959837f74e1f1948cde7a022 Merge: 5cbe1554a 102d9c12c Author: Thomas von Deyen Date: Thu Jun 11 16:41:06 2020 +0200 Merge pull request #1877 from tvdeyen/preview-per-site Configure edit page preview per site commit 102d9c12c36d316b4a326206b7dfdc988097a9b9 Author: Thomas von Deyen Date: Thu Jun 11 14:26:26 2020 +0200 Use pages url path in page preview for external sites If the page preview renders an external site instead of the internal one we pass the url-path instead of the urlname so we make sure that root page urls are ` /` and the language code is prefixed. commit 5795da26f98cf231cd5f07c1ac588c3163deced6 Author: Thomas von Deyen Date: Thu Jun 11 14:24:25 2020 +0200 Allow previe url to be configured per site You can now configure the preview url per site. preview: My site name: host: https://www.my-static-site.com auth: username: <%= ENV["BASIC_AUTH_USERNAME"] %> password: <%= ENV["BASIC_AUTH_PASSWORD"] %> commit 5cbe1554abb2d572ca3ddbbcef8cc945e50e0b55 Author: Thomas von Deyen Date: Thu Jun 11 11:03:51 2020 +0200 v5.0.0.beta1 commit 87f8018c3532c454088c10db75d8fc41f9b3e0ff Merge: e3f94b4b5 751a3dfef Author: Thomas von Deyen Date: Thu Jun 11 10:23:13 2020 +0200 Merge pull request #1780 from alebacca89/fix_remove_picture_in_element fix remove correct image in edit element content picture commit 751a3dfefd3c0a50e921d91f5cd540799ce56f36 Author: Alessandro Baccanelli Date: Tue Apr 7 13:35:33 2020 +0200 fix remove correct image in edit element content picture commit e3f94b4b5ff43d268e1aeec772f72aaf13d20adb Merge: 69fefff28 87b264835 Author: Thomas von Deyen Date: Wed Jun 10 12:25:43 2020 +0200 Merge pull request #1876 from tvdeyen/page-tree-fix-sorting Fix Page tree sorting after root page removal commit 87b264835136852a5aa0832d1ea75a04a0172817 Author: Thomas von Deyen Date: Wed Jun 10 11:23:45 2020 +0200 Fix page sorting after root page removal Since we removed the root page we need to adjust the sortable items class. commit 59bc15274ed37a65be9f21a268c4fb06946a218e Author: Thomas von Deyen Date: Wed Jun 10 11:23:22 2020 +0200 Format page sorter with Prettier commit 69fefff28bfeb517c946aa98d8ca63acd122b58b Author: Thomas von Deyen Date: Fri Jun 5 13:50:35 2020 +0200 Update 5.0 changelog commit b3c794e8ca7c723d4e74f054747107708dc3f87c Author: Thomas von Deyen Date: Fri Jun 5 13:45:47 2020 +0200 Update CHANGELOG for 4.6 release commit 71bf393817262b72c78d3c384e72c9b773d268c7 Merge: 9a1990623 e81587836 Author: Thomas von Deyen Date: Fri Jun 5 13:35:01 2020 +0200 Merge pull request #1868 from tvdeyen/remove-attach-to-menu Remove Page#visible commit e8158783681626acf466d180142aa64706365333 Author: Thomas von Deyen Date: Thu Jun 4 00:40:20 2020 +0200 Simplify urlname generating There is no need to rebuild every ancestors slug since we already have the parents urlname we can append the slug to. commit cb7de340e8bf3906012a2df2ba4b2afd0be046cf Author: Thomas von Deyen Date: Thu May 21 08:57:20 2020 +0200 Remove visible from existing pages commit e5eabe1003a81910c5e36cd1384c6276e178bfc3 Author: Thomas von Deyen Date: Thu May 21 08:47:48 2020 +0200 Remove usages of visible attribute from Page This is a legacy attribute that was used to show the page in the navigation. Since the render_navigation helper has been removed and the feature has been replaced with a much better feature (Menus) we do not need this attribute anymore. Also this attribute was used to generate the url of a page only taking visible parents into account. This also has been removed. commit 3730ddd0cc9aa77dd67dbe70104c45a15bc817a9 Author: Thomas von Deyen Date: Thu Jun 4 00:04:28 2020 +0200 Fix page status in page info dialog commit 44c4a3f59c7df0fb834602ac7a644bd2b054cef3 Author: Thomas von Deyen Date: Wed Jun 3 00:48:21 2020 +0200 Remove attach_to_menu feature The attach-page-to-menu button in the page form was meant as a transitional feature for introducing menus. Now that they have been established and managing menus has been refined we can remove this rather complex feature. commit 9a19906237670a0d9550279fda6849fa42d9f8f7 Merge: b60c75fa5 daa78e32f Author: Thomas von Deyen Date: Fri Jun 5 11:41:17 2020 +0200 Merge pull request #1874 from tvdeyen/upgrader-fixes 5.0 Upgrader fixes commit daa78e32f9d83d1594cdf924e741aff614e6cba0 Author: Thomas von Deyen Date: Thu Jun 4 23:37:40 2020 +0200 Fix the add menu_type migration This migration fails being rolled back because change_column is not reversible. Using `change_column_null` resolves that issue. Also it does not make sense to write into a column we delete any way. commit 9f18b952c03f61ea29a84ef2191146ab710e423a Author: Thomas von Deyen Date: Thu Jun 4 23:32:12 2020 +0200 Fix the order of upgrade tasks If we run the dedecated 5.0 upgrade tasks before installing the migrations it will skip installing them. Since it is not harmfil to run the migrations before hand and this was the case for the upgrader all the time this fix should be considered fine. commit a67c6325199dd130503622a7e0f0e29ee2eb8c4c Author: Thomas von Deyen Date: Thu Jun 4 23:30:33 2020 +0200 Do not seed while upgrading The Alchemy seeder does not need to run. commit b60c75fa5e1e6ca630849d68e6fcf5176c245164 Merge: dc242d9d2 a43c8ded6 Author: Thomas von Deyen Date: Thu Jun 4 09:14:55 2020 +0200 Merge pull request #1863 from AlchemyCMS/depfu/update/rubocop-0.85.0 [ruby] Upgrade rubocop to version 0.85.0 commit dc242d9d2d754b1eab154723f474340d0f792948 Merge: c4b6d5916 52387882b Author: Thomas von Deyen Date: Thu Jun 4 09:12:55 2020 +0200 Merge pull request #1872 from tvdeyen/remove-url_nesting-config Remove url_nesting config commit 52387882b82b888d6cfd6fe1a8823b138e2a23a3 Author: Thomas von Deyen Date: Wed Jun 3 23:54:32 2020 +0200 Remove url_nesting config commit c4b6d591675f86c34f6c67699089c6c70f3257a3 Merge: bf9ecd0dd c8fcfbab7 Author: Thomas von Deyen Date: Wed Jun 3 21:43:00 2020 +0200 Merge pull request #1871 from AlchemyCMS/depfu/update/sassc-2.4.0 [ruby] Upgrade sassc to version 2.4.0 commit c8fcfbab716a7c2e771dbd28178b804c62b6da6c Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Wed Jun 3 14:46:19 2020 +0000 Update sassc to version 2.4.0 commit bf9ecd0ddb69d78cbdde765cebdfe10e868b9885 Merge: 5d630bc5c 51a7d8277 Author: Thomas von Deyen Date: Wed Jun 3 10:44:06 2020 +0200 Merge pull request #1867 from tvdeyen/4.6-backports Cherry picks changes from 4.6-stable that needs to be ported over to master Fix the sitemap wrapper height (#1861) Update Urlname translation (#1857) Introduce Page#url_path (#1859) Show url_path in page tree (#1856) Use depth for page tree serializer root_or_leaf (#1864) commit 5d630bc5c43a80fb0348458fc63d5a82b982565a Author: Alexander ADAM Date: Wed Jun 3 09:18:24 2020 +0200 fix GitHub Actions spelling (#1869) commit 51a7d82779206158f9a44d7e87760fd12305b816 Author: Thomas von Deyen Date: Tue May 19 00:16:44 2020 +0200 Reduce horizontal padding of small button commit bb0c50d9ba8e20d00398b4ab8dbe9f2e666d9ade Author: Thomas von Deyen Date: Tue May 19 00:12:21 2020 +0200 Add active button style commit 86e59ccf530b0c2b621488d72e321e898b8cd79d Author: Thomas von Deyen Date: Tue Jun 2 23:45:26 2020 +0200 Use depth for page tree serializer root_or_leaf (#1864) Depth is a cached attribute on the page that does not need calculation and will always be correct even if the root page has been removed. This commit helps to migrate to Alchemy 5 without making further changes to the page tree serializer. Without that change page trees that already have been migrated to "root-page-less" Alchemy 5 will have page toggle switches disappear in the first level. commit 961fdc732d9e4f9767c4ce5b5f8ff7b74a8295ea Author: Thomas von Deyen Date: Tue Jun 2 22:34:33 2020 +0200 Show url name in Page tree (#1856) * Show url_path of page in admin page tree The url_path of a page is useful information. We should always display it not only if the page redirects to external. * Format sitemap.scss with Prettier * Change the sitemap urlname background color The old color was too prominent for that information. * Show a legend on top of admin page trees * Use a left aligned column layout for page tree * Slighty more space for node page name and url * Eager load associated records in page tree serializer Now that we need the language and its site we should eager load them in the page tree serializer. Also the locker of a page should be eager loaded. On a site with ~300 pages loading the page tree went from 722ms to 221ms on my machine. commit c801a36837260bc8e24d822816b722795bd483cc Author: Thomas von Deyen Date: Tue Jun 2 00:49:51 2020 +0200 Introduce page.url_path and use it for alchemyPageSelect (#1859) * Introduce page url_path service class Use this class to generate the url_path to a page. It takes several circumstances into account 1. It returns just a slash for language root pages of the default langauge 2. It returns a url path with a leading slash for regular pages 3. It returns a url path with a leading slash and language code prefix for pages not having the default language 4. It returns a url path with a leading slash and the language code for language root pages of a non-default language * Return the page url_path with API responses We want to use the newly introduced url_path attribute on the page selector in the admin. It in general makes sense to have the correct url to a page from the API anyway. * Remove @url_prefix This variable is not used anymore. since we generate the url in page links via the API response. * Use Page#url_path in alchemyPageSelect We do not need to care about adding leading slashes in the template anymore and it fixes a lot of missing features for multi language pages. * Use page.url_path in node.url Instead of having (buggy) page url generating code in the node model, we use the newly introduced page.url_path attribute that takes all necessities into account. * Appease Rubocop * Use page.url_path in page link dialog And with that fix bugs for wrongly generated urls in multi language environments. commit 68c7e0256c7cfe62ec5f33cda539c0574cd861d0 Author: Thomas von Deyen Date: Thu May 28 00:44:06 2020 +0200 Update urlname translation to URL-Path That's what this part of the URL is called according to RFC 1738 commit 40ce6aeb72d46630b54f0c7cb53aa67995c4df60 Author: Thomas von Deyen Date: Thu May 28 00:43:02 2020 +0200 Use Slug as urlname field label The value entered in this field is the slug. commit e75927fd6f2fefd0e04726d8bc27e1ed80830a78 Author: Thomas von Deyen Date: Sat May 30 00:03:47 2020 +0200 Fix the sitemap wrapper height We want the loading spinner centered. commit 9105536eea6ee8bbab573b8f6e38d6933ef77f35 Merge: 2767a66b3 21a102843 Author: Thomas von Deyen Date: Wed Jun 3 00:01:15 2020 +0200 Merge pull request #1844 from tvdeyen/remove-url_nesting Remove url_nesting configuration and always create nested urls commit 2767a66b32a720770f2568a9d3e882723c608d7f Author: Thomas von Deyen Date: Tue Jun 2 23:30:52 2020 +0200 Use apt update instead of apt-get in GH action (#1866) We have some weird connection error on GH actions while installing the database headers. Let's try to use a more modern tool... commit a43c8ded663e1a90df43bebbc94e656e0024c3d4 Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Tue Jun 2 17:22:53 2020 +0000 Update rubocop to version 0.85.0 commit 21a102843bc10944fb98afd0ffef6939d342748f Author: Thomas von Deyen Date: Thu May 21 10:10:49 2020 +0200 Remove the url_nesting config option And always created nested urls. commit 2f15aba37b41b53b001cc36a41109e2636301133 Author: Thomas von Deyen Date: Thu May 21 09:16:57 2020 +0200 Deprecate url_nesting configuration commit de453c5d453373c522d6f0d0553bd85f9f5fd752 Author: Thomas von Deyen Date: Wed May 27 11:14:10 2020 +0200 Set proper nested set scope on page (#1837) * Set proper nested set scope on page * Ensure layoutpages factory never creates a parent With the awesome nested set scope set to language and layoutpage there is no parent with the same scope to move the newly created page to. * Ensure page factory sets language from parent If the page has a parent set we use that as the language. We must avoid to call the parent method in the factory, that's why we use the @cached_attributes here. * Fix page language_root factory for when language is nil We might set language to nil in tests explicitly. * Refactor page specs after factory changes Now that the page factory sets the language to the parent if necessary we can rewrite some specs. commit c7dad35cc326b873944be5a7d23d3e4db2c647d4 Author: Thomas von Deyen Date: Wed May 27 10:43:59 2020 +0200 Remove active_record_5_1? (#1854) This check is not neessary anymore since we dropped support for Rails 5.0 and 5.1 commit 50f19ea5776c656b686c03c81ff4ae11a08e30d0 Author: Thomas von Deyen Date: Tue May 26 20:26:29 2020 +0200 Use Alchemy npm package instead of hacking webpacker (#1853) * Move npm package code into dedicated folder We want to get rid of webpacker * Remove rails/webpacker With that we can dramatically simplify the jest setup * Raise npm package version * Install alchemy npm package into host app Instead of compiling the pack in a hacky way we install the alchemy/admin npm package as dependency of the host apps package.json and copy the admin entry point into the host apps packs folder. This reduces the amount of hacks we need in Alchemy in order to make webpack work. * Remove webpacker hacks Now that we publish a npm package and install alchemy/admin as host app dependency we can remove our webpacker hacks. * Remove and ignore webpacker files from dummy app We want to run the install generator during test setup. * Run Alchemy installer during test setup That way we ensure that everything is setup correctly and the generator is actually tested in a sort. * Cache node_modules of dummy app on CI This gem has two node_modules folder now, on in the dummy app and one for the jest specs. commit 913d3dde369ef7b3db4c3bba4f15858af1c1b1cb Author: Thomas von Deyen Date: Tue May 26 15:20:22 2020 +0200 Fix node select ES5 syntax (#1851) * Do not use object destructering in node select This is not supported in ES5 * Format node select with Prettier commit 14625b7749f0207db1535b72505aa1bb3f9ca604 Author: Martin Meyerhoff Date: Tue May 26 10:29:34 2020 +0200 Remove male sign after emoji (#1849) The wizard can be gender neutral. commit 5397826824c36f796c13dead3c90c92e387058b2 Author: Martin Meyerhoff Date: Tue May 26 10:29:17 2020 +0200 Run yarn:install after installing webpacker in install generator (#1850) Without doing this, the app will not find Alchemy's webpack packs. commit 84ac809011420bd3715c9d06f3f5fbd30d68ea10 Author: Mikael NorlĂ©n Date: Mon May 25 23:22:54 2020 +0200 Add indifferent access to default options in encoded_image (#1840) commit fe36e20658e12702d04bba5b316ebc7bc2d491d7 Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Mon May 25 23:19:48 2020 +0200 Update rubocop to version 0.84.0 (#1845) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> commit f7ffece2bbf92b85fa01410f6b09979ec41e51a0 Author: Martin Meyerhoff Date: Mon May 25 13:21:59 2020 +0200 Do not use ES6 Syntax in Node Selector (#1846) The previous version of this code fails in host apps that do not have Uglifier in Harmony mode. Let's stick with ES5 instead. commit 73963dd6c8171bc9a0c784768fd51e5c22933c23 Author: Martin Meyerhoff Date: Tue May 19 09:12:11 2020 +0200 Translated root menus (#1825) * Add menu_type column to nodes This column holds information about what type this menu is - a footer or a main menu, for example. * Make menu_type available in the admin This adds the following functionality: - Choose a menu type when creating a new root node - Change the menu name for a root node if that's necessary or desired via `edit` - Restrict the options for menu type depending on which nodes on a language are already present * Render child nodes directly in nodes partial The "options" local is often not given, and the `node_partial_name` key is never set any longer, as it is always `node.to_partial_path`.` * Render footer in Dummy App This can be used to test Alchemy::EssenceNodes from the Dummy app. commit 6a08040e7fd55b01e29671f27ae879d34788f641 Author: Martin Meyerhoff Date: Mon May 18 20:55:37 2020 +0200 Fix deleting an EssenceNode from a content (#1834) We can leverage Rails' functionality here. commit 3e4d476d712d10adcadfebe5858c4a8e85fdb8f2 Author: Martin Meyerhoff Date: Mon May 18 20:54:58 2020 +0200 Use Rails standards for deleting pages from EssencePage (#1833) Rails offers ways for setting relations, let's use them. commit 2cb3228ddbe4e19ae92257e56074df92d449afb6 Author: Martin Meyerhoff Date: Mon May 18 20:53:27 2020 +0200 Install Webpacker in install generator (#1835) We use this generator in the Alchemy ecosystem, and the dummy apps of gems like alchemy-devise or alchemy-solidus need webpacker installed to function. commit 02983a039b6cf63118847607a476a86481a5fa30 Author: Martin Meyerhoff Date: Thu May 14 14:52:15 2020 +0200 Scope has one site (#1832) * Make alchemy_pages.layoutpage null: false We want to have an actual Boolean and no nil values. * Scope Language Root Page to Non-Layoutpages Layoutpages can not be root pages. commit 8ff8ca28854ceefe8f56825c22124fc08bfe3ce0 Author: Martin Meyerhoff Date: Thu May 14 14:23:52 2020 +0200 Add error flash to resource controller (#1827) This adds an error flash to the resource controller if some action does not succeed. commit f25b8e542e8cc24f354e077f50ea546fc97c1c1e Author: Martin Meyerhoff Date: Thu May 14 10:37:23 2020 +0200 Render nodes (#1831) * Allow directly rendering nodes We can use Rails' model rendering mechanism to render nodes by changing `to_partial_path` to not point to the wrapper, but to the node itself. This yields a few simplifications. * Adapt generator partials to not use node_partial_path This is now `node.to_partial_path`. * Actually render node when rendering an EssenceNode commit f58f4f98bb496a647be060c9ecc75387861f04a9 Author: Martin Meyerhoff Date: Wed May 13 23:43:33 2020 +0200 Add errors when node cant be deleted (#1828) * Add error before destroying node with attached essence Nodes that are used on e.g. content pages can not be simply destroyed. This commit adds an error message so the user knows what's going on. * Do not destroy nodes with dependent nodes that are in use on a page This moves the validation up in the file so that awesome_nested_set does not run its `delete_all` callback before we validate that it can do that. It also extends the validation such that we collect all the pages that are referenced in the subtree below this node. commit 729cd21fba8e23e04edd0b7a3fdac05607ab816f Author: Martin Meyerhoff Date: Wed May 13 16:53:11 2020 +0200 Fix Association between Nodes and EssenceNodes (#1826) There was a typo here resulting in Nodes being associated to themselves in an impossible way. commit 0eec65a43daf01b4d699f48b3bd93011ff9a0792 Author: Martin Meyerhoff Date: Mon May 11 09:36:14 2020 +0200 Add a quick Node select (#1821) * Add a quick Node select This faster select also shows the ancestors of each node, such that one is not confused between nodes that have similar names. * Fix Copy-Paste error that tries assigning Pages to EssenceNodes Prior to this commit, saving an EssenceNode by ID would never work... * Serialize Node with Ancestors for initial selection * Center initial selection in Alchemy::EssenceNode select Maybe we should look at using Flexbox for this so we can center things more easily. * Add specs for NodesController Index Action It's always good to have specs. commit 30c2aa88ca0452a23c8e473f0761da1937960d23 Author: Martin Meyerhoff Date: Fri May 8 23:13:51 2020 +0200 Add has_one association for root page (#1820) This should allow us to preload better, and I believe Rails will also cache the association if necessary. commit 81ae05fe123fa6fd740dbe0024e2f7fde2a55d7b Author: Thomas von Deyen Date: Fri May 8 22:53:12 2020 +0200 Use rails root in install generator (#1822) * Move generators out of rails folder * Use Rails app root to join paths Without that it is not always ensured that the path is the hosts apps root path. * Refactor elements generator naming validation commit 5a86ae51723dbdd69d8eddc22b9a5a7959f08b1f Author: Thomas von Deyen Date: Wed May 6 23:30:07 2020 +0200 Make page.language mandatory (#1818) Now that we removed all hidden "systempages" we can ensure that a language is always present on a page. commit 6fccc3b9905d448ec924244814e032061a63b2ca Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Wed May 6 17:54:46 2020 +0200 Update babel-jest to version 26.0.1 (#1819) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> commit 4b43ce8ac7422203c85343500357a632b3cfe9e4 Author: Thomas von Deyen Date: Wed May 6 14:38:15 2020 +0200 Remove root page (#1817) * Remove Page#systempage? * Remove Page.root We do not need a single root page. All pages that share the same language are a nested set. * Format page_scopes.rb * Format pages controller spec file * Add upgrader task to remove root page * Rubocop -a commit dc7b25d3228e4e9a5c8054abe8103bc43c53ff24 Merge: 19aa55f5c 2196e8002 Author: Martin Meyerhoff Date: Wed May 6 13:13:52 2020 +0200 Merge pull request #1807 from mamhoff/remove-site-id-from-nodes Remove Site ID from nodes commit 19aa55f5cc9b6aabbb73ef016f8501bf57911249 Author: Thomas von Deyen Date: Wed May 6 13:02:56 2020 +0200 Remove Page.ancestors_for (#1813) Its only used by the render_breadcrumb helper and that could just use the self_and_ancestors scope. commit 2196e8002e9f522e17c85782d02a2f1d92e9690f Author: Martin Meyerhoff Date: Wed May 6 10:22:17 2020 +0200 Add Down Migration commit 8b7cd55cb347fcb733704f2e4c6a944937139555 Author: Thomas von Deyen Date: Wed May 6 00:57:37 2020 +0200 Fix page unlock page icon replacement (#1816) Without this strong selector it happens that more icons get appended than necessary. Also rewritten in VanillaJS commit c7261e8d640d374d9760cbf9d482c25c64473932 Author: Thomas von Deyen Date: Wed May 6 00:57:17 2020 +0200 Invoke rake task in upgrader instead of system call (#1815) commit 6d9e5d824a2ff0023fb4dc474e37e5071312ff8d Author: Thomas von Deyen Date: Wed May 6 00:56:57 2020 +0200 Remove old 4.4 upgrader class (#1814) commit 12c97fea27f7a54f86cb07231a8c801bc127fe9e Author: Thomas von Deyen Date: Tue May 5 20:53:40 2020 +0200 Remove layout root pages (#1812) * Remove layout root page We do not need this hidden page. All pages that share the same language and have `layoutpage` set to true are the layoutpages for the current language. * Pass the language in new page form We want to be sure that the language we instantiate the page with gets passed with the form * Only set the language from parent if present And use the current page, not the default (that is the current anyway if no current language has been explicitely set). * Add upgrader for removing layout root pages commit 9a67a364060a860c40963d5c25c76c63d89e9475 Author: Thomas von Deyen Date: Tue May 5 17:52:22 2020 +0200 Use timestamps method in migration files (#1811) Instead of an database dependent datetime column, we should use the Rails database agnostic timestamps method for creating the columns. commit afd266d332bc58921fdbde1b04353fc44aa441ef Author: Thomas von Deyen Date: Tue May 5 17:49:51 2020 +0200 Remove legacy element serializer (#1810) This serializer has been introduced 4 years ago with the release of Alchemy 3.1. This can be safely removed now. commit 34b1721f7bf608eb885097a2480105d21e5f42d0 Author: Thomas von Deyen Date: Tue May 5 17:12:08 2020 +0200 Remove timestamps from essences and contents (#1809) * Remove timestamps from essence and contents tables These records always ever get created within an element. So, this information is duplicated. * Format attachment and picture model classes with Rufo * Touch element after essence has been saved The content will rarely be cached inside of an already cached element. * Format attachment and picture spec files * Touch elements after attachment or picture has been saved Do not touch contents any more. We removed the timestamps as they only get created in the context of an element and the element gets cached, not the single content. * Rubocop -a commit c206a2ad00ce015f29eb7c7a46234a5925187698 Author: Thomas von Deyen Date: Tue May 5 12:24:50 2020 +0200 Remove stamper from content model (#1808) The content model always ever is created within an element and it does not make sense to have this information twice. commit 09abf7b016db47538868b50781159fe74e453d09 Author: Martin Meyerhoff Date: Tue May 5 12:21:43 2020 +0200 Add Alchemy::Language.has_many :nodes (#1806) We need those association declarations for better preloading. commit ba1acf5db71fbe74acf5b9bf4b092d4020cc9b71 Author: Thomas von Deyen Date: Tue May 5 09:18:54 2020 +0200 Drop Rails 5.0 and 5.1 support (#1805) * Drop Rails 5.0 and 5.1 support These Rails versions are not officially supported anymore and we want to be able to better support MySQL with bigint foreign keys. * Use references in migrations for foreign keys That way the database adapter will chose the correct column type for foreign keys. Adds better support for MySQL. Closes #1796 commit a6f36404755e4db3bc69cb3cff25073aeeffdc7f Author: Thomas von Deyen Date: Tue May 5 08:53:04 2020 +0200 Remove acts_as_list from Content (#1798) Closes #1793 commit 4b60c7b4ba8bbbc8fb01e3671f1095a1164db965 Author: Martin Meyerhoff Date: Mon May 4 23:10:24 2020 +0200 Remove Site ID from nodes Nodes can get their Site through their language. commit 9301a0610fd2b8fa4a35c20d2d2ca205e820076f Author: Thomas von Deyen Date: Mon May 4 23:31:27 2020 +0200 Remove enforce_ssl (#1804) Removes the enforce_ssl configuration and the SSLProtection module. Please use the Rails build in config.force_ssl or your Webserver for that. commit 09f3041cfd26c9956fd0a9373cad75d75e8c5618 Author: Thomas von Deyen Date: Mon May 4 23:30:39 2020 +0200 Remove stamper from essences (#1802) We do not need the creator and updater information on the essences. We already have them on the elements. commit 7596a4898658093b293396478138b954e2bf28bc Author: Thomas von Deyen Date: Thu Apr 30 11:36:11 2020 +0200 Make the preview url configurable (#1803) * Make the preview url configurable By default Alchemy uses its internal page preview renderer, but you can configure it now to be any URL instead. Basic Auth is supported as well. Example config/alchemy/config.yml preview: host: https://www.my-static-site.com auth: username: <%= ENV["BASIC_AUTH_USERNAME"] %> password: <%= ENV["BASIC_AUTH_PASSWORD"] %> This is great for static sites. * Satisfy the Hound commit 1e95b3199f7eb9665e544e7594a65072aaeabfa3 Author: Thomas von Deyen Date: Wed Apr 29 21:54:56 2020 +0200 Use Rufo to format all files in a consistent way (#1799) * Rubocop -a * Fix rubocop config * [Rufo] Add trailing commas * [Rufo] Use double quotes for strings * Add Rufo as local dev dependency commit 7a79235489560a9d4dc272d2917af69bf5a8b882 Author: Martin Meyerhoff Date: Tue Apr 28 12:52:14 2020 +0200 Add EssenceNode (#1792) This Essence can be used to add a menu and its children to an Element. commit 489181e6efb857ea6bf2c9f308c99749b4841943 Merge: 5093856a3 28876c6fa Author: Thomas von Deyen Date: Mon Apr 27 08:34:43 2020 +0200 Merge branch 'fix-email' commit 5093856a3b680be0a2c4cffb78d13bc93c9c4d99 Author: Thomas von Deyen Date: Mon Apr 27 08:28:08 2020 +0200 Convert NodeTree into ES6 (#1782) * Install sortablejs npm package * Revert "Add favicon to assets manifest" The favicon is already linked with the images folder. * Convert NodeTree and utils into ES6 modules And make the `on` delegated event handler work with multiple base nodes * Fix base node folding * Extract ajax and events into own utils modules commit addc2116db78ccc4d0e005d9cc8c08a919eeb486 Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Wed Apr 22 11:25:37 2020 +0200 Update rubocop to version 0.82.0 (#1785) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> commit 37d7737e546ee924591684ae6c14a23e0944e8d1 Author: Thomas von Deyen Date: Wed Apr 22 10:40:49 2020 +0200 Use 2.5.7 of code climate coverage reporter GH action (#1790) * Use 2.5.7 of code climate coverage reporter GH action * Raise mysql service health check retries This service takes for ever to start commit 4433ede860fc51fc10f77f609acfc19959853ee2 Author: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Date: Wed Apr 22 09:51:36 2020 +0200 Update sassc to version 2.3.0 (#1787) Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> commit 5d8ae3bea34d6558cdd3956d4bf01e539f8bdcfd Author: Thomas von Deyen Date: Fri Apr 17 08:51:14 2020 +0200 Fix regular icons (#1784) * Fix Font Awesome regular icons It's `asset_url` not `assets_url`. * Update icons to Font Awesome 5.13.0 Aka. the Corona release commit e24643227dab5346182273ceddb66c882dfffa1d Author: Thomas von Deyen Date: Tue Apr 7 21:38:24 2020 +0200 Add Webpacker (#1775) * Install webpacker and configure own instance Following the official webpacker engines guide * Add demo admin JS pack Something to work with during testing * Serve and compile Alchemy packs in host app Serve the Alchemy packs via Rack::Static if public file server is enabled (ie. during development) and enhance the yarn:install and assets:precompile tasks so that the Alchemy packs get compiled asd well. Copy over the files into host apps public/ folder afterwards. * Ensure that we run yarn install before installing Alchemy * Ensure to also serve packs in tests * Install node modules on GH CI * Do not use our own webpacker instance in page preview We want the webpacker instance of the host app in the preview frame. * Use webpacker 5 * Add a prettier config * Add a webpack-dev-server proxy * Only enhance rake tasks that are present If you install webpacker into a fresh Rails app there is not yarn:install task yet. * Update Babel config and add core-js * Add and configure Jest * Run Jest specs in GH CI * Convert i18n module into ES6 * Add favicon to assets Sprockets complains that we need this file * Enable Jest coverage reports for code climate * Ignore unknown window messages Now that we have Webpack installed the dev server emits messages on the window as well. Lets just ignore them. commit 28876c6fab6382b27698bcc8246667b73ac37930 Author: Thomas von Deyen Date: Sun Feb 23 15:12:55 2020 +0100 Fix email in gemspec --- .github/workflows/ci.yml | 45 +- .github/workflows/stale.yml | 2 +- .gitignore | 22 +- .prettierrc | 6 + .rubocop.yml | 16 +- CHANGELOG.md | 90 ++ Gemfile | 45 +- README.md | 3 +- Rakefile | 1 + alchemy_cms.gemspec | 11 +- app/assets/javascripts/alchemy/admin.js | 6 +- .../alchemy/alchemy.base.js.coffee | 3 +- .../alchemy/alchemy.element_editors.js.coffee | 2 - .../alchemy/alchemy.i18n.js.coffee | 32 - .../alchemy/alchemy.link_dialog.js.coffee | 10 +- .../javascripts/alchemy/alchemy.node_tree.js | 66 - .../alchemy/alchemy.page_sorter.js | 34 +- .../alchemy/alchemy.translations.js.coffee | 29 - .../javascripts/alchemy/alchemy.utils.js | 45 - app/assets/javascripts/alchemy/node_select.js | 39 + .../javascripts/alchemy/templates/index.js | 1 + .../javascripts/alchemy/templates/node.hbs | 16 + .../javascripts/alchemy/templates/page.hbs | 2 +- app/assets/stylesheets/alchemy/_mixins.scss | 5 +- .../stylesheets/alchemy/_variables.scss | 4 +- app/assets/stylesheets/alchemy/admin.scss | 1 + app/assets/stylesheets/alchemy/lists.scss | 8 - .../stylesheets/alchemy/node-select.scss | 43 + app/assets/stylesheets/alchemy/nodes.scss | 4 +- app/assets/stylesheets/alchemy/sitemap.scss | 84 +- .../alchemy/admin/attachments_controller.rb | 16 +- .../alchemy/admin/base_controller.rb | 23 +- .../alchemy/admin/clipboard_controller.rb | 8 +- .../alchemy/admin/contents_controller.rb | 2 +- .../alchemy/admin/dashboard_controller.rb | 18 +- .../alchemy/admin/elements_controller.rb | 40 +- .../admin/essence_pictures_controller.rb | 6 +- .../alchemy/admin/languages_controller.rb | 8 +- .../alchemy/admin/layoutpages_controller.rb | 2 +- .../alchemy/admin/nodes_controller.rb | 7 +- .../alchemy/admin/pages_controller.rb | 66 +- .../alchemy/admin/pictures_controller.rb | 28 +- .../alchemy/admin/resources_controller.rb | 29 +- .../alchemy/admin/sites_controller.rb | 4 +- .../alchemy/admin/tags_controller.rb | 6 +- .../alchemy/admin/trash_controller.rb | 12 +- .../alchemy/api/base_controller.rb | 4 +- .../alchemy/api/contents_controller.rb | 8 +- .../alchemy/api/elements_controller.rb | 16 +- .../alchemy/api/nodes_controller.rb | 38 +- .../alchemy/api/pages_controller.rb | 25 +- .../alchemy/attachments_controller.rb | 10 +- app/controllers/alchemy/base_controller.rb | 16 +- .../alchemy/messages_controller.rb | 28 +- app/controllers/alchemy/pages_controller.rb | 22 +- .../concerns/alchemy/admin/archive_overlay.rb | 4 +- .../alchemy/admin/current_language.rb | 2 +- .../alchemy/admin/uploader_responses.rb | 10 +- .../concerns/alchemy/legacy_page_redirects.rb | 8 +- .../concerns/alchemy/page_redirects.rb | 2 +- .../concerns/alchemy/site_redirects.rb | 2 +- app/decorators/alchemy/content_editor.rb | 10 +- app/decorators/alchemy/element_editor.rb | 16 +- .../alchemy/admin/attachments_helper.rb | 12 +- app/helpers/alchemy/admin/base_helper.rb | 70 +- app/helpers/alchemy/admin/contents_helper.rb | 6 +- app/helpers/alchemy/admin/elements_helper.rb | 4 +- app/helpers/alchemy/admin/essences_helper.rb | 8 +- app/helpers/alchemy/admin/form_helper.rb | 2 +- .../alchemy/admin/navigation_helper.rb | 46 +- app/helpers/alchemy/admin/pages_helper.rb | 24 +- app/helpers/alchemy/admin/pictures_helper.rb | 10 +- app/helpers/alchemy/admin/tags_helper.rb | 14 +- app/helpers/alchemy/base_helper.rb | 14 +- app/helpers/alchemy/elements_block_helper.rb | 4 +- app/helpers/alchemy/elements_helper.rb | 28 +- app/helpers/alchemy/pages_helper.rb | 44 +- app/helpers/alchemy/url_helper.rb | 2 +- app/mailers/alchemy/base_mailer.rb | 2 +- app/mailers/alchemy/messages_mailer.rb | 2 +- app/models/alchemy/attachment.rb | 20 +- app/models/alchemy/base_record.rb | 6 +- app/models/alchemy/content.rb | 50 +- app/models/alchemy/content/factory.rb | 10 +- app/models/alchemy/element.rb | 76 +- app/models/alchemy/element/definitions.rb | 6 +- .../alchemy/element/element_contents.rb | 12 +- .../alchemy/element/element_essences.rb | 6 +- app/models/alchemy/element/presenters.rb | 4 +- app/models/alchemy/element_to_page.rb | 2 +- app/models/alchemy/essence_boolean.rb | 4 +- app/models/alchemy/essence_date.rb | 4 +- app/models/alchemy/essence_file.rb | 6 +- app/models/alchemy/essence_html.rb | 4 +- app/models/alchemy/essence_link.rb | 4 +- app/models/alchemy/essence_node.rb | 18 + app/models/alchemy/essence_page.rb | 19 +- app/models/alchemy/essence_picture.rb | 14 +- app/models/alchemy/essence_picture_view.rb | 8 +- app/models/alchemy/essence_richtext.rb | 4 +- app/models/alchemy/essence_select.rb | 4 +- app/models/alchemy/essence_text.rb | 2 - app/models/alchemy/language.rb | 39 +- app/models/alchemy/language/code.rb | 8 +- app/models/alchemy/legacy_page_url.rb | 2 +- app/models/alchemy/message.rb | 6 +- app/models/alchemy/node.rb | 43 +- app/models/alchemy/page.rb | 162 +-- app/models/alchemy/page/fixed_attributes.rb | 3 +- app/models/alchemy/page/page_elements.rb | 76 +- app/models/alchemy/page/page_naming.rb | 77 +- app/models/alchemy/page/page_natures.rb | 15 +- app/models/alchemy/page/page_scopes.rb | 52 +- app/models/alchemy/page/url_path.rb | 64 + app/models/alchemy/picture.rb | 68 +- app/models/alchemy/picture/preprocessor.rb | 26 + app/models/alchemy/picture/transformations.rb | 14 +- app/models/alchemy/picture/url.rb | 8 +- app/models/alchemy/site/layout.rb | 4 +- .../concerns/alchemy/content_touching.rb | 24 - app/models/concerns/alchemy/touch_elements.rb | 24 + app/serializers/alchemy/content_serializer.rb | 3 - .../alchemy/essence_boolean_serializer.rb | 6 +- .../alchemy/essence_date_serializer.rb | 6 +- .../alchemy/essence_file_serializer.rb | 6 +- .../alchemy/essence_html_serializer.rb | 6 +- .../alchemy/essence_link_serializer.rb | 6 +- .../alchemy/essence_picture_serializer.rb | 8 +- .../alchemy/essence_richtext_serializer.rb | 6 +- .../alchemy/essence_select_serializer.rb | 6 +- .../alchemy/essence_text_serializer.rb | 8 +- .../alchemy/legacy_element_serializer.rb | 15 - app/serializers/alchemy/node_serializer.rb | 2 + app/serializers/alchemy/page_serializer.rb | 3 +- .../alchemy/page_tree_serializer.rb | 23 +- .../alchemy/admin/layoutpages/index.html.erb | 8 +- app/views/alchemy/admin/nodes/_form.html.erb | 37 +- app/views/alchemy/admin/nodes/_node.html.erb | 6 +- app/views/alchemy/admin/nodes/index.html.erb | 3 +- .../pages/_create_language_form.html.erb | 8 - app/views/alchemy/admin/pages/_form.html.erb | 3 +- .../alchemy/admin/pages/_menu_fields.html.erb | 33 - .../admin/pages/_new_page_form.html.erb | 1 + app/views/alchemy/admin/pages/_page.html.erb | 9 +- .../alchemy/admin/pages/_page_infos.html.erb | 4 - .../alchemy/admin/pages/_sitemap.html.erb | 6 + app/views/alchemy/admin/pages/edit.html.erb | 2 +- app/views/alchemy/admin/pages/info.html.erb | 6 +- app/views/alchemy/admin/pages/unlock.js.erb | 19 +- .../alchemy/admin/pictures/index.html.erb | 21 +- .../essences/_essence_node_editor.html.erb | 27 + .../essences/_essence_node_view.html.erb | 1 + .../essences/_essence_page_editor.html.erb | 2 +- app/views/alchemy/pages/show.rss.builder | 4 +- app/views/layouts/alchemy/admin.html.erb | 1 + babel.config.js | 12 + bin/rails | 8 +- config/alchemy/config.yml | 40 +- config/initializers/assets.rb | 2 +- config/initializers/dragonfly.rb | 2 +- config/initializers/mini_profiler.rb | 4 +- config/initializers/simple_form.rb | 12 +- config/locales/alchemy.en.yml | 22 +- config/routes.rb | 48 +- config/spring.rb | 4 +- .../20200226213334_alchemy_four_point_four.rb | 146 +- ...0423073425_create_alchemy_essence_nodes.rb | 11 + ...0200504210159_remove_site_id_from_nodes.rb | 28 + ...anguage_id_foreign_key_to_alchemy_pages.rb | 8 + ...11113603_add_menu_type_to_alchemy_nodes.rb | 27 + ...4091507_make_page_layoutpage_null_false.rb | 6 + ...73500_remove_visible_from_alchemy_pages.rb | 24 + lib/alchemy/admin/locale.rb | 2 +- lib/alchemy/admin/preview_url.rb | 85 ++ lib/alchemy/auth_accessors.rb | 14 +- lib/alchemy/cache_digests/template_tracker.rb | 8 +- lib/alchemy/config.rb | 27 +- lib/alchemy/configuration_methods.rb | 1 + lib/alchemy/controller_actions.rb | 2 +- lib/alchemy/deprecation.rb | 2 +- lib/alchemy/elements_finder.rb | 10 +- lib/alchemy/engine.rb | 24 +- lib/alchemy/essence.rb | 30 +- lib/alchemy/filetypes.rb | 10 +- lib/alchemy/forms/builder.rb | 8 +- lib/alchemy/hints.rb | 2 +- lib/alchemy/i18n.rb | 2 +- lib/alchemy/modules.rb | 24 +- lib/alchemy/name_conversions.rb | 10 +- lib/alchemy/page_layout.rb | 23 +- lib/alchemy/paths.rb | 2 +- lib/alchemy/permissions.rb | 12 +- lib/alchemy/resource.rb | 26 +- lib/alchemy/resources_helper.rb | 24 +- lib/alchemy/routing_constraints.rb | 2 +- lib/alchemy/seeder.rb | 39 +- lib/alchemy/shell.rb | 10 +- lib/alchemy/ssl_protection.rb | 32 - lib/alchemy/tasks/tidy.rb | 6 +- .../test_support/essence_shared_examples.rb | 144 +- .../factories/attachment_factory.rb | 10 +- .../test_support/factories/content_factory.rb | 12 +- .../factories/dummy_user_factory.rb | 14 +- .../test_support/factories/element_factory.rb | 18 +- .../factories/essence_file_factory.rb | 6 +- .../factories/essence_page_factory.rb | 6 +- .../factories/essence_picture_factory.rb | 8 +- .../factories/essence_text_factory.rb | 6 +- .../factories/language_factory.rb | 31 +- .../test_support/factories/node_factory.rb | 14 +- .../test_support/factories/page_factory.rb | 42 +- .../test_support/factories/picture_factory.rb | 10 +- .../test_support/factories/site_factory.rb | 12 +- lib/alchemy/test_support/shared_contexts.rb | 8 +- .../test_support/shared_uploader_examples.rb | 6 +- lib/alchemy/tinymce.rb | 26 +- lib/alchemy/upgrader.rb | 12 +- lib/alchemy/upgrader/five_point_zero.rb | 33 +- lib/alchemy/upgrader/four_point_four.rb | 52 - .../upgrader/tasks/element_views_updater.rb | 8 +- .../tasks/harden_gutentag_migrations.rb | 6 +- lib/alchemy/version.rb | 2 +- lib/alchemy_cms.rb | 101 +- lib/{rails => }/generators/alchemy/base.rb | 8 +- .../alchemy/elements/elements_generator.rb | 21 +- .../alchemy/elements/templates/view.html.erb | 0 .../alchemy/elements/templates/view.html.haml | 0 .../alchemy/elements/templates/view.html.slim | 0 .../alchemy/essence/essence_generator.rb | 6 +- .../alchemy/essence/templates/editor.html.erb | 0 .../alchemy/essence/templates/view.html.erb | 0 .../alchemy/install/files/_article.html.erb | 0 .../alchemy/install/files/_standard.html.erb | 0 .../alchemy/install/files/alchemy.en.yml | 0 .../alchemy/install/files/alchemy_admin.js | 1 + .../generators/alchemy/install/files/all.css | 0 .../generators/alchemy/install/files/all.js | 0 .../install/files/application.html.erb | 0 .../alchemy/install/files/article.scss | 0 .../alchemy/install/install_generator.rb | 110 ++ .../alchemy/install/templates/dragonfly.rb.tt | 0 .../alchemy/install/templates/elements.yml.tt | 0 .../alchemy/install/templates/menus.yml.tt | 0 .../install/templates/page_layouts.yml.tt | 0 .../alchemy/menus/menus_generator.rb | 4 +- .../alchemy/menus/templates/node.html.erb | 5 +- .../alchemy/menus/templates/node.html.haml | 5 +- .../alchemy/menus/templates/node.html.slim | 5 +- .../alchemy/menus/templates/wrapper.html.erb | 2 +- .../alchemy/menus/templates/wrapper.html.haml | 2 +- .../alchemy/menus/templates/wrapper.html.slim | 2 +- .../alchemy/module/module_generator.rb | 4 +- .../alchemy/module/templates/ability.rb.tt | 0 .../alchemy/module/templates/controller.rb.tt | 0 .../module/templates/module_config.rb.tt | 0 .../page_layouts/page_layouts_generator.rb | 6 +- .../page_layouts/templates/layout.html.erb | 0 .../page_layouts/templates/layout.html.haml | 0 .../page_layouts/templates/layout.html.slim | 0 .../site_layouts/site_layouts_generator.rb | 4 +- .../site_layouts/templates/layout.html.erb | 0 .../site_layouts/templates/layout.html.haml | 0 .../site_layouts/templates/layout.html.slim | 0 .../alchemy/views/views_generator.rb | 12 +- .../alchemy/install/install_generator.rb | 65 - lib/tasks/alchemy/convert.rake | 38 - lib/tasks/alchemy/db.rake | 3 +- lib/tasks/alchemy/install.rake | 4 +- lib/tasks/alchemy/tidy.rake | 16 +- lib/tasks/alchemy/upgrade.rake | 46 +- package.json | 45 + package/admin.js | 14 + package/src/__tests__/i18n.spec.js | 70 + package/src/i18n.js | 48 + package/src/node_tree.js | 72 + package/src/translations.js | 32 + package/src/utils/__tests__/ajax.spec.js | 124 ++ package/src/utils/__tests__/events.spec.js | 38 + package/src/utils/ajax.js | 48 + package/src/utils/events.js | 16 + .../admin/attachments_controller_spec.rb | 106 +- .../alchemy/admin/base_controller_spec.rb | 38 +- .../admin/clipboard_controller_spec.rb | 48 +- .../admin/dashboard_controller_spec.rb | 46 +- .../alchemy/admin/elements_controller_spec.rb | 74 +- .../admin/essence_files_controller_spec.rb | 62 +- .../admin/essence_pictures_controller_spec.rb | 136 +- .../admin/languages_controller_spec.rb | 58 +- .../admin/layoutpages_controller_spec.rb | 28 +- .../alchemy/admin/nodes_controller_spec.rb | 40 +- .../alchemy/admin/pictures_controller_spec.rb | 184 +-- .../admin/resources_controller_spec.rb | 75 +- .../alchemy/admin/sites_controller_spec.rb | 28 +- .../alchemy/admin/tags_controller_spec.rb | 30 +- .../alchemy/admin/translations_spec.rb | 24 +- .../alchemy/admin/trash_controller_spec.rb | 2 +- .../alchemy/api/contents_controller_spec.rb | 62 +- .../alchemy/api/elements_controller_spec.rb | 78 +- .../alchemy/api/pages_controller_spec.rb | 225 +-- .../alchemy/attachments_controller_spec.rb | 16 +- .../alchemy/base_controller_spec.rb | 20 +- .../alchemy/elements_controller_spec.rb | 8 +- .../alchemy/messages_controller_spec.rb | 80 +- .../alchemy/on_page_layout_mixin_spec.rb | 88 +- .../alchemy/pages_controller_spec.rb | 145 +- .../decorators/alchemy/content_editor_spec.rb | 32 +- .../decorators/alchemy/element_editor_spec.rb | 50 +- spec/dummy/Rakefile | 2 +- .../dummy/app/controllers/login_controller.rb | 2 +- spec/dummy/app/models/dummy_model.rb | 2 +- spec/dummy/app/models/dummy_user.rb | 2 +- spec/dummy/app/models/event.rb | 12 +- .../app/views/alchemy/elements/_menu.html.erb | 5 + .../menus/footer_navigation/_node.html.erb | 5 +- .../menus/footer_navigation/_wrapper.html.erb | 4 +- .../_node.html.erb | 5 +- .../alchemy/menus/main_menu/_wrapper.html.erb | 6 + .../menus/main_navigation/_wrapper.html.erb | 6 - .../app/views/layouts/application.html.erb | 6 +- spec/dummy/bin/bundle | 4 +- spec/dummy/bin/rails | 6 +- spec/dummy/bin/rake | 4 +- spec/dummy/bin/setup | 16 +- spec/dummy/bin/update | 16 +- spec/dummy/config.ru | 2 +- spec/dummy/config/alchemy/elements.yml | 5 + spec/dummy/config/alchemy/page_layouts.yml | 3 +- .../initializers/content_security_policy.rb | 23 +- ...0423073425_create_alchemy_essence_nodes.rb | 1 + ...0200504210159_remove_site_id_from_nodes.rb | 1 + ...anguage_id_foreign_key_to_alchemy_pages.rb | 1 + ...11113603_add_menu_type_to_alchemy_nodes.rb | 1 + ...4091507_make_page_layoutpage_null_false.rb | 1 + ...73500_remove_visible_from_alchemy_pages.rb | 1 + spec/dummy/db/schema.rb | 107 +- .../assets/javascripts/alchemy/admin/all.js | 2 +- .../assets/stylesheets/alchemy/admin/all.css | 2 +- spec/factories.rb | 8 +- spec/features/admin/admin_layout_spec.rb | 8 +- .../attachment_assignment_overlay_spec.rb | 2 +- spec/features/admin/dashboard_spec.rb | 34 +- .../admin/edit_elements_feature_spec.rb | 18 +- .../admin/language_tree_feature_spec.rb | 32 +- .../features/admin/languages_features_spec.rb | 50 +- .../admin/legacy_page_url_management_spec.rb | 50 +- spec/features/admin/link_overlay_spec.rb | 28 +- .../admin/locale_select_feature_spec.rb | 26 +- .../admin/locked_page_feature_spec.rb | 18 +- spec/features/admin/menus_features_spec.rb | 28 +- .../admin/modules_integration_spec.rb | 12 +- .../features/admin/navigation_feature_spec.rb | 18 +- .../admin/page_creation_feature_spec.rb | 20 +- .../admin/page_destroy_feature_spec.rb | 14 +- .../admin/page_editing_feature_spec.rb | 109 +- .../admin/page_sorting_feature_spec.rb | 12 +- .../admin/picture_assignment_overlay_spec.rb | 6 +- .../admin/picture_library_integration_spec.rb | 36 +- .../admin/resources_integration_spec.rb | 54 +- .../admin/site_select_feature_spec.rb | 22 +- spec/features/admin/tinymce_feature_spec.rb | 14 +- spec/features/navigation_spec.rb | 2 +- spec/features/page_feature_spec.rb | 70 +- spec/features/page_redirects_spec.rb | 73 +- spec/features/page_seeder_spec.rb | 38 +- spec/features/security_spec.rb | 18 +- spec/features/user_seeder_spec.rb | 20 +- .../alchemy/admin/attachments_helper_spec.rb | 10 +- .../helpers/alchemy/admin/base_helper_spec.rb | 86 +- .../alchemy/admin/contents_helper_spec.rb | 32 +- .../alchemy/admin/elements_helper_spec.rb | 18 +- .../alchemy/admin/essences_helper_spec.rb | 16 +- .../alchemy/admin/navigation_helper_spec.rb | 168 +-- .../alchemy/admin/pages_helper_spec.rb | 24 +- .../alchemy/admin/pictures_helper_spec.rb | 12 +- .../helpers/alchemy/admin/tags_helper_spec.rb | 34 +- spec/helpers/alchemy/base_helper_spec.rb | 70 +- .../alchemy/elements_block_helper_spec.rb | 40 +- spec/helpers/alchemy/elements_helper_spec.rb | 64 +- spec/helpers/alchemy/pages_helper_spec.rb | 108 +- spec/helpers/alchemy/url_helper_spec.rb | 44 +- spec/libraries/admin/preview_url_spec.rb | 140 ++ spec/libraries/auth_accessors_spec.rb | 30 +- spec/libraries/config_spec.rb | 91 +- spec/libraries/configuration_methods_spec.rb | 26 +- spec/libraries/controller_actions_spec.rb | 18 +- spec/libraries/elements_finder_spec.rb | 116 +- spec/libraries/i18n_spec.rb | 40 +- .../scoped_pagination_url_helper_spec.rb | 6 +- spec/libraries/logger_spec.rb | 10 +- spec/libraries/modules_spec.rb | 64 +- spec/libraries/page_layout_spec.rb | 64 +- spec/libraries/paths_spec.rb | 12 +- spec/libraries/permissions_spec.rb | 56 +- spec/libraries/resource_spec.rb | 76 +- spec/libraries/resources_helper_spec.rb | 88 +- spec/libraries/shell_spec.rb | 42 +- spec/libraries/template_tracker_spec.rb | 54 +- spec/libraries/tinymce_spec.rb | 70 +- spec/libraries/userstamp_spec.rb | 6 +- spec/mailers/alchemy/messages_mailer_spec.rb | 10 +- spec/models/alchemy/attachment_spec.rb | 116 +- spec/models/alchemy/content_spec.rb | 207 ++- spec/models/alchemy/element_spec.rb | 382 +++--- spec/models/alchemy/element_to_page_spec.rb | 6 +- spec/models/alchemy/essence_boolean_spec.rb | 2 +- spec/models/alchemy/essence_date_spec.rb | 4 +- spec/models/alchemy/essence_file_spec.rb | 10 +- spec/models/alchemy/essence_html_spec.rb | 10 +- spec/models/alchemy/essence_link_spec.rb | 4 +- spec/models/alchemy/essence_node_spec.rb | 23 + spec/models/alchemy/essence_page_spec.rb | 46 +- spec/models/alchemy/essence_picture_spec.rb | 118 +- .../alchemy/essence_picture_view_spec.rb | 90 +- spec/models/alchemy/essence_richtext_spec.rb | 8 +- spec/models/alchemy/essence_select_spec.rb | 4 +- spec/models/alchemy/essence_text_spec.rb | 52 +- spec/models/alchemy/folded_page_spec.rb | 2 +- spec/models/alchemy/language_spec.rb | 121 +- spec/models/alchemy/legacy_page_url_spec.rb | 10 +- spec/models/alchemy/message_spec.rb | 18 +- spec/models/alchemy/node_spec.rb | 103 +- .../alchemy/page/fixed_attributes_spec.rb | 58 +- spec/models/alchemy/page/url_path_spec.rb | 68 + spec/models/alchemy/page_spec.rb | 1202 +++++++---------- spec/models/alchemy/picture_spec.rb | 188 +-- spec/models/alchemy/picture_url_spec.rb | 80 +- spec/models/alchemy/site_spec.rb | 88 +- spec/models/alchemy/tag_spec.rb | 18 +- spec/models/dummy_model_spec.rb | 4 +- spec/rails_helper.rb | 50 +- .../alchemy/admin/contents_controller_spec.rb | 10 +- .../alchemy/admin/pages_controller_spec.rb | 381 +++--- .../alchemy/admin/resources_requests_spec.rb | 28 +- .../alchemy/admin/site_requests_spec.rb | 12 +- .../alchemy/api/nodes_controller_spec.rb | 101 +- .../alchemy/legacy_page_redirecting_spec.rb | 28 +- .../alchemy/page_request_caching_spec.rb | 38 +- spec/requests/alchemy/site_requests_spec.rb | 10 +- spec/requests/alchemy/sitemap_spec.rb | 48 +- spec/routing/api_routing_spec.rb | 46 +- spec/routing/routing_spec.rb | 90 +- .../alchemy/element_serializer_spec.rb | 28 +- .../alchemy/node_serializer_spec.rb | 17 +- spec/spec_helper.rb | 12 +- spec/support/capybara_helpers.rb | 18 +- spec/support/custom_news_elements_finder.rb | 2 +- spec/support/hint_examples.rb | 18 +- spec/support/transformation_examples.rb | 30 +- spec/views/admin/pictures/show_spec.rb | 12 +- .../admin/elements/element_view_spec.rb | 32 +- .../_main_navigation_entry.html.erb_spec.rb | 36 +- .../essences/essence_boolean_editor_spec.rb | 24 +- .../essences/essence_boolean_view_spec.rb | 16 +- .../essences/essence_date_editor_spec.rb | 6 +- spec/views/essences/essence_date_view_spec.rb | 20 +- .../essences/essence_file_editor_spec.rb | 28 +- spec/views/essences/essence_file_view_spec.rb | 36 +- spec/views/essences/essence_html_view_spec.rb | 10 +- .../essences/essence_link_editor_spec.rb | 8 +- spec/views/essences/essence_link_view_spec.rb | 18 +- .../essences/essence_page_editor_spec.rb | 10 +- spec/views/essences/essence_page_view_spec.rb | 12 +- .../essences/essence_picture_editor_spec.rb | 32 +- .../essences/essence_richtext_view_spec.rb | 22 +- .../essences/essence_select_editor_spec.rb | 18 +- .../essences/essence_select_view_spec.rb | 8 +- .../essences/essence_text_editor_spec.rb | 18 +- spec/views/essences/essence_text_view_spec.rb | 34 +- spec/views/pages/meta_data_spec.rb | 10 +- vendor/assets/fonts/fa-regular-400.eot | Bin 34390 -> 34390 bytes vendor/assets/fonts/fa-regular-400.svg | 4 +- vendor/assets/fonts/fa-regular-400.ttf | Bin 34092 -> 34092 bytes vendor/assets/fonts/fa-regular-400.woff | Bin 16800 -> 16800 bytes vendor/assets/fonts/fa-regular-400.woff2 | Bin 13600 -> 13584 bytes vendor/assets/fonts/fa-solid-900.eot | Bin 194078 -> 202902 bytes vendor/assets/fonts/fa-solid-900.svg | 244 +++- vendor/assets/fonts/fa-solid-900.ttf | Bin 193792 -> 202616 bytes vendor/assets/fonts/fa-solid-900.woff | Bin 99004 -> 103300 bytes vendor/assets/fonts/fa-solid-900.woff2 | Bin 76120 -> 79444 bytes .../javascripts/sortable/Sortable.min.js | 2 - .../stylesheets/fontawesome/_icons.scss | 31 + .../stylesheets/fontawesome/_variables.scss | 35 +- .../stylesheets/fontawesome/fontawesome.scss | 2 +- .../stylesheets/fontawesome/regular.scss | 14 +- .../assets/stylesheets/fontawesome/solid.scss | 2 +- 485 files changed, 7726 insertions(+), 6718 deletions(-) create mode 100644 .prettierrc delete mode 100644 app/assets/javascripts/alchemy/alchemy.i18n.js.coffee delete mode 100644 app/assets/javascripts/alchemy/alchemy.node_tree.js delete mode 100644 app/assets/javascripts/alchemy/alchemy.translations.js.coffee delete mode 100644 app/assets/javascripts/alchemy/alchemy.utils.js create mode 100644 app/assets/javascripts/alchemy/node_select.js create mode 100644 app/assets/javascripts/alchemy/templates/node.hbs create mode 100644 app/assets/stylesheets/alchemy/node-select.scss create mode 100644 app/models/alchemy/essence_node.rb create mode 100644 app/models/alchemy/page/url_path.rb create mode 100644 app/models/alchemy/picture/preprocessor.rb delete mode 100644 app/models/concerns/alchemy/content_touching.rb create mode 100644 app/models/concerns/alchemy/touch_elements.rb delete mode 100644 app/serializers/alchemy/legacy_element_serializer.rb delete mode 100644 app/views/alchemy/admin/pages/_menu_fields.html.erb create mode 100644 app/views/alchemy/essences/_essence_node_editor.html.erb create mode 100644 app/views/alchemy/essences/_essence_node_view.html.erb create mode 100644 babel.config.js create mode 100644 db/migrate/20200423073425_create_alchemy_essence_nodes.rb create mode 100644 db/migrate/20200504210159_remove_site_id_from_nodes.rb create mode 100644 db/migrate/20200505215518_add_language_id_foreign_key_to_alchemy_pages.rb create mode 100644 db/migrate/20200511113603_add_menu_type_to_alchemy_nodes.rb create mode 100644 db/migrate/20200514091507_make_page_layoutpage_null_false.rb create mode 100644 db/migrate/20200519073500_remove_visible_from_alchemy_pages.rb create mode 100644 lib/alchemy/admin/preview_url.rb delete mode 100644 lib/alchemy/ssl_protection.rb delete mode 100644 lib/alchemy/upgrader/four_point_four.rb rename lib/{rails => }/generators/alchemy/base.rb (83%) rename lib/{rails => }/generators/alchemy/elements/elements_generator.rb (56%) rename lib/{rails => }/generators/alchemy/elements/templates/view.html.erb (100%) rename lib/{rails => }/generators/alchemy/elements/templates/view.html.haml (100%) rename lib/{rails => }/generators/alchemy/elements/templates/view.html.slim (100%) rename lib/{rails => }/generators/alchemy/essence/essence_generator.rb (93%) rename lib/{rails => }/generators/alchemy/essence/templates/editor.html.erb (100%) rename lib/{rails => }/generators/alchemy/essence/templates/view.html.erb (100%) rename lib/{rails => }/generators/alchemy/install/files/_article.html.erb (100%) rename lib/{rails => }/generators/alchemy/install/files/_standard.html.erb (100%) rename lib/{rails => }/generators/alchemy/install/files/alchemy.en.yml (100%) create mode 100644 lib/generators/alchemy/install/files/alchemy_admin.js rename lib/{rails => }/generators/alchemy/install/files/all.css (100%) rename lib/{rails => }/generators/alchemy/install/files/all.js (100%) rename lib/{rails => }/generators/alchemy/install/files/application.html.erb (100%) rename lib/{rails => }/generators/alchemy/install/files/article.scss (100%) create mode 100644 lib/generators/alchemy/install/install_generator.rb rename lib/{rails => }/generators/alchemy/install/templates/dragonfly.rb.tt (100%) rename lib/{rails => }/generators/alchemy/install/templates/elements.yml.tt (100%) rename lib/{rails => }/generators/alchemy/install/templates/menus.yml.tt (100%) rename lib/{rails => }/generators/alchemy/install/templates/page_layouts.yml.tt (100%) rename lib/{rails => }/generators/alchemy/menus/menus_generator.rb (88%) rename lib/{rails => }/generators/alchemy/menus/templates/node.html.erb (74%) rename lib/{rails => }/generators/alchemy/menus/templates/node.html.haml (71%) rename lib/{rails => }/generators/alchemy/menus/templates/node.html.slim (70%) rename lib/{rails => }/generators/alchemy/menus/templates/wrapper.html.erb (76%) rename lib/{rails => }/generators/alchemy/menus/templates/wrapper.html.haml (73%) rename lib/{rails => }/generators/alchemy/menus/templates/wrapper.html.slim (73%) rename lib/{rails => }/generators/alchemy/module/module_generator.rb (92%) rename lib/{rails => }/generators/alchemy/module/templates/ability.rb.tt (100%) rename lib/{rails => }/generators/alchemy/module/templates/controller.rb.tt (100%) rename lib/{rails => }/generators/alchemy/module/templates/module_config.rb.tt (100%) rename lib/{rails => }/generators/alchemy/page_layouts/page_layouts_generator.rb (81%) rename lib/{rails => }/generators/alchemy/page_layouts/templates/layout.html.erb (100%) rename lib/{rails => }/generators/alchemy/page_layouts/templates/layout.html.haml (100%) rename lib/{rails => }/generators/alchemy/page_layouts/templates/layout.html.slim (100%) rename lib/{rails => }/generators/alchemy/site_layouts/site_layouts_generator.rb (88%) rename lib/{rails => }/generators/alchemy/site_layouts/templates/layout.html.erb (100%) rename lib/{rails => }/generators/alchemy/site_layouts/templates/layout.html.haml (100%) rename lib/{rails => }/generators/alchemy/site_layouts/templates/layout.html.slim (100%) rename lib/{rails => }/generators/alchemy/views/views_generator.rb (77%) delete mode 100644 lib/rails/generators/alchemy/install/install_generator.rb delete mode 100644 lib/tasks/alchemy/convert.rake create mode 100644 package.json create mode 100644 package/admin.js create mode 100644 package/src/__tests__/i18n.spec.js create mode 100644 package/src/i18n.js create mode 100644 package/src/node_tree.js create mode 100644 package/src/translations.js create mode 100644 package/src/utils/__tests__/ajax.spec.js create mode 100644 package/src/utils/__tests__/events.spec.js create mode 100644 package/src/utils/ajax.js create mode 100644 package/src/utils/events.js create mode 100644 spec/dummy/app/views/alchemy/elements/_menu.html.erb rename spec/dummy/app/views/alchemy/menus/{main_navigation => main_menu}/_node.html.erb (72%) create mode 100644 spec/dummy/app/views/alchemy/menus/main_menu/_wrapper.html.erb delete mode 100644 spec/dummy/app/views/alchemy/menus/main_navigation/_wrapper.html.erb create mode 120000 spec/dummy/db/migrate/20200423073425_create_alchemy_essence_nodes.rb create mode 120000 spec/dummy/db/migrate/20200504210159_remove_site_id_from_nodes.rb create mode 120000 spec/dummy/db/migrate/20200505215518_add_language_id_foreign_key_to_alchemy_pages.rb create mode 120000 spec/dummy/db/migrate/20200511113603_add_menu_type_to_alchemy_nodes.rb create mode 120000 spec/dummy/db/migrate/20200514091507_make_page_layoutpage_null_false.rb create mode 120000 spec/dummy/db/migrate/20200519073500_remove_visible_from_alchemy_pages.rb create mode 100644 spec/libraries/admin/preview_url_spec.rb create mode 100644 spec/models/alchemy/essence_node_spec.rb create mode 100644 spec/models/alchemy/page/url_path_spec.rb delete mode 100644 vendor/assets/javascripts/sortable/Sortable.min.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e99baefce..5e176f7325 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: MYSQL_PASSWORD: password MYSQL_DATABASE: alchemy_cms_dummy_test MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - uses: actions/checkout@v1 - name: Set up Ruby @@ -60,15 +60,15 @@ jobs: if: matrix.database == 'postgresql' run: | mkdir -p /home/runner/apt/cache - sudo apt-get update -qq - sudo apt-get install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache" + sudo apt update -qq + sudo apt install -qq --fix-missing libpq-dev -o dir::cache::archives="/home/runner/apt/cache" sudo chown -R runner /home/runner/apt/cache - name: Install MySQL headers if: matrix.database == 'mysql' run: | mkdir -p /home/runner/apt/cache - sudo apt-get update -qq - sudo apt-get install -qq --fix-missing libmysqlclient-dev -o dir::cache::archives="/home/runner/apt/cache" + sudo apt update -qq + sudo apt install -qq --fix-missing libmysqlclient-dev -o dir::cache::archives="/home/runner/apt/cache" sudo chown -R runner /home/runner/apt/cache - name: Install bundler run: | @@ -85,11 +85,19 @@ jobs: timeout-minutes: 10 run: | bundle install --jobs 4 --retry 3 --path vendor/bundle + - name: Restore node modules cache + id: yarn-cache + uses: actions/cache@preview + with: + path: spec/dummy/node_modules + key: ${{ runner.os }}-yarn-dummy-${{ hashFiles('./package.json') }} + restore-keys: | + ${{ runner.os }}-yarn-dummy- - name: Prepare database run: | bundle exec rake alchemy:spec:prepare - name: Run tests & publish code coverage - uses: paambaati/codeclimate-action@v2.5.5 + uses: paambaati/codeclimate-action@v2.5.7 env: CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 with: @@ -99,3 +107,28 @@ jobs: with: name: Screenshots path: spec/dummy/tmp/screenshots + Jest: + runs-on: ubuntu-latest + env: + NODE_ENV: test + steps: + - uses: actions/checkout@v1 + - name: Restore node modules cache + uses: actions/cache@preview + with: + path: node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('./package.json') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install yarn + run: yarn install + - name: Run jest + run: yarn jest + - name: Run jest & publish code coverage + uses: paambaati/codeclimate-action@v2.5.7 + env: + CC_TEST_REPORTER_ID: bca4349e32f97919210ac8a450b04904b90683fcdd57d65a22c0f5065482bc22 + with: + coverageLocations: + ./coverage/lcov.info:lcov + coverageCommand: yarn jest --collectCoverage --coverageDirectory=coverage diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 75f22b4762..262e51d5a9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,5 +13,5 @@ jobs: - uses: actions/stale@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue has not seen any activity in a long time. If the issue descriped still exists in recent versions of Alchemy, please open a new issue or preferably open a PR with a fix. Thanks for reporting.' + stale-issue-message: 'This issue has not seen any activity in a long time. If the issue described still exists in recent versions of Alchemy, please open a new issue or preferably open a PR with a fix. Thanks for reporting.' stale-pr-message: 'This pull request has not seen any activiy in a long time. Probably because of missing tests or a necessary rebase. Please open a new PR to latest master if you want to continue working on this. Thanks for the contribution.' diff --git a/.gitignore b/.gitignore index 3a28ffe484..a49a90c1cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,22 @@ pkg tmp log .sass-cache -spec/dummy/uploads/ +spec/dummy/.browserslistrc +spec/dummy/app/assets/stylesheets/alchemy/ +spec/dummy/app/javascript/ +spec/dummy/babel.config.js +spec/dummy/bin/webpack +spec/dummy/bin/webpack-dev-server +spec/dummy/config/alchemy/config.yml +spec/dummy/config/webpack/ +spec/dummy/config/webpacker.yml spec/dummy/db/*.sqlite3* -spec/dummy/public/assets +spec/dummy/package.json +spec/dummy/postcss.config.js +spec/dummy/public/assets/ +spec/dummy/public/packs/ +spec/dummy/public/packs-test/ +spec/dummy/uploads/ .rvmrc /coverage/ *.gem @@ -22,3 +35,8 @@ spec/dummy/public/assets .ruby-version .env .rspec +node_modules +yarn-error.log +yarn-debug.log* +.yarn-integrity +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..348cb46f7b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "trailingComma": "none", + "vueIndentScriptAndStyle": true, + "arrowParens": "always" +} diff --git a/.rubocop.yml b/.rubocop.yml index 076dc5c8a0..05dd2cc324 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,7 @@ # Relaxed.Ruby.Style AllCops: - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.4 Exclude: - 'bin/rspec' - 'vendor/**/*' @@ -9,6 +9,7 @@ AllCops: - 'spec/dummy/config/**/*' - 'alchemy_cms.gemspec' - 'Rakefile' + - 'node_modules/**/*' # Really, rubocop? Bundler/OrderedGems: @@ -220,8 +221,19 @@ Style/SpecialGlobalVars: StyleGuide: http://relaxed.ruby.style/#stylespecialglobalvars Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/TrailingCommaInArguments: Enabled: false - StyleGuide: http://relaxed.ruby.style/#stylestringliterals + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: consistent_comma Style/WhileUntilModifier: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index f9002419dc..0e62bef2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,74 @@ ## 5.0.0 (unreleased) +- Language Factory: Create default language in host app's locale [#1884](https://github.com/AlchemyCMS/alchemy_cms/pull/1884) ([mamhoff](https://github.com/mamhoff)) +- Respect filter and tagging params in picture archive size buttons [#1880](https://github.com/AlchemyCMS/alchemy_cms/pull/1880) ([tvdeyen](https://github.com/tvdeyen)) +- Extract picture thumbnail sizes in a constant [#1879](https://github.com/AlchemyCMS/alchemy_cms/pull/1879) ([tvdeyen](https://github.com/tvdeyen)) +- Configurable Image Preprocessor [#1878](https://github.com/AlchemyCMS/alchemy_cms/pull/1878) ([tvdeyen](https://github.com/tvdeyen)) +- Configure edit page preview per site [#1877](https://github.com/AlchemyCMS/alchemy_cms/pull/1877) ([tvdeyen](https://github.com/tvdeyen)) +- Fix Page tree sorting after root page removal [#1876](https://github.com/AlchemyCMS/alchemy_cms/pull/1876) ([tvdeyen](https://github.com/tvdeyen)) +- 5.0 Upgrader fixes [#1874](https://github.com/AlchemyCMS/alchemy_cms/pull/1874) ([tvdeyen](https://github.com/tvdeyen)) +- Remove url_nesting config [#1872](https://github.com/AlchemyCMS/alchemy_cms/pull/1872) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade sassc to version 2.4.0 [#1871](https://github.com/AlchemyCMS/alchemy_cms/pull/1871) ([depfu](https://github.com/apps/depfu)) +- fix GitHub Actions spelling [#1869](https://github.com/AlchemyCMS/alchemy_cms/pull/1869) ([alexanderadam](https://github.com/alexanderadam)) +- Remove Page#visible [#1868](https://github.com/AlchemyCMS/alchemy_cms/pull/1868) ([tvdeyen](https://github.com/tvdeyen)) +- 4.6 backports for master [#1867](https://github.com/AlchemyCMS/alchemy_cms/pull/1867) ([tvdeyen](https://github.com/tvdeyen)) +- Use apt update instead of apt-get in GH action [#1866](https://github.com/AlchemyCMS/alchemy_cms/pull/1866) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade rubocop to version 0.85.0 [#1863](https://github.com/AlchemyCMS/alchemy_cms/pull/1863) ([depfu](https://github.com/apps/depfu)) +- Remove active_record_5_1? method [#1854](https://github.com/AlchemyCMS/alchemy_cms/pull/1854) ([tvdeyen](https://github.com/tvdeyen)) +- Use Alchemy npm package instead of hacking webpacker [#1853](https://github.com/AlchemyCMS/alchemy_cms/pull/1853) ([tvdeyen](https://github.com/tvdeyen)) +- Fix node select ES5 syntax [#1851](https://github.com/AlchemyCMS/alchemy_cms/pull/1851) ([tvdeyen](https://github.com/tvdeyen)) +- Run yarn:install after installing webpacker in install generator [#1850](https://github.com/AlchemyCMS/alchemy_cms/pull/1850) ([mamhoff](https://github.com/mamhoff)) +- Remove male sign after emoji [#1849](https://github.com/AlchemyCMS/alchemy_cms/pull/1849) ([mamhoff](https://github.com/mamhoff)) +- Do not use ES6 Syntax in Node Selector [#1846](https://github.com/AlchemyCMS/alchemy_cms/pull/1846) ([mamhoff](https://github.com/mamhoff)) +- [ruby] Upgrade rubocop to version 0.84.0 [#1845](https://github.com/AlchemyCMS/alchemy_cms/pull/1845) ([depfu](https://github.com/apps/depfu)) +- Always create nested urls [#1844](https://github.com/AlchemyCMS/alchemy_cms/pull/1844) ([tvdeyen](https://github.com/tvdeyen)) +- Fix: Add indifferent access to default options in encoded_image [#1840](https://github.com/AlchemyCMS/alchemy_cms/pull/1840) ([mickenorlen](https://github.com/mickenorlen)) +- Set proper nested set scope on page [#1837](https://github.com/AlchemyCMS/alchemy_cms/pull/1837) ([tvdeyen](https://github.com/tvdeyen)) +- Install Webpacker in install generator [#1835](https://github.com/AlchemyCMS/alchemy_cms/pull/1835) ([mamhoff](https://github.com/mamhoff)) +- Fix deleting an EssenceNode from a content [#1834](https://github.com/AlchemyCMS/alchemy_cms/pull/1834) ([mamhoff](https://github.com/mamhoff)) +- Use Rails standards for deleting pages from EssencePage [#1833](https://github.com/AlchemyCMS/alchemy_cms/pull/1833) ([mamhoff](https://github.com/mamhoff)) +- Scope has one site [#1832](https://github.com/AlchemyCMS/alchemy_cms/pull/1832) ([mamhoff](https://github.com/mamhoff)) +- Render nodes [#1831](https://github.com/AlchemyCMS/alchemy_cms/pull/1831) ([mamhoff](https://github.com/mamhoff)) +- Add errors when node cant be deleted [#1828](https://github.com/AlchemyCMS/alchemy_cms/pull/1828) ([mamhoff](https://github.com/mamhoff)) +- Add error flash to resource controller [#1827](https://github.com/AlchemyCMS/alchemy_cms/pull/1827) ([mamhoff](https://github.com/mamhoff)) +- Fix Association between Nodes and EssenceNodes [#1826](https://github.com/AlchemyCMS/alchemy_cms/pull/1826) ([mamhoff](https://github.com/mamhoff)) +- Translated root menus [#1825](https://github.com/AlchemyCMS/alchemy_cms/pull/1825) ([mamhoff](https://github.com/mamhoff)) +- Use rails root in install generator [#1822](https://github.com/AlchemyCMS/alchemy_cms/pull/1822) ([tvdeyen](https://github.com/tvdeyen)) +- Add a quick Node select [#1821](https://github.com/AlchemyCMS/alchemy_cms/pull/1821) ([mamhoff](https://github.com/mamhoff)) +- Add has_one association for root page [#1820](https://github.com/AlchemyCMS/alchemy_cms/pull/1820) ([mamhoff](https://github.com/mamhoff)) +- [js] Upgrade babel-jest to version 26.0.1 [#1819](https://github.com/AlchemyCMS/alchemy_cms/pull/1819) ([depfu](https://github.com/apps/depfu)) +- Make page language mandatory [#1818](https://github.com/AlchemyCMS/alchemy_cms/pull/1818) ([tvdeyen](https://github.com/tvdeyen)) +- Remove root page [#1817](https://github.com/AlchemyCMS/alchemy_cms/pull/1817) ([tvdeyen](https://github.com/tvdeyen)) +- Fix page unlock page icon replacement [#1816](https://github.com/AlchemyCMS/alchemy_cms/pull/1816) ([tvdeyen](https://github.com/tvdeyen)) +- Invoke rake task in upgrader instead of system call [#1815](https://github.com/AlchemyCMS/alchemy_cms/pull/1815) ([tvdeyen](https://github.com/tvdeyen)) +- Remove old 4.4 upgrader class [#1814](https://github.com/AlchemyCMS/alchemy_cms/pull/1814) ([tvdeyen](https://github.com/tvdeyen)) +- Remove Page.ancestors_for [#1813](https://github.com/AlchemyCMS/alchemy_cms/pull/1813) ([tvdeyen](https://github.com/tvdeyen)) +- Remove layout root pages [#1812](https://github.com/AlchemyCMS/alchemy_cms/pull/1812) ([tvdeyen](https://github.com/tvdeyen)) +- Use timestamps in migration [#1811](https://github.com/AlchemyCMS/alchemy_cms/pull/1811) ([tvdeyen](https://github.com/tvdeyen)) +- Remove legacy element serializer [#1810](https://github.com/AlchemyCMS/alchemy_cms/pull/1810) ([tvdeyen](https://github.com/tvdeyen)) +- Remove timestamps from essences and contents [#1809](https://github.com/AlchemyCMS/alchemy_cms/pull/1809) ([tvdeyen](https://github.com/tvdeyen)) +- Remove stamper from contents [#1808](https://github.com/AlchemyCMS/alchemy_cms/pull/1808) ([tvdeyen](https://github.com/tvdeyen)) +- Remove Site ID from nodes [#1807](https://github.com/AlchemyCMS/alchemy_cms/pull/1807) ([mamhoff](https://github.com/mamhoff)) +- Add Alchemy::Language.has_many :nodes [#1806](https://github.com/AlchemyCMS/alchemy_cms/pull/1806) ([mamhoff](https://github.com/mamhoff)) +- Drop Rails 5.0 and 5.1 support [#1805](https://github.com/AlchemyCMS/alchemy_cms/pull/1805) ([tvdeyen](https://github.com/tvdeyen)) +- Remove enforce_ssl [#1804](https://github.com/AlchemyCMS/alchemy_cms/pull/1804) ([tvdeyen](https://github.com/tvdeyen)) +- Make the preview url configurable [#1803](https://github.com/AlchemyCMS/alchemy_cms/pull/1803) ([tvdeyen](https://github.com/tvdeyen)) +- Remove stamper from essences [#1802](https://github.com/AlchemyCMS/alchemy_cms/pull/1802) ([tvdeyen](https://github.com/tvdeyen)) +- Use Rufo to format all files in a consistent way [#1799](https://github.com/AlchemyCMS/alchemy_cms/pull/1799) ([tvdeyen](https://github.com/tvdeyen)) +- Remove acts_as_list from Content [#1798](https://github.com/AlchemyCMS/alchemy_cms/pull/1798) ([tvdeyen](https://github.com/tvdeyen)) +- Add EssenceNode [#1792](https://github.com/AlchemyCMS/alchemy_cms/pull/1792) ([mamhoff](https://github.com/mamhoff)) +- Use 2.5.7 of code climate coverage reporter GH action [#1790](https://github.com/AlchemyCMS/alchemy_cms/pull/1790) ([tvdeyen](https://github.com/tvdeyen)) +- [ruby] Upgrade sassc to version 2.3.0 [#1787](https://github.com/AlchemyCMS/alchemy_cms/pull/1787) ([depfu](https://github.com/apps/depfu)) +- [ruby] Upgrade rubocop to version 0.82.0 [#1785](https://github.com/AlchemyCMS/alchemy_cms/pull/1785) ([depfu](https://github.com/apps/depfu)) +- Fix regular icons [#1784](https://github.com/AlchemyCMS/alchemy_cms/pull/1784) ([tvdeyen](https://github.com/tvdeyen)) +- Convert NodeTree into ES6 [#1782](https://github.com/AlchemyCMS/alchemy_cms/pull/1782) ([tvdeyen](https://github.com/tvdeyen)) +- Add Webpacker [#1775](https://github.com/AlchemyCMS/alchemy_cms/pull/1775) ([tvdeyen](https://github.com/tvdeyen)) +- Multi language menus [#1774](https://github.com/AlchemyCMS/alchemy_cms/pull/1774) ([rmparr](https://github.com/rmparr)) +- On Boarding Flow [#1770](https://github.com/AlchemyCMS/alchemy_cms/pull/1770) ([tvdeyen](https://github.com/tvdeyen)) +- Fix bug in language from session w/o site [#1769](https://github.com/AlchemyCMS/alchemy_cms/pull/1769) ([tvdeyen](https://github.com/tvdeyen)) +- Fix fontawesome in production [#1765](https://github.com/AlchemyCMS/alchemy_cms/pull/1765) ([mickenorlen](https://github.com/mickenorlen)) +- Remove implicit Site and Language creation [#1763](https://github.com/AlchemyCMS/alchemy_cms/pull/1763) ([mamhoff](https://github.com/mamhoff)) +- Add content editor data attributes based on name/id and css_classes presenter method [#1761](https://github.com/AlchemyCMS/alchemy_cms/pull/1761) ([mickenorlen](https://github.com/mickenorlen)) - Add alchemy.test to development domains [#1760](https://github.com/AlchemyCMS/alchemy_cms/pull/1760) ([tvdeyen](https://github.com/tvdeyen)) - Update Fontawesome [#1759](https://github.com/AlchemyCMS/alchemy_cms/pull/1759) ([tvdeyen](https://github.com/tvdeyen)) - Fix test coverage reporting [#1757](https://github.com/AlchemyCMS/alchemy_cms/pull/1757) ([tvdeyen](https://github.com/tvdeyen)) @@ -24,6 +93,27 @@ - Add ContentEditor decorator [#1645](https://github.com/AlchemyCMS/alchemy_cms/pull/1645) ([tvdeyen](https://github.com/tvdeyen)) - Remove local options from essence editors [#1638](https://github.com/AlchemyCMS/alchemy_cms/pull/1638) ([tvdeyen](https://github.com/tvdeyen)) +## 4.6.1 (2020-06-04) + +- Fix 4.6 upgrader + +## 4.6.0 (2020-06-04) + +- Use apt update instead of apt-get in GH action [#1865](https://github.com/AlchemyCMS/alchemy_cms/pull/1865) ([tvdeyen](https://github.com/tvdeyen)) +- Use depth for page tree serializer root_or_leaf [#1864](https://github.com/AlchemyCMS/alchemy_cms/pull/1864) ([tvdeyen](https://github.com/tvdeyen)) +- Fix sitemap wrapper height [#1861](https://github.com/AlchemyCMS/alchemy_cms/pull/1861) ([tvdeyen](https://github.com/tvdeyen)) +- Do not return the root page with API responses. [#1860](https://github.com/AlchemyCMS/alchemy_cms/pull/1860) ([tvdeyen](https://github.com/tvdeyen)) +- Introduce page.url_path and use it for alchemyPageSelect [#1859](https://github.com/AlchemyCMS/alchemy_cms/pull/1859) ([tvdeyen](https://github.com/tvdeyen)) +- Update Urlname translation [#1857](https://github.com/AlchemyCMS/alchemy_cms/pull/1857) ([tvdeyen](https://github.com/tvdeyen)) +- Show url name in Page tree [#1856](https://github.com/AlchemyCMS/alchemy_cms/pull/1856) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate Page#visible attribute [#1855](https://github.com/AlchemyCMS/alchemy_cms/pull/1855) ([tvdeyen](https://github.com/tvdeyen)) +- 4.6: Re-add `auto_logout_time` configuration option [#1852](https://github.com/AlchemyCMS/alchemy_cms/pull/1852) ([mamhoff](https://github.com/mamhoff)) +- Backport ContentEditor to 4.6, deprecate removed methods on `Alchemy::Content` [#1847](https://github.com/AlchemyCMS/alchemy_cms/pull/1847) ([mamhoff](https://github.com/mamhoff)) +- Deprecate auto_logout_time (4.6) [#1843](https://github.com/AlchemyCMS/alchemy_cms/pull/1843) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate require_ssl (4.6) [#1842](https://github.com/AlchemyCMS/alchemy_cms/pull/1842) ([tvdeyen](https://github.com/tvdeyen)) +- Deprecate url_nesting configuration (4.6) [#1841](https://github.com/AlchemyCMS/alchemy_cms/pull/1841) ([tvdeyen](https://github.com/tvdeyen)) +- Allow page visible toggle (4.6) [#1838](https://github.com/AlchemyCMS/alchemy_cms/pull/1838) ([tvdeyen](https://github.com/tvdeyen)) + ## 4.5.0 (2020-03-30) - Sortable menus [#1758](https://github.com/AlchemyCMS/alchemy_cms/pull/1758) ([mamhoff](https://github.com/mamhoff)) diff --git a/Gemfile b/Gemfile index e43124be57..b1f343681e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,33 +1,34 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" gemspec -rails_version = ENV.fetch('RAILS_VERSION', 6.0).to_f -gem 'rails', "~> #{rails_version}.0" +rails_version = ENV.fetch("RAILS_VERSION", 6.0).to_f +gem "rails", "~> #{rails_version}.0" -if ENV['DB'].nil? || ENV['DB'] == 'sqlite' - gem 'sqlite3', rails_version > 5.0 ? '~> 1.4.1' : '~> 1.3.6' +if ENV["DB"].nil? || ENV["DB"] == "sqlite" + gem "sqlite3", "~> 1.4.1" end -gem 'mysql2', '~> 0.5.1' if ENV['DB'] == 'mysql' -gem 'pg', '~> 1.0' if ENV['DB'] == 'postgresql' +gem "mysql2", "~> 0.5.1" if ENV["DB"] == "mysql" +gem "pg", "~> 1.0" if ENV["DB"] == "postgresql" group :development, :test do - if ENV['GITHUB_ACTIONS'] - gem 'sassc', '~> 2.1.0' # https://github.com/sass/sassc-ruby/issues/146 + if ENV["GITHUB_ACTIONS"] + gem "sassc", "~> 2.4.0" # https://github.com/sass/sassc-ruby/issues/146 else - gem 'launchy' - gem 'annotate' - gem 'bumpy' - gem 'yard' - gem 'redcarpet' - gem 'pry-byebug' - gem 'rubocop', '~> 0.80.1', require: false - gem 'listen' - gem 'localeapp', '~> 3.0', require: false - gem 'dotenv', '~> 2.2' - gem 'github_fast_changelog', require: false - gem 'active_record_query_trace', require: false - gem 'rack-mini-profiler', require: false + gem "launchy" + gem "annotate" + gem "bumpy" + gem "yard" + gem "redcarpet" + gem "pry-byebug" + gem "rubocop", "~> 0.85.0", require: false + gem "listen" + gem "localeapp", "~> 3.0", require: false + gem "dotenv", "~> 2.2" + gem "github_fast_changelog", require: false + gem "active_record_query_trace", require: false + gem "rack-mini-profiler", require: false + gem "rufo", require: false end end diff --git a/README.md b/README.md index f559b8597b..a972eb19cf 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ or visit the existing demo at https://alchemy-demo.herokuapp.com ## đźš‚ Rails Version -**This version of AlchemyCMS runs with all versions of Rails 5 and Rails 6** +**This version of AlchemyCMS runs with Rails 5.2 and Rails 6.0** +* For a Rails 5.0 or 5.1 compatible version use the [`4.5-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/4.5-stable). * For a Rails 4.2 compatible version use the [`3.6-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.6-stable). * For a Rails 4.0/4.1 compatible version use the [`3.1-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/3.1-stable). * For a Rails 3.2 compatible version use the [`2.8-stable` branch](https://github.com/AlchemyCMS/alchemy_cms/tree/2.8-stable). diff --git a/Rakefile b/Rakefile index f3c0b67156..494e284beb 100644 --- a/Rakefile +++ b/Rakefile @@ -45,6 +45,7 @@ namespace :alchemy do bin/rake db:create && \ bin/rake db:environment:set && \ bin/rake db:migrate:reset && \ + bin/rails g alchemy:install --skip --skip-demo-files && \ cd - BASH result || fail diff --git a/alchemy_cms.gemspec b/alchemy_cms.gemspec index 7d442a1b3c..74dec0ef7e 100644 --- a/alchemy_cms.gemspec +++ b/alchemy_cms.gemspec @@ -8,10 +8,10 @@ Gem::Specification.new do |gem| gem.version = Alchemy::VERSION gem.platform = Gem::Platform::RUBY gem.authors = ['Thomas von Deyen', 'Robin Boening', 'Marc Schettke', 'Hendrik Mans', 'Carsten Fregin', 'Martin Meyerhoff'] - gem.email = ['alchemy@magiclabs.de'] + gem.email = ['hello@alchemy-cms.com'] gem.homepage = 'https://alchemy-cms.com' - gem.summary = 'A powerful, userfriendly and flexible CMS for Rails 5' - gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails 5 CMS.' + gem.summary = 'A powerful, userfriendly and flexible CMS for Rails' + gem.description = 'Alchemy is a powerful, userfriendly and flexible Rails CMS.' gem.requirements << 'ImageMagick (libmagick), v6.6 or greater.' gem.required_ruby_version = '>= 2.3.0' gem.license = 'BSD New' @@ -32,7 +32,7 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'kaminari', ['~> 1.1'] gem.add_runtime_dependency 'originator', ['~> 3.1'] gem.add_runtime_dependency 'non-stupid-digest-assets', ['~> 1.0.8'] - gem.add_runtime_dependency 'rails', ['>= 5.0.0', '< 6.1'] + gem.add_runtime_dependency 'rails', ['>= 5.2.0', '< 6.1'] gem.add_runtime_dependency 'ransack', ['>= 1.8', '< 3.0'] gem.add_runtime_dependency 'request_store', ['~> 1.2'] gem.add_runtime_dependency 'responders', ['>= 2.0', '< 4.0'] @@ -41,10 +41,11 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency 'simple_form', ['>= 4.0', '< 6'] gem.add_runtime_dependency 'sprockets', ['>= 3.0', '< 5'] gem.add_runtime_dependency 'turbolinks', ['>= 2.5'] + gem.add_runtime_dependency 'webpacker', ['>= 4.0', '< 6'] gem.add_development_dependency 'capybara', ['~> 3.0'] gem.add_development_dependency 'capybara-screenshot', ['~> 1.0'] - gem.add_development_dependency 'factory_bot_rails', ['~> 5.0'] + gem.add_development_dependency 'factory_bot_rails', ['~> 6.0'] gem.add_development_dependency 'puma', ['~> 4.0'] gem.add_development_dependency 'rails-controller-testing', ['~> 1.0'] gem.add_development_dependency 'rspec-activemodel-mocks', ['~> 1.0'] diff --git a/app/assets/javascripts/alchemy/admin.js b/app/assets/javascripts/alchemy/admin.js index 7b728dfd24..32c2d118e3 100644 --- a/app/assets/javascripts/alchemy/admin.js +++ b/app/assets/javascripts/alchemy/admin.js @@ -15,10 +15,8 @@ //= require requestAnimationFrame //= require select2 //= require handlebars -//= require sortable/Sortable.min //= require alchemy/templates //= require alchemy/alchemy.base -//= require alchemy/alchemy.utils //= require alchemy/alchemy.autocomplete //= require alchemy/alchemy.browser //= require alchemy/alchemy.buttons @@ -34,14 +32,12 @@ //= require alchemy/alchemy.growler //= require alchemy/alchemy.gui //= require alchemy/alchemy.hotkeys -//= require alchemy/alchemy.i18n //= require alchemy/alchemy.image_cropper //= require alchemy/alchemy.image_overlay //= require alchemy/alchemy.string_extension //= require alchemy/alchemy.link_dialog //= require alchemy/alchemy.list_filter //= require alchemy/alchemy.initializer -//= require alchemy/alchemy.node_tree //= require alchemy/alchemy.page_sorter //= require alchemy/alchemy.uploader //= require alchemy/alchemy.preview_window @@ -49,6 +45,6 @@ //= require alchemy/alchemy.spinner //= require alchemy/alchemy.tinymce //= require alchemy/alchemy.tooltips -//= require alchemy/alchemy.translations //= require alchemy/alchemy.trash_window //= require alchemy/page_select +//= require alchemy/node_select diff --git a/app/assets/javascripts/alchemy/alchemy.base.js.coffee b/app/assets/javascripts/alchemy/alchemy.base.js.coffee index db7ea0bbe8..20b7de18a8 100644 --- a/app/assets/javascripts/alchemy/alchemy.base.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.base.js.coffee @@ -62,9 +62,10 @@ $.extend Alchemy, removePicture: (selector) -> $form_field = $(selector) $element = $form_field.closest(".element-editor") + $content = $form_field.closest(".content_editor") if $form_field[0] $form_field.val "" - $element.find(".thumbnail_background").html('') + $content.find(".thumbnail_background").html('') Alchemy.setElementDirty $element false diff --git a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee index 1d86ff16f1..80ae7b18be 100644 --- a/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee @@ -179,8 +179,6 @@ Alchemy.ElementEditors = if data.message == 'Alchemy.focusElementEditor' $element = $("#element_#{data.element_id}") Alchemy.ElementEditors.focusElement($element) - else - console.warn 'Unknown message received!', data onClickBody: (e) -> element = $(e.target).parents('.element-editor')[0] diff --git a/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee b/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee deleted file mode 100644 index 5f08c82c07..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee +++ /dev/null @@ -1,32 +0,0 @@ -#= require alchemy/alchemy.translations - -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -Alchemy.I18n = - - KEY_SEPARATOR: /\./ - - # Translates given string - # - translate: (key, replacement) -> - if !Alchemy.locale? - throw 'Alchemy.locale is not set! Please set Alchemy.locale to a locale string in order to translate something.' - translations = Alchemy.translations[Alchemy.locale] - if translations - if @KEY_SEPARATOR.test(key) - keys = key.split(@KEY_SEPARATOR) - translation = translations[keys[0]][keys[1]] || key - else - translation = translations[key] || key - if replacement - translation.replace(/%\{.+\}/, replacement) - else - translation - else - console.warn "Translations for locale #{Alchemy.locale} not found!" - key - -# Global utility method for translating a given string -# -Alchemy.t = (key, replacement) -> - Alchemy.I18n.translate(key, replacement) diff --git a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee index 46bf1bad57..cbc7e463b3 100644 --- a/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +++ b/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee @@ -76,16 +76,16 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog meta = data.meta results: data.pages.map (page) -> - id: "/#{page.urlname}" + id: page.url_path name: page.name - urlname: page.urlname + url_path: page.url_path page_id: page.id more: meta.page * meta.per_page < meta.total_count initSelection: ($element, callback) => urlname = $element.val() $.get Alchemy.routes.api_pages_path, q: - urlname_eq: urlname.replace(/^\//, '') + urlname_eq: urlname.replace(/^\/([a-z]{2}(-[A-Z]{2})?\/)?/, '') page: 1 per_page: 1, (data) => @@ -93,9 +93,9 @@ class window.Alchemy.LinkDialog extends Alchemy.Dialog if page @initElementSelect(page.id) callback - id: "/#{page.urlname}" + id: page.url_path name: page.name - urlname: page.name + url_path: page.url_path page_id: page.id formatSelection: (page) -> page.name diff --git a/app/assets/javascripts/alchemy/alchemy.node_tree.js b/app/assets/javascripts/alchemy/alchemy.node_tree.js deleted file mode 100644 index 9213d8bb70..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.node_tree.js +++ /dev/null @@ -1,66 +0,0 @@ -Alchemy.NodeTree = { - onFinishDragging: function (evt) { - var url = Alchemy.routes.move_api_node_path(evt.item.dataset.id) - var data = { - target_parent_id: evt.to.dataset.nodeId, - new_position: evt.newIndex - }; - var ajax = Alchemy.ajax('PATCH', url, data) - - ajax.then(function(response) { - Alchemy.growl('Successfully moved menu item.') - Alchemy.NodeTree.displayNodeFolders() - }).catch(function() { - Alchemy.growl(error.message || error); - }) - }, - - displayNodeFolders: function () { - document.querySelectorAll('li.menu-item').forEach(function (el) { - var leftIconArea = el.querySelector('.nodes_tree-left_images') - var list = el.querySelector('ul') - var node = { folded: el.dataset.folded === 'true', id: el.dataset.id } - - if (list.children.length > 0 || node.folded ) { - leftIconArea.innerHTML = HandlebarsTemplates.node_folder({ node: node }) - } else { - leftIconArea.innerHTML = ' ' - } - }); - }, - - handleNodeFolders: function() { - Alchemy.on('click', '.nodes_tree', '.node_folder', function(evt) { - var nodeId = this.dataset.nodeId - var menu_item = this.closest('li.menu-item') - var url = Alchemy.routes.toggle_folded_api_node_path(nodeId) - var list = menu_item.querySelector('.children') - var ajax = Alchemy.ajax('PATCH', url) - - ajax.then(function() { - list.classList.toggle('folded') - menu_item.dataset.folded = menu_item.dataset.folded == 'true' ? 'false' : 'true' - Alchemy.NodeTree.displayNodeFolders(); - }).catch(function(error){ - Alchemy.growl(error.message || error); - }); - }); - }, - - init: function() { - this.handleNodeFolders() - this.displayNodeFolders() - - document.querySelectorAll('.nodes_tree ul.children').forEach(function (el) { - new Sortable(el, { - group: 'nodes', - animation: 150, - fallbackOnBody: true, - swapThreshold: 0.65, - handle: '.node_name', - invertSwap: true, - onEnd: Alchemy.NodeTree.onFinishDragging - }); - }); - } -} diff --git a/app/assets/javascripts/alchemy/alchemy.page_sorter.js b/app/assets/javascripts/alchemy/alchemy.page_sorter.js index df57ce4be1..9c4039c97d 100644 --- a/app/assets/javascripts/alchemy/alchemy.page_sorter.js +++ b/app/assets/javascripts/alchemy/alchemy.page_sorter.js @@ -1,24 +1,24 @@ -Alchemy.PageSorter = function() { - var $sortables = $('ul#sitemap').find('ul.level_1_children'); +Alchemy.PageSorter = function () { + var $sortables = $("ul#sitemap").find("ul.level_0_children") $sortables.nestedSortable({ - disableNesting: 'no-nest', + disableNesting: "no-nest", forcePlaceholderSize: true, - handle: '.handle', - items: 'li', - listType: 'ul', + handle: ".handle", + items: "li", + listType: "ul", opacity: 0.5, - placeholder: 'placeholder', + placeholder: "placeholder", tabSize: 16, - tolerance: 'pointer', - toleranceElement: '> div' - }); + tolerance: "pointer", + toleranceElement: "> div" + }) - $('#save_page_order').click(function(e) { - e.preventDefault(); - Alchemy.Buttons.disable(this); + $("#save_page_order").click(function (e) { + e.preventDefault() + Alchemy.Buttons.disable(this) $.post(Alchemy.routes.order_admin_pages_path, { - set: JSON.stringify($sortables.nestedSortable('toHierarchy')) - }); - }); -}; + set: JSON.stringify($sortables.nestedSortable("toHierarchy")) + }) + }) +} diff --git a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee b/app/assets/javascripts/alchemy/alchemy.translations.js.coffee deleted file mode 100644 index 57e2f84d49..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +++ /dev/null @@ -1,29 +0,0 @@ -window.Alchemy = {} if typeof(window.Alchemy) is 'undefined' - -# Holds translations for javascripts -# -Alchemy.translations = - en: - allowed_chars: 'of %{count} chars' - cancel: 'Cancel' - cancelled: 'Cancelled' - click_to_edit: 'click to edit' - complete: 'Complete' - element_dirty_notice: 'This element has unsaved changes. Do you really want to fold it?' - help: 'Help' - ok: 'Ok' - page_dirty_notice: 'You have unsaved changes on this page. They will be lost if you continue.' - page_found: 'Page found' - pages_found: 'Pages found' - url_validation_failed: 'The url has no valid format.' - warning: 'Warning!' - 'File is too large': 'File is too large' - 'File is too small': 'File is too small' - 'File type not allowed': 'File type not allowed' - 'Maximum number of files exceeded': 'Maximum number of files exceeded.' - 'Uploaded bytes exceed file size': 'Uploaded bytes exceed file size' - formats: - datetime: "Y-m-d H:i" - date: "Y-m-d" - time: "H:i" - time_24hr: false diff --git a/app/assets/javascripts/alchemy/alchemy.utils.js b/app/assets/javascripts/alchemy/alchemy.utils.js deleted file mode 100644 index 5c35fa08e0..0000000000 --- a/app/assets/javascripts/alchemy/alchemy.utils.js +++ /dev/null @@ -1,45 +0,0 @@ -Alchemy.on = function (eventName, baseSelector, targetSelector, callback) { - var baseNode = document.querySelector(baseSelector) - baseNode.addEventListener(eventName, function (evt) { - var targets = Array.from(baseNode.querySelectorAll(targetSelector)) - var currentNode = evt.target - while (currentNode !== baseNode) { - if (targets.includes(currentNode)) { - callback.call(currentNode, evt) - return - } - currentNode = currentNode.parentElement - } - }); -} - -Alchemy.ajax = function(method, url, data) { - var xhr = new XMLHttpRequest() - var token = document.querySelector('meta[name="csrf-token"]').attributes.content.textContent - var promise = new Promise(function (resolve, reject) { - xhr.onload = function() { - try { - resolve({ - data: JSON.parse(xhr.responseText), - status: xhr.status - }) - } catch (error) { - reject(new Error(JSON.parse(xhr.responseText).error)) - } - }; - xhr.onerror = function() { - reject(new Error(xhr.statusText)) - } - }); - xhr.open(method, url); - xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.setRequestHeader('X-CSRF-Token', token) - if (data) { - xhr.send(JSON.stringify(data)) - } else { - xhr.send() - } - - return promise -} diff --git a/app/assets/javascripts/alchemy/node_select.js b/app/assets/javascripts/alchemy/node_select.js new file mode 100644 index 0000000000..a358703905 --- /dev/null +++ b/app/assets/javascripts/alchemy/node_select.js @@ -0,0 +1,39 @@ +$.fn.alchemyNodeSelect = function (options) { + var renderNodeTemplate = function (node) { + return HandlebarsTemplates.node({ node: node }) + } + var queryParamsFromTerm = function (term) { + return { + filter: Object.assign( + { name_or_page_name_cont: term }, + options.query_params + ) + } + } + var resultsFromResponse = function (response) { + var meta = response.meta + var data = response.data + var more = meta.page * meta.per_page < meta.total_count + return { results: data, more: more } + } + + return this.select2({ + placeholder: options.placeholder, + allowClear: true, + minimumInputLength: 3, + initSelection: function (_$el, callback) { + if (options.initialSelection) { + callback(options.initialSelection) + } + }, + ajax: { + url: options.url, + datatype: "json", + quietMillis: 300, + data: queryParamsFromTerm, + results: resultsFromResponse + }, + formatSelection: renderNodeTemplate, + formatResult: renderNodeTemplate + }) +} diff --git a/app/assets/javascripts/alchemy/templates/index.js b/app/assets/javascripts/alchemy/templates/index.js index 0405d22de6..acbaf8b3db 100644 --- a/app/assets/javascripts/alchemy/templates/index.js +++ b/app/assets/javascripts/alchemy/templates/index.js @@ -1,3 +1,4 @@ //= require alchemy/templates/spinner //= require alchemy/templates/page //= require alchemy/templates/node_folder +//= require alchemy/templates/node diff --git a/app/assets/javascripts/alchemy/templates/node.hbs b/app/assets/javascripts/alchemy/templates/node.hbs new file mode 100644 index 0000000000..50d487d912 --- /dev/null +++ b/app/assets/javascripts/alchemy/templates/node.hbs @@ -0,0 +1,16 @@ +
+ +
+ + {{#each node.ancestors}} + {{ this.name }} /  + {{/each}} + + + {{ node.name }} + +
+
+ {{ node.url }} +
+
diff --git a/app/assets/javascripts/alchemy/templates/page.hbs b/app/assets/javascripts/alchemy/templates/page.hbs index 1686cacbe1..81748efe20 100644 --- a/app/assets/javascripts/alchemy/templates/page.hbs +++ b/app/assets/javascripts/alchemy/templates/page.hbs @@ -4,6 +4,6 @@ {{ page.name }} - /{{ page.urlname }} + {{ page.url_path }} diff --git a/app/assets/stylesheets/alchemy/_mixins.scss b/app/assets/stylesheets/alchemy/_mixins.scss index f68687d3f4..2251b133b6 100644 --- a/app/assets/stylesheets/alchemy/_mixins.scss +++ b/app/assets/stylesheets/alchemy/_mixins.scss @@ -49,9 +49,8 @@ border-color: $hover-border-color; } - &:active, &:active:focus { - border-color: $hover-border-color; - box-shadow: none; + &:active, &.active { + box-shadow: inset $button-box-shadow; } &:focus { diff --git a/app/assets/stylesheets/alchemy/_variables.scss b/app/assets/stylesheets/alchemy/_variables.scss index 343c204b5e..6bb3e2bd14 100644 --- a/app/assets/stylesheets/alchemy/_variables.scss +++ b/app/assets/stylesheets/alchemy/_variables.scss @@ -76,7 +76,7 @@ $button-text-shadow: none !default; $button-box-shadow: 0px 1px 1px -1px #333 !default; $button-focus-box-shadow: 0px 1px 1px 0px $button-focus-border-color !default; $button-padding: 0.55em 2em !default; -$small-button-padding: 0.4em 1.25em !default; +$small-button-padding: 0.4em 0.8em !default; $button-margin: $form-field-margin !default; $secondary-button-bg-color: transparent !default; @@ -108,7 +108,7 @@ $form-right-width: 65% !default; $sitemap-line-height: 32px !default; $sitemap-page-background-color: rgba($white, 0.75) !default; $sitemap-page-hover-color: rgba($light_yellow, 0.5) !default; -$sitemap-info-background-color: rgba($linked-color, 0.5) !default; +$sitemap-info-background-color: rgba($white, 0.5) !default; $sitemap-highlight-color: rgba(#fffba5, 0.5) !default; $main-menu-width: 150px !default; diff --git a/app/assets/stylesheets/alchemy/admin.scss b/app/assets/stylesheets/alchemy/admin.scss index 16e2418dc8..e657f50327 100644 --- a/app/assets/stylesheets/alchemy/admin.scss +++ b/app/assets/stylesheets/alchemy/admin.scss @@ -31,6 +31,7 @@ @import "alchemy/image_library"; @import "alchemy/labels"; @import "alchemy/nodes"; +@import "alchemy/node-select"; @import "alchemy/notices"; @import "alchemy/page-select"; @import "alchemy/pagination"; diff --git a/app/assets/stylesheets/alchemy/lists.scss b/app/assets/stylesheets/alchemy/lists.scss index 6463353737..890a52be88 100644 --- a/app/assets/stylesheets/alchemy/lists.scss +++ b/app/assets/stylesheets/alchemy/lists.scss @@ -13,14 +13,6 @@ ul.list { padding: 0; list-style-type: none; - &#layoutpages { - margin-top: 16px; - - li { - margin-left: 8px; - } - } - li { list-style-type: none; display: block; diff --git a/app/assets/stylesheets/alchemy/node-select.scss b/app/assets/stylesheets/alchemy/node-select.scss new file mode 100644 index 0000000000..ebccde55db --- /dev/null +++ b/app/assets/stylesheets/alchemy/node-select.scss @@ -0,0 +1,43 @@ +.node-select--node, +.node-select--node-url { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.node-select--node { + display: flex; + align-items: center; + + .icon { + margin: 0 8px 0 4px; + + .select2-highlighted & { + color: $white + } + } +} + +.node-select--node-name { + font-weight: bold; +} + +.node-select--node-url { + margin-left: auto; + padding: $default-padding 2*$default-padding; + color: $dark-gray; + font-size: $small-font-size; + + .select2-highlighted & { + color: $white + } +} + +// The container of the rendered node is slightly larger than other a line of +// text, as it would be for the Alchemy::EssencePage. Reducing the padding here from 0.6em +// to 0.4em centers the content nicely. +.essence_node { + .select2-container.alchemy_selectbox .select2-choice { + padding: 0.4em 0.75em; + } +} diff --git a/app/assets/stylesheets/alchemy/nodes.scss b/app/assets/stylesheets/alchemy/nodes.scss index c35731998c..4c2aa6512f 100644 --- a/app/assets/stylesheets/alchemy/nodes.scss +++ b/app/assets/stylesheets/alchemy/nodes.scss @@ -18,7 +18,7 @@ .node_page, .node_url { - width: 200px; + width: 250px; max-width: 45%; white-space: nowrap; text-overflow: ellipsis; @@ -64,7 +64,7 @@ margin: 0; padding: 0; - .folded > li { + &.folded > li { display: none; } } diff --git a/app/assets/stylesheets/alchemy/sitemap.scss b/app/assets/stylesheets/alchemy/sitemap.scss index 35fb24957c..13c5fc24a0 100644 --- a/app/assets/stylesheets/alchemy/sitemap.scss +++ b/app/assets/stylesheets/alchemy/sitemap.scss @@ -1,3 +1,6 @@ +$sitemap-url-large-width: 250px; +$sitemap-url-xlarge-width: 350px; + #sort_panel { background: $light-gray; padding: 47px 0 8px 0; @@ -20,7 +23,7 @@ #sitemap-wrapper { position: relative; - min-height: 100%; + min-height: calc(100vh - 96px); } .sitemap_pagename_link { @@ -28,23 +31,35 @@ padding: 0 10px; margin: 2px; text-decoration: none; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; &.inactive { color: #656565; } } -.redirect_url { +.sitemap_url { + display: none; float: right; - text-align: right; background-color: $sitemap-info-background-color; - line-height: $sitemap-line-height; + line-height: $sitemap-line-height - 2px; font-size: $small-font-size; - padding: 0 2*$default-padding; - max-width: 45%; + padding: 0 2 * $default-padding; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + border: 1px solid $sitemap-page-background-color; + + @media screen and (min-width: $large-screen-break-point) { + display: block; + width: $sitemap-url-large-width; + } + + @media screen and (min-width: 1440px) { + width: $sitemap-url-xlarge-width; + } } .sitemap_line_spacer { @@ -55,7 +70,7 @@ .sitemap_page { height: $sitemap-line-height; - margin: 3*$default-margin 0; + margin: 3 * $default-margin 0; position: relative; transition: background-color $transition-duration; @@ -89,13 +104,13 @@ width: 32px; line-height: $sitemap-line-height; float: left; - padding: 0 2*$default-padding; + padding: 0 2 * $default-padding; text-align: center; } .sitemap_right_tools { height: $sitemap-line-height; - padding: 0 2*$default-padding; + padding: 0 2 * $default-padding; float: right; .sitemap_tool { @@ -130,7 +145,9 @@ &.sorting { padding-top: 100px; - .page_icon { cursor: move } + .page_icon { + cursor: move; + } } .page_folder { @@ -180,28 +197,51 @@ .page_status { display: inline-block; + + .alchemy-dialog & { + display: block; + } } #sitemap_heading { + display: flex; padding: 0; - - .page_infos { - margin-right: 210px; - text-align: left; - float: right; - line-height: 28px; - background: transparent; - } + line-height: 28px; .page_name { - line-height: 28px; margin-left: 43px; } + + .page_urlname { + display: none; + margin-left: auto; + padding-left: 2 * $default-padding; + padding-right: 2 * $default-padding; + + @media screen and (min-width: $large-screen-break-point) { + display: block; + width: $sitemap-url-large-width; + } + + @media screen and (min-width: 1440px) { + width: $sitemap-url-xlarge-width; + } + } + + .page_status { + padding-left: 2 * $default-padding; + margin-right: 188px; + margin-left: auto; + + @media screen and (min-width: $large-screen-break-point) { + margin-left: initial; + } + } } #page_filter_result { display: none; - margin-left: 2*$default-margin; + margin-left: 2 * $default-margin; } .alchemy-dialog { @@ -213,6 +253,8 @@ margin: 0; padding: 0 24px 8px 8px; - .page_icon { cursor: default } + .page_icon { + cursor: default; + } } } diff --git a/app/controllers/alchemy/admin/attachments_controller.rb b/app/controllers/alchemy/admin/attachments_controller.rb index 54eecdc262..47bc3ca362 100644 --- a/app/controllers/alchemy/admin/attachments_controller.rb +++ b/app/controllers/alchemy/admin/attachments_controller.rb @@ -6,11 +6,11 @@ class AttachmentsController < ResourcesController include UploaderResponses include ArchiveOverlay - helper 'alchemy/admin/tags' + helper "alchemy/admin/tags" def index @query = Attachment.ransack(search_filter_params[:q]) - @query.sorts = 'name asc' if @query.sorts.empty? + @query.sorts = "name asc" if @query.sorts.empty? @attachments = @query.result if search_filter_params[:tagged_with].present? @@ -42,13 +42,13 @@ def create def update @attachment.update(attachment_attributes) - if attachment_attributes['file'].present? + if attachment_attributes["file"].present? handle_uploader_response(status: :accepted) else render_errors_or_redirect( @attachment, admin_attachments_path(search_filter_params), - Alchemy.t("File successfully updated") + Alchemy.t("File successfully updated"), ) end end @@ -57,14 +57,14 @@ def destroy name = @attachment.name @attachment.destroy @url = admin_attachments_url(search_filter_params) - flash[:notice] = Alchemy.t('File deleted successfully', name: name) + flash[:notice] = Alchemy.t("File deleted successfully", name: name) end def download @attachment = Attachment.find(params[:id]) send_file @attachment.file.path, { filename: @attachment.file_name, - type: @attachment.file_mime_type + type: @attachment.file_mime_type, } end @@ -74,8 +74,8 @@ def search_filter_params @_search_filter_params ||= params.except(*COMMON_SEARCH_FILTER_EXCLUDES + [:attachment]).permit( *common_search_filter_includes + [ :file_type, - :content_id - ] + :content_id, + ], ) end diff --git a/app/controllers/alchemy/admin/base_controller.rb b/app/controllers/alchemy/admin/base_controller.rb index 4e3e16c3cb..63a0a4e34e 100644 --- a/app/controllers/alchemy/admin/base_controller.rb +++ b/app/controllers/alchemy/admin/base_controller.rb @@ -6,7 +6,6 @@ class BaseController < Alchemy::BaseController include Userstamp include Locale - before_action { enforce_ssl if ssl_required? && !request.ssl? } before_action :load_locked_pages helper_method :clipboard_empty?, :trash_empty?, :get_clipboard, :is_admin? @@ -27,14 +26,14 @@ class BaseController < Alchemy::BaseController def leave authorize! :leave, :alchemy_admin - render template: '/alchemy/admin/leave', layout: !request.xhr? + render template: "/alchemy/admin/leave", layout: !request.xhr? end private # Disable layout rendering for xhr requests. def set_layout - request.xhr? ? false : 'alchemy/admin' + request.xhr? ? false : "alchemy/admin" end # Handles exceptions @@ -55,7 +54,7 @@ def show_error_notice(error) if request.xhr? render action: "error_notice" else - render '500', status: 500 + render "500", status: 500 end end @@ -105,7 +104,7 @@ def render_errors_or_redirect(object, redirect_url, flash_notice) flash[:notice] = Alchemy.t(flash_notice) do_redirect_to redirect_url else - render action: (params[:action] == 'update' ? 'edit' : 'new') + render action: (params[:action] == "update" ? "edit" : "new") end end @@ -113,7 +112,7 @@ def render_errors_or_redirect(object, redirect_url, flash_notice) # def do_redirect_to(url_or_path) respond_to do |format| - format.js { + format.js { @redirect_url = url_or_path render :redirect } @@ -131,7 +130,7 @@ def raise_exception? # Are we currently in the page edit mode page preview. def is_page_preview? - controller_path == 'alchemy/admin/pages' && action_name == 'show' + controller_path == "alchemy/admin/pages" && action_name == "show" end def load_locked_pages @@ -142,11 +141,11 @@ def load_locked_pages # def current_alchemy_site @current_alchemy_site ||= begin - site_id = params[:site_id] || session[:alchemy_site_id] - site = Site.find_by(id: site_id) || super - session[:alchemy_site_id] = site&.id - site - end + site_id = params[:site_id] || session[:alchemy_site_id] + site = Site.find_by(id: site_id) || super + session[:alchemy_site_id] = site&.id + site + end end end end diff --git a/app/controllers/alchemy/admin/clipboard_controller.rb b/app/controllers/alchemy/admin/clipboard_controller.rb index 3977e72ede..06c6733f4a 100644 --- a/app/controllers/alchemy/admin/clipboard_controller.rb +++ b/app/controllers/alchemy/admin/clipboard_controller.rb @@ -17,10 +17,10 @@ def index def insert @item = model_class.find(remarkable_params[:remarkable_id]) - unless @clipboard.detect { |item| item['id'] == remarkable_params[:remarkable_id] } + unless @clipboard.detect { |item| item["id"] == remarkable_params[:remarkable_id] } @clipboard << { - 'id' => remarkable_params[:remarkable_id], - 'action' => params[:remove] ? 'cut' : 'copy' + "id" => remarkable_params[:remarkable_id], + "action" => params[:remove] ? "cut" : "copy", } end respond_to do |format| @@ -30,7 +30,7 @@ def insert def remove @item = model_class.find(remarkable_params[:remarkable_id]) - @clipboard.delete_if { |item| item['id'] == remarkable_params[:remarkable_id] } + @clipboard.delete_if { |item| item["id"] == remarkable_params[:remarkable_id] } respond_to do |format| format.js end diff --git a/app/controllers/alchemy/admin/contents_controller.rb b/app/controllers/alchemy/admin/contents_controller.rb index d4685c266e..13b460dc69 100644 --- a/app/controllers/alchemy/admin/contents_controller.rb +++ b/app/controllers/alchemy/admin/contents_controller.rb @@ -3,7 +3,7 @@ module Alchemy module Admin class ContentsController < Alchemy::Admin::BaseController - helper 'alchemy/admin/essences' + helper "alchemy/admin/essences" authorize_resource class: Alchemy::Content diff --git a/app/controllers/alchemy/admin/dashboard_controller.rb b/app/controllers/alchemy/admin/dashboard_controller.rb index 0ec91fcedc..cde544c502 100644 --- a/app/controllers/alchemy/admin/dashboard_controller.rb +++ b/app/controllers/alchemy/admin/dashboard_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'net/http' -require 'alchemy/version' +require "net/http" +require "alchemy/version" module Alchemy module Admin @@ -28,9 +28,9 @@ def info def update_check @alchemy_version = Alchemy.version if @alchemy_version < latest_alchemy_version - render plain: 'true' + render plain: "true" else - render plain: 'false' + render plain: "false" end rescue UpdateServiceUnavailable => e render plain: e, status: 503 @@ -41,7 +41,7 @@ def update_check # Returns latest alchemy version. def latest_alchemy_version versions = get_alchemy_versions - return '' if versions.blank? + return "" if versions.blank? # reject any non release version versions.reject! { |v| v =~ /[a-z]/ } @@ -54,14 +54,14 @@ def get_alchemy_versions response = query_rubygems if response.code == "200" alchemy_versions = JSON.parse(response.body) - alchemy_versions.collect { |h| h['number'] }.sort + alchemy_versions.collect { |h| h["number"] }.sort else # rubygems.org not available? # then we try github response = query_github if response.code == "200" alchemy_tags = JSON.parse(response.body) - alchemy_tags.collect { |h| h['name'].tr('v', '') }.sort + alchemy_tags.collect { |h| h["name"].tr("v", "") }.sort else # no luck at all? raise UpdateServiceUnavailable @@ -71,12 +71,12 @@ def get_alchemy_versions # Query the RubyGems API for Alchemy versions. def query_rubygems - make_api_request('https://rubygems.org/api/v1/versions/alchemy_cms.json') + make_api_request("https://rubygems.org/api/v1/versions/alchemy_cms.json") end # Query the GitHub API for Alchemy tags. def query_github - make_api_request('https://api.github.com/repos/AlchemyCMS/alchemy_cms/tags') + make_api_request("https://api.github.com/repos/AlchemyCMS/alchemy_cms/tags") end # Make a HTTP API request for given request url. diff --git a/app/controllers/alchemy/admin/elements_controller.rb b/app/controllers/alchemy/admin/elements_controller.rb index 11520da98a..54ad29933c 100644 --- a/app/controllers/alchemy/admin/elements_controller.rb +++ b/app/controllers/alchemy/admin/elements_controller.rb @@ -17,7 +17,7 @@ def new @parent_element = Element.find_by(id: params[:parent_element_id]) @elements = @page.available_elements_within_current_scope(@parent_element) @element = @page.elements.build - @clipboard = get_clipboard('elements') + @clipboard = get_clipboard("elements") @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page) end @@ -30,7 +30,7 @@ def create else @element = Element.create(create_element_params) end - if @page.definition['insert_elements_at'] == 'top' + if @page.definition["insert_elements_at"] == "top" @insert_at_top = true @element.move_to_top end @@ -40,7 +40,7 @@ def create else @element.page = @page @elements = @page.available_element_definitions - @clipboard = get_clipboard('elements') + @clipboard = get_clipboard("elements") @clipboard_items = Element.all_from_clipboard_for_page(@clipboard, @page) render :new end @@ -56,7 +56,7 @@ def update @element_validated = @element.update(element_params) else @element_validated = false - @notice = Alchemy.t('Validation failed') + @notice = Alchemy.t("Validation failed") @error_message = "

#{@notice}

#{Alchemy.t(:content_validations_headline)}

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

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

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

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

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

    Root page not found.

    -

    Please run bin/rake db:seed task.

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

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

    +

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

    <%= @page.layout_display_name %>

    - +

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

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