From eb3dd15da109cb3e052b1909912bb3e63129faa1 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Wed, 10 Apr 2024 16:20:03 +0300 Subject: [PATCH 001/221] feat(slb-146): silverback autosave and preview module --- .lagoon/Dockerfile | 2 +- .vscode/settings.json | 4 +- apps/cms/composer.json | 3 + apps/cms/config/sync/core.extension.yml | 1 + .../sync/silverback_autosave.messages.yml | 3 + .../sync/silverback_autosave.settings.yml | 15 + apps/cms/config/sync/system.performance.yml | 4 +- apps/cms/patches/fetch-entity-patch | 40 ++ .../drupal/silverback_autosave/LICENSE.txt | 339 ++++++++++++ .../drupal/silverback_autosave/composer.json | 6 + .../install/silverback_autosave.messages.yml | 1 + .../install/silverback_autosave.settings.yml | 10 + .../schema/silverback_autosave.schema.yml | 56 ++ .../css/silverback_autosave.css | 15 + .../js/silverback_autosave.js | 512 ++++++++++++++++++ .../silverback_autosave.info.yml | 8 + .../silverback_autosave.install | 111 ++++ .../silverback_autosave.libraries.yml | 16 + .../silverback_autosave.links.menu.yml | 6 + .../silverback_autosave.module | 147 +++++ .../silverback_autosave.routing.yml | 7 + .../silverback_autosave.services.yml | 52 ++ .../src/Ajax/OpenAutosaveDisabledDialog.php | 21 + .../Extension/ModuleHandlerEmptyAlter.php | 22 + .../Theme/ThemeManagerEmptyAlter.php | 22 + .../src/EventSubscriber/ConfigSubscriber.php | 130 +++++ .../src/Form/AutosaveButtonClickedTrait.php | 72 +++ .../src/Form/AutosaveEntityFormHandler.php | 327 +++++++++++ .../AutosaveEntityFormHandlerInterface.php | 25 + .../src/Form/AutosaveFormAlterTrait.php | 281 ++++++++++ .../src/Form/AutosaveFormBuilder.php | 254 +++++++++ .../src/Form/AutosaveFormErrorHandler.php | 43 ++ .../src/Form/AutosaveFormInterface.php | 111 ++++ .../src/Form/AutosaveFormSettingsForm.php | 213 ++++++++ .../src/Form/AutosaveFormValidator.php | 80 +++ .../AutosaveEntityFormDatabaseStorage.php | 310 +++++++++++ .../AutosaveEntityFormStorageInterface.php | 203 +++++++ .../Storage/AutosaveFormBackendInterface.php | 83 +++ packages/ui/src/utils/operation.ts | 5 +- 39 files changed, 3553 insertions(+), 7 deletions(-) create mode 100644 apps/cms/config/sync/silverback_autosave.messages.yml create mode 100644 apps/cms/config/sync/silverback_autosave.settings.yml create mode 100644 apps/cms/patches/fetch-entity-patch create mode 100644 packages/drupal/silverback_autosave/LICENSE.txt create mode 100644 packages/drupal/silverback_autosave/composer.json create mode 100644 packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml create mode 100644 packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml create mode 100644 packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml create mode 100644 packages/drupal/silverback_autosave/css/silverback_autosave.css create mode 100644 packages/drupal/silverback_autosave/js/silverback_autosave.js create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.info.yml create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.install create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.libraries.yml create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.module create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.routing.yml create mode 100644 packages/drupal/silverback_autosave/silverback_autosave.services.yml create mode 100644 packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php create mode 100644 packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php create mode 100644 packages/drupal/silverback_autosave/src/EmptyAlter/Theme/ThemeManagerEmptyAlter.php create mode 100644 packages/drupal/silverback_autosave/src/EventSubscriber/ConfigSubscriber.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormSettingsForm.php create mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php create mode 100644 packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php create mode 100644 packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php create mode 100644 packages/drupal/silverback_autosave/src/Storage/AutosaveFormBackendInterface.php diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile index f81105156..333bcd2cb 100644 --- a/.lagoon/Dockerfile +++ b/.lagoon/Dockerfile @@ -15,7 +15,7 @@ RUN npm install -g pnpm@8.6.12 && pnpm config set store-dir /tmp/cache/pnpm # Copy pnpm lockfile and install dependencies. COPY pnpm-lock.yaml .npmrc /app/ -#COPY patches /app/patches +COPY patches /app/patches RUN --mount=type=cache,target=/tmp/cache pnpm fetch && \ # There is a bug in pnpm: `pnpm fetch` creates _some_ node_modules folders # with _some_ packages. This can lead to an incomplete package installation. diff --git a/.vscode/settings.json b/.vscode/settings.json index 4929af1d2..df21dac26 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,5 +31,7 @@ "*.profile": "php" }, "graphql-config.load.filePath": ".graphqlrc.json", - "graphql-config.load.rootDir": "packages/schema" + "graphql-config.load.rootDir": "packages/schema", + "phpsab.executablePathCBF": "./apps/cms/vendor/bin/phpcbf", + "phpsab.executablePathCS": "./apps/cms/vendor/bin/phpcs" } diff --git a/apps/cms/composer.json b/apps/cms/composer.json index d7ca377fd..6235b4cf4 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -96,6 +96,9 @@ "patches": { "drupal/userprotect": { "Fix site install": "https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch" + }, + "amazeelabs/silverback_gatsby": { + "Autosave preview": "./patches/fetch-entity-patch" } }, "patchLevel": { diff --git a/apps/cms/config/sync/core.extension.yml b/apps/cms/config/sync/core.extension.yml index 42ce257f4..c32cf22ef 100644 --- a/apps/cms/config/sync/core.extension.yml +++ b/apps/cms/config/sync/core.extension.yml @@ -59,6 +59,7 @@ module: role_delegation: 0 serialization: 0 shortcut: 0 + silverback_autosave: 0 silverback_campaign_urls: 0 silverback_cloudinary: 0 silverback_external_preview: 0 diff --git a/apps/cms/config/sync/silverback_autosave.messages.yml b/apps/cms/config/sync/silverback_autosave.messages.yml new file mode 100644 index 000000000..4b81262fb --- /dev/null +++ b/apps/cms/config/sync/silverback_autosave.messages.yml @@ -0,0 +1,3 @@ +_core: + default_config_hash: In3MY-CuVp64RxCeU5X6rsEhR6jR4Si8OG6BX18DALg +entity_saved_in_background_alert_message: 'The content has been modified elsewhere. As a result, autosaving has been disabled and autosaved states for the current page have been deleted. Autosave will be reactivated after reloading the page.' diff --git a/apps/cms/config/sync/silverback_autosave.settings.yml b/apps/cms/config/sync/silverback_autosave.settings.yml new file mode 100644 index 000000000..3bd7d1556 --- /dev/null +++ b/apps/cms/config/sync/silverback_autosave.settings.yml @@ -0,0 +1,15 @@ +_core: + default_config_hash: HaTNbtQe6_A0WF-ZElHNWcv0VK_aqOj5lBEFhAgY85s +interval: 10000 +only_on_form_change: false +active_on: + content_entity_forms: true + config_entity_forms: false +notification: + active: true + message: 'Updating preview...' + delay: 1000 +allowed_content_entity_types: + node: + bundles: + page: page diff --git a/apps/cms/config/sync/system.performance.yml b/apps/cms/config/sync/system.performance.yml index d8dbe9a10..bf0902d1c 100644 --- a/apps/cms/config/sync/system.performance.yml +++ b/apps/cms/config/sync/system.performance.yml @@ -4,7 +4,7 @@ cache: page: max_age: 0 css: - preprocess: true + preprocess: false gzip: true fast_404: enabled: true @@ -12,5 +12,5 @@ fast_404: exclude_paths: '/\/(?:styles|imagecache)\//' html: '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

' js: - preprocess: true + preprocess: false gzip: true diff --git a/apps/cms/patches/fetch-entity-patch b/apps/cms/patches/fetch-entity-patch new file mode 100644 index 000000000..b3a5aeaba --- /dev/null +++ b/apps/cms/patches/fetch-entity-patch @@ -0,0 +1,40 @@ +diff --git a/src/Plugin/GraphQL/DataProducer/FetchEntity.php b/src/Plugin/GraphQL/DataProducer/FetchEntity.php +index ffef6a8..7c84154 100644 +--- a/src/Plugin/GraphQL/DataProducer/FetchEntity.php ++++ b/src/Plugin/GraphQL/DataProducer/FetchEntity.php +@@ -250,6 +250,35 @@ class FetchEntity extends DataProducerPluginBase implements ContainerFactoryPlug + } + } + ++ // Autosave: get autosaved values. ++ if (\Drupal::service('module_handler')->moduleExists('silverback_autosave')) { ++ $context->mergeCacheMaxAge(0); ++ // @todo Add DI to both. ++ $service = \Drupal::service('silverback_autosave.entity_form_storage'); ++ /** ++ * Quick and dirty because this causes leaked metadata error. ++ * $entityForm = \Drupal::service('entity.form_builder')->getForm($entity, 'edit'); ++ * $form_id = $entityForm['form_id']['#value']; ++ */ ++ $form_id = "{$entity->getEntityTypeId()}_{$entity->bundle()}_edit_form"; ++ $autosaved_state = $service->getEntityAndFormState($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), \Drupal::currentUser()->id()); ++ /** @var \Drupal\Core\Entity\EntityInterface $autosaved_entity */ ++ $autosaved_entity = $autosaved_state['entity'] ?? NULL; ++ /** @var \Drupal\Core\Form\FormStateInterface $autosaved_form_state */ ++ $autosaved_form_state = $autosaved_state['form_state'] ?? []; ++ if ($autosaved_entity && !empty($autosaved_form_state)) { ++ $current_user_input = $autosaved_form_state->getUserInput(); ++ foreach ($autosaved_entity->getFields() as $name => $field) { ++ if (in_array($name, ['title', 'body']) || str_starts_with($name, 'field_')) { ++ if (isset($current_user_input[$name])) { ++ $field->setValue($current_user_input[$name]); ++ } ++ } ++ } ++ return $autosaved_entity; ++ } ++ } ++ + return $entity; + }); + } diff --git a/packages/drupal/silverback_autosave/LICENSE.txt b/packages/drupal/silverback_autosave/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/packages/drupal/silverback_autosave/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/packages/drupal/silverback_autosave/composer.json b/packages/drupal/silverback_autosave/composer.json new file mode 100644 index 000000000..d1359e6b8 --- /dev/null +++ b/packages/drupal/silverback_autosave/composer.json @@ -0,0 +1,6 @@ +{ + "name": "drupal/silverback_autosave", + "type": "drupal-module", + "description": "Adds autosave feature on forms.", + "license": "GPL-2.0-or-later" +} diff --git a/packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml b/packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml new file mode 100644 index 000000000..338d94046 --- /dev/null +++ b/packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml @@ -0,0 +1 @@ +entity_saved_in_background_alert_message: 'The content has been modified elsewhere. As a result, autosaving has been disabled and autosaved states for the current page have been deleted. Autosave will be reactivated after reloading the page.' \ No newline at end of file diff --git a/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml b/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml new file mode 100644 index 000000000..c5a0c78aa --- /dev/null +++ b/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml @@ -0,0 +1,10 @@ +interval: 10000 +only_on_form_change: FALSE +active_on: + content_entity_forms: TRUE + config_entity_forms: FALSE +notification: + active: TRUE + message: "Updating preview..." + delay: 1000 +allowed_content_entity_types: { } diff --git a/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml b/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml new file mode 100644 index 000000000..86bef3549 --- /dev/null +++ b/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml @@ -0,0 +1,56 @@ +# Schema for the configuration files of the Autosave Form module. + +silverback_autosave.settings: + type: config_object + label: 'Autosave Form Settings' + mapping: + interval: + type: integer + label: 'The interval used to trigger autosave.' + only_on_form_change: + type: boolean + label: 'Run autosave submission within the specified interval only if there was a form change.' + active_on: + type: mapping + label: 'Active on' + mapping: + content_entity_forms: + type: boolean + label: 'Content entity forms' + config_entity_forms: + type: boolean + label: 'Config entity forms' + notification: + type: mapping + label: 'Notification' + mapping: + active: + type: boolean + label: 'Show notification each time autosave is triggered' + message: + type: text + label: 'Notification message' + delay: + type: integer + label: 'Notification duration' + allowed_content_entity_types: + type: sequence + label: 'Allowed content entity types' + sequence: + type: mapping + label: 'Entity type' + mapping: + bundles: + type: sequence + label: 'Allowed bundles of the entity type' + sequence: + type: string + label: 'Bundle' + +silverback_autosave.messages: + type: config_object + label: 'Autosave Form Messages' + mapping: + entity_saved_in_background_alert_message: + type: string + label: 'The alert message to show to the user if the entity has been saved meanwhile in the background.' diff --git a/packages/drupal/silverback_autosave/css/silverback_autosave.css b/packages/drupal/silverback_autosave/css/silverback_autosave.css new file mode 100644 index 000000000..85d069e50 --- /dev/null +++ b/packages/drupal/silverback_autosave/css/silverback_autosave.css @@ -0,0 +1,15 @@ +#autosave-notification { + display: none; + position: fixed; + bottom: 5px; + right: 5px; + z-index: 9999; + background-color: black; + color: white; + padding: 0.5em 1em; +} + +#autosave-notification:before { + content: "\2714"; + padding-right: 10px; +} diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js new file mode 100644 index 000000000..be91b640e --- /dev/null +++ b/packages/drupal/silverback_autosave/js/silverback_autosave.js @@ -0,0 +1,512 @@ +/** + * @file + * Defines Javascript behaviors for the silverback_autosave module. + */ + +(function ($, Drupal, drupalSettings, once) { + 'use strict'; + + /** + * Define defaults. + */ + Drupal.autosaveForm = { + timer: null, + interval: null, + onlyOnFormChange: false, + autosaveFormRunning: false, + autosaveFormInstances: {}, + initialized: false, + formHasErrors: false, + message: '', + dialog_options: [], + autosave_submit_class: 'autosave-form-save', + autosave_restore_class: 'autosave-form-restore', + autosave_reject_class: 'autosave-form-reject', + notification: { + active: true, + message: Drupal.t('Updating preview...'), + delay: 1000, + }, + form: null, + }; + + /** + * Add a variable which determines if the window is being unloaded. + */ + Drupal.autosaveForm.beforeUnloadCalled = false; + $(window).on('beforeunload pagehide', function () { + Drupal.autosaveForm.beforeUnloadCalled = true; + }); + + /** + * Default dialog options. + */ + Drupal.autosaveForm.defaultDialogOptions = { + open: function () { + $(this).siblings('.ui-dialog-titlebar').remove(); + }, + modal: true, + zIndex: 10000, + position: { my: 'top', at: 'top+25%' }, + autoOpen: true, + width: 'auto', + resizable: false, + closeOnEscape: false, + }; + + /** + * Behaviors the autosave form feature. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the autosave behavior. + */ + Drupal.behaviors.autosaveForm = { + attach: function (context, settings) { + var $context = $(context); + var autosave_submit = $context.find( + 'form .' + Drupal.autosaveForm.autosave_submit_class, + ); + + // Disable autosave when the form is submitted in order to prevent race + // conditions and creating further autosave entries after the entity is + // saved. + if (autosave_submit.length > 0) { + Drupal.autosaveForm.form = $(autosave_submit[0]).parents( + 'form.autosave-form', + ); + + Drupal.autosaveForm.form.submit(function () { + if (Drupal.autosaveForm.autosaveFormRunning) { + Drupal.autosaveForm.autosaveFormRunning = false; + clearInterval(Drupal.autosaveForm.timer); + Drupal.autosaveForm.timer = null; + } + }); + } + + if (settings.hasOwnProperty('autosaveForm')) { + $.extend(Drupal.autosaveForm, settings.autosaveForm); + } + + if (Drupal.autosaveForm.initialized) { + // If requested so turn off ajax submission. + if ( + !Drupal.autosaveForm.autosaveFormRunning && + Drupal.autosaveForm.timer + ) { + clearInterval(Drupal.autosaveForm.timer); + Drupal.autosaveForm.timer = null; + } else { + return; + } + } + + // Continue to show the dialog or activate autosave functionality only in + // case the context contains the autosave submit and it is not disabled. + if ( + autosave_submit.length === 0 || + autosave_submit.is(':disabled') || + autosave_submit.hasClass('is-disabled') + ) { + return; + } + Drupal.autosaveForm.autosaveSubmit = autosave_submit; + + if ( + !Drupal.autosaveForm.initialized && + !Drupal.autosaveForm.autosaveFormRunning + ) { + Drupal.autosaveForm.initialized = true; + + $('
') + .appendTo('body') + .append(Drupal.autosaveForm.notification.message); + + // Show the resume/discard confirmation message only if the form does + // not contain any errors, otherwise continue with normal autosave + // submissions. This is for the use case where a form is submitted but + // returned to the user with validation errors in which case we should + // not show the resume/discard message but continue the autosave + // submissions. + if (Drupal.autosaveForm.message && !Drupal.autosaveForm.formHasErrors) { + var dialogOptions = { + buttons: { + button_confirm: { + text: Drupal.t('Resume editing'), + class: 'autosave-form-resume-button', + click: function () { + // Non ajax buttons are bound to click. + // autosave-form-restore + $('.' + Drupal.autosaveForm.autosave_restore_class).trigger( + 'click', + ); + }, + }, + button_reject: { + text: Drupal.t('Discard'), + class: 'autosave-form-reject-button', + click: function () { + triggerAjaxSubmitWithoutProgressIndication( + Drupal.autosaveForm.autosave_reject_class, + true, + ); + $(this).dialog('close'); + }, + primary: true, + }, + }, + close: function (event, ui) { + $(this).remove(); + $(context) + .find('.' + Drupal.autosaveForm.autosave_restore_class) + .remove(); + $(context) + .find('.' + Drupal.autosaveForm.autosave_reject_class) + .remove(); + $(context).find('.autosave-form-restore-discard').remove(); + autosavePeriodic(); + }, + }; + + $.extend( + true, + dialogOptions, + Drupal.autosaveForm.defaultDialogOptions, + Drupal.autosaveForm.dialog_options, + ); + + $('
') + .appendTo('body') + .html('
' + Drupal.autosaveForm.message + '
') + .dialog(dialogOptions); + + // Temp + const btn = $('.autosave-form-restore'); + $('.ui-dialog-buttonset').append(btn[0]); + // .... + } else { + autosavePeriodic(); + } + } + + /** + * Returns the ajax instance corresponding to an element. + * + * @param {string} class_name + * The element class name for which to return its ajax instance. + * + * @return {Drupal.Ajax | null} + * The ajax instance if found, otherwise null. + */ + function findAjaxInstance(class_name) { + if ( + !Drupal.autosaveForm.autosaveFormInstances.hasOwnProperty(class_name) + ) { + var element = document.getElementsByClassName(class_name)[0]; + var ajax = null; + var selector = '#' + element.id; + for (var index in Drupal.ajax.instances) { + if (Drupal.ajax.instances.hasOwnProperty(index)) { + var ajaxInstance = Drupal.ajax.instances[index]; + if (ajaxInstance && ajaxInstance.selector === selector) { + ajax = ajaxInstance; + break; + } + } + } + Drupal.autosaveForm.autosaveFormInstances[class_name] = ajax; + } + return Drupal.autosaveForm.autosaveFormInstances[class_name]; + } + + /** + * Triggers an ajax submit based on the class of the ajax element. + * + * @param {string} ajax_class + * The class of the ajax element. + * @param {boolean} skip_checks + * Skip checks. + */ + function triggerAjaxSubmitWithoutProgressIndication( + ajax_class, + skip_checks = false, + ) { + // If the autosave button suddenly gets the 'is-disabled' class then + // autosave submission should not run until the class is removed. + if ( + Drupal.autosaveForm.autosaveSubmit.is(':disabled') || + Drupal.autosaveForm.autosaveSubmit.hasClass('is-disabled') + ) { + return; + } + + // If configured so run only in the specified interval only if there + // was a form change. + if ( + !skip_checks && + Drupal.autosaveForm.onlyOnFormChange && + !Drupal.autosaveForm.form.data('autosave-form-changed') + ) { + return; + } + + // If there are gutenberg fields, we need to make sure the textarea value + // is updated before autosaving. + const $gutenbergFields = $('.field--gutenberg textarea').first(); + if ($gutenbergFields.length) { + const { data } = window.wp; + $gutenbergFields.each(function (i, element) { + // Update editor textarea with gutenberg content. + $(element).val(data.select('core/editor').getEditedPostContent()); + // We need to update the 'editor-value-is-changed' flag otherwise + // the content won't be updated. + $(element).data({ 'editor-value-is-changed': true }); + $(element).attr('data-editor-value-is-changed', true); + }); + } + + var ajax = findAjaxInstance(ajax_class); + if (ajax) { + if (Drupal.autosaveForm.notification.active) { + $('#autosave-notification') + .fadeIn() + .delay(Drupal.autosaveForm.notification.delay) + .fadeOut(); + } + ajax.success = function (response, status) { + // @todo: On success send a post request to update the preview + + // If interval submission is configured to happen only on form + // change, then reset the changed flag on successful autosave. + if ( + Drupal.autosaveForm.onlyOnFormChange && + Drupal.autosaveForm.form.data('autosave-form-changed') + ) { + Drupal.autosaveForm.form.data('autosave-form-changed', false); + } + + // Call original method with main functionality. + Drupal.Ajax.prototype.success.call(this, response, status); + }; + ajax.options.error = function ( + xmlhttprequest, + text_status, + error_thrown, + ) { + if (xmlhttprequest.status === 0 || xmlhttprequest.status >= 400) { + Drupal.autosaveForm.autosaveFormRunning = false; + clearInterval(Drupal.autosaveForm.timer); + Drupal.autosaveForm.timer = null; + + if (!Drupal.autosaveForm.beforeUnloadCalled) { + var dialogOptions = { + buttons: { + button_confirm: { + text: Drupal.t('Ok'), + primary: true, + click: function () { + $(this).dialog('close'); + }, + }, + }, + }; + $.extend( + true, + dialogOptions, + Drupal.autosaveForm.defaultDialogOptions, + ); + + $('
') + .appendTo('body') + .html( + '
' + + Drupal.t( + 'A server error occurred during autosaving the current page. As a result autosave is disabled. To activate it please revisit the page and continue the editing from the last autosaved state of the current page.', + ) + + '
', + ) + .dialog(dialogOptions); + } + } + }; + // Disable progress indication. + ajax.progress = false; + $(ajax.element).trigger( + ajax.element_settings + ? ajax.element_settings.event + : ajax.elementSettings.event, + ); + } + } + + /** + * Starts periodic autosave submission. + */ + function autosavePeriodic() { + if (Drupal.autosaveForm.interval) { + Drupal.autosaveForm.autosaveFormRunning = true; + + // Run the autosave submission at the beginning to capture the user + // input and compare it later for changes, however wait for sometime + // until triggering the submission in order to let all the Drupal + // behaviors be executed and probably alter the page before doing the + // first submission, otherwise we might capture not the correct user + // input and on the second submission detect changes even if there + // aren't actually any changes. + // @todo Remove this and let autosave attach itself instead as the + // last behavior as soon as the following issues are fixed: + // @see https://www.drupal.org/node/2367655 + // @see https://www.drupal.org/node/2474019 + if (Drupal.autosaveForm.interval > 500) { + setTimeout(function () { + triggerAjaxSubmitWithoutProgressIndication( + Drupal.autosaveForm.autosave_submit_class, + true, + ); + }, 500); + } + + Drupal.autosaveForm.timer = setInterval(function () { + if (!Drupal.ajax.instances.some(isAjaxing)) { + triggerAjaxSubmitWithoutProgressIndication( + Drupal.autosaveForm.autosave_submit_class, + ); + } + }, Drupal.autosaveForm.interval); + } + } + + /** + * Checks if an ajax instance is currently running a submission. + * + * @param {Drupal.Ajax} instance + * The ajax instance. + * + * @return {boolean} + * TRUE if the ajax instance is in a state of submitting. + */ + function isAjaxing(instance) { + return ( + instance && + instance.hasOwnProperty('ajaxing') && + instance.ajaxing === true + ); + } + }, + }; + + /** + * Command to open a dialog for notifying that autosave has been disabled. + * + * We have to use a dedicated command, as otherwise there is no way to define + * the "click" on the button and close the dialog when the button is clicked. + * + * @param {Drupal.Ajax} ajax + * The Drupal Ajax object. + * @param {object} response + * Object holding the server response. + * @param {number} [status] + * The HTTP status code. + */ + Drupal.AjaxCommands.prototype.openAutosaveDisabledDialog = function ( + ajax, + response, + status, + ) { + response.dialogOptions.buttons = { + button_confirm: { + text: Drupal.t('Ok'), + click: function () { + $(this).dialog('close'); + }, + }, + }; + // Remove the "x" button and force confirmation through the "Ok" button. + response.dialogOptions.open = function () { + $(this) + .siblings('.ui-dialog-titlebar') + .find('.ui-dialog-titlebar-close') + .remove(); + }; + + Drupal.AjaxCommands.prototype.openDialog(ajax, response, status); + }; + + /** + * Attach behaviors to monitor changes on entity forms. + * + * This is a modified version of the monitoring functionality provided by + * https://www.drupal.org/project/entity_form_monitor. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches triggers. + */ + Drupal.behaviors.autosaveFormMonitor = { + attach: function (context, settings) { + var $context = $(context); + var $form = $context.find('form.autosave-form'); + if ($form.length === 0) { + var $form = $context.parents('form.autosave-form'); + } + + // Disable autosave when the form is submitted in order to prevent race + // conditions and creating further autosave entries after the entity is + // saved. + if ($form.length > 0) { + // Detect new elements added through field widgets. + if ($context.find('.ajax-new-content').length > 0) { + $form.data('autosave-form-changed', true); + } + + // Add a change handler that will help us determine if any inputs + // inside the entity forms have changed values. + var inputs = $form + .find(':input, [contenteditable="true"]') + // Filter out buttons + .not('button, input[type="button"]'); + + $(once('autosave-form-input-monitor', inputs)) + .on('change textInput input', function (event) { + var $form = $(event.target).parents('.autosave-form').first(); + if ($form.length) { + var val = $(this).val(); + if (val != $(this).attr('autosave-old-val')) { + $(this).attr('autosave-old-val', val); + $form.data('autosave-form-changed', true); + } + } + }) + // Detect Ajax changes e.g. removing an element. + .on('mousedown', function (event) { + if (event.target.type === 'submit') { + $form.data('autosave-form-changed', true); + } + }); + + // Add change handlers to any CKEditor instances. + if (typeof CKEDITOR !== 'undefined') { + CKEDITOR.on('instanceCreated', function (event) { + event.editor.on('change', function (event) { + // Handle CKEditor change events. + if ( + typeof event.editor !== 'undefined' && + typeof event.target === 'undefined' + ) { + event.target = event.editor.element.$; + } + + var $form = $(event.target).parents('.autosave-form').first(); + if ($form.length) { + $form.data('autosave-form-changed', true); + } + }); + }); + } + } + }, + }; +})(jQuery, Drupal, drupalSettings, once); diff --git a/packages/drupal/silverback_autosave/silverback_autosave.info.yml b/packages/drupal/silverback_autosave/silverback_autosave.info.yml new file mode 100644 index 000000000..6608a328d --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.info.yml @@ -0,0 +1,8 @@ +name: Silverback autosave +type: module +description: 'Adds autosave & preview feature on gutenberg nodes.' +package: Content +core_version_requirement: ^9.5 || ^10 +configure: silverback_autosave.admin_settings +dependencies: + - drupal:system diff --git a/packages/drupal/silverback_autosave/silverback_autosave.install b/packages/drupal/silverback_autosave/silverback_autosave.install new file mode 100644 index 000000000..79270393c --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.install @@ -0,0 +1,111 @@ + 'Saves the form state of partially filled content entity form for restoration by the silverback_autosave module.', + 'fields' => [ + 'form_id' => [ + 'type' => 'varchar_ascii', + 'length' => AutosaveEntityFormStorageInterface::silverback_autosave_FORM_ID_LENGTH, + 'not null' => TRUE, + ], + // We need the form session id as it is possible that the user opens the + // same form in two tabs and concurrently edits it. Therefore we have to + // assign each form session to an unique auto save session. + // We use the form build id for this and add an extra length to cover any + // case. + 'form_session_id' => [ + 'type' => 'varchar_ascii', + 'length' => AutosaveEntityFormStorageInterface::silverback_autosave_FORM_ID_LENGTH, + 'not null' => TRUE, + ], + 'entity_type_id' => [ + 'type' => 'varchar_ascii', + 'length' => EntityTypeInterface::ID_MAX_LENGTH, + 'not null' => TRUE, + ], + 'entity_id' => [ + 'type' => 'varchar_ascii', + 'length' => EntityTypeInterface::ID_MAX_LENGTH, + 'not null' => TRUE, + ], + 'langcode' => [ + 'type' => 'varchar_ascii', + 'length' => 12, + 'not null' => TRUE, + ], + 'uid' => [ + 'type' => 'int', + 'not null' => TRUE, + ], + 'timestamp' => [ + 'type' => 'int', + 'not null' => TRUE, + ], + 'entity' => [ + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ], + 'form_state' => [ + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ], + ], + 'primary key' => ['form_id', 'form_session_id', 'entity_type_id', 'entity_id', 'langcode', 'uid', 'timestamp'], + ]; + + return $schema; +} + +/** + * Implements hook_install(). + */ +function silverback_autosave_install() { + // For some reasons it might happen that the autosave form library is not + // loaded or the handler is missing because on module install those both + // cache entries have not been invalidated, therefore we do it manually here. + Cache::invalidateTags(['entity_types', 'library_info']); +} + +/** + * Add the settings for the notification to the settings config. + */ +function silverback_autosave_update_8001() { + $config = \Drupal::configFactory()->getEditable('silverback_autosave.settings'); + if ($config->get('notification') === NULL) { + $config->set('notification', [ + 'active' => TRUE, + 'message' => 'Saving draft...', + 'delay' => 1000, + ]); + $config->save(TRUE); + } +} + +/** + * Populates the setting "only_on_form_change". + */ +function silverback_autosave_update_8002() { + $config = \Drupal::configFactory()->getEditable('silverback_autosave.settings'); + if ($config->get('only_on_form_change') === NULL) { + $config->set('only_on_form_change', FALSE); + $config->save(TRUE); + } +} diff --git a/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml b/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml new file mode 100644 index 000000000..aa5489ad3 --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml @@ -0,0 +1,16 @@ +drupal.silverback_autosave: + version: VERSION + css: + theme: + css/silverback_autosave.css: {} + js: + js/silverback_autosave.js: {} + dependencies: + - core/jquery + - core/drupal + - core/drupalSettings + - core/drupal.dialog + - core/drupal.dialog.ajax + - core/drupal.ajax + - core/once + - gutenberg/edit-node diff --git a/packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml b/packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml new file mode 100644 index 000000000..cb94aa739 --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml @@ -0,0 +1,6 @@ +silverback_autosave.admin_settings: + title: 'Silverback autosave and preview settings' + parent: system.admin_config_content + description: 'Change interval and forms on which autosave is active.' + route_name: silverback_autosave.admin_settings + weight: -20 diff --git a/packages/drupal/silverback_autosave/silverback_autosave.module b/packages/drupal/silverback_autosave/silverback_autosave.module new file mode 100644 index 000000000..2f3dabfdb --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.module @@ -0,0 +1,147 @@ +hasHandlerClass('silverback_autosave')) { + $entity_type->setHandlerClass('silverback_autosave', AutosaveEntityFormHandler::class); + } + } +} + +/** + * Implements hook_form_alter(). + */ +function silverback_autosave_form_alter(&$form, FormStateInterface $form_state, $form_id) { + + $active_on = \Drupal::configFactory()->get('silverback_autosave.settings')->get('active_on'); + $form_object = $form_state->getFormObject(); + + if ($form_object instanceof ContentEntityFormInterface) { + if (!$active_on['content_entity_forms']) { + return; + } + + $entity = $form_object->getEntity(); + $entity_type_id = $entity->getEntityTypeId(); + $allowed_content_entity_types = \Drupal::configFactory() + ->get('silverback_autosave.settings') + ->get('allowed_content_entity_types'); + + // Autosave is enabled if either no restriction has been made or the current + // entity type is part of the restriction and as well either no restriction + // on bundle level has been made or the current bundle is part of that + // restriction. + if (!empty($allowed_content_entity_types)) { + if (!isset($allowed_content_entity_types[$entity_type_id]) || + (!empty($allowed_content_entity_types[$entity_type_id]['bundles']) && !isset($allowed_content_entity_types[$entity_type_id]['bundles'][$entity->bundle()]))) { + return; + } + } + } + elseif ($form_object instanceof EntityFormInterface) { + if (!$active_on['config_entity_forms']) { + return; + } + } + else { + return; + } + + // Allow autosave only for entity form routes, as forms might be included in + // blocks and other places and it is impossible to determine to which URL we + // have to post the autosave submit to. Also we don't support embedded forms + // as e.g. it might be surprising for the user getting autosave on the entity + // view, because e.g. a block is using an entity form. + $route = \Drupal::routeMatch()->getRouteObject(); + if ($route && ($route_defaults = $route->getDefaults()) && isset($route_defaults['_entity_form'])) { + [$entity_type_id, $form_op] = explode('.', $route_defaults['_entity_form']); + $entity = $form_object->getEntity(); + if (($entity->getEntityTypeId() != $entity_type_id) || ($form_object->getOperation() != $form_op)) { + return; + } + } + else { + return; + } + + $entity_type_manager = \Drupal::entityTypeManager(); + if ($entity_type_manager->hasHandler($entity->getEntityTypeId(), 'silverback_autosave')) { + $silverback_autosave_handler = $entity_type_manager->getHandler($entity->getEntityTypeId(), 'silverback_autosave'); + if ($silverback_autosave_handler instanceof AutosaveEntityFormHandlerInterface) { + $silverback_autosave_handler->formAlter($form, $form_state); + } + } +} + +/** + * Implements hook_entity_update(). + */ +function silverback_autosave_entity_update(EntityInterface $entity) { + static $conflict_enabled; + static $autosave_entity_form_storage; + + if (!isset($autosave_entity_form_storage)) { + /** @var \Drupal\silverback_autosave\Storage\AutosaveEntityFormStorageInterface $autosave_entity_form_storage */ + $autosave_entity_form_storage = \Drupal::service('silverback_autosave.entity_form_storage'); + } + + if (($conflict_enabled === FALSE) || (is_null($conflict_enabled) && !($conflict_enabled = \Drupal::moduleHandler()->moduleExists('conflict')))) { + // If conflict management is not available the autosaved entity states have + // to be removed when the entity is saved. + $autosave_entity_form_storage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id()); + } + else { + $entity_type = $entity->getEntityType(); + if ($entity_type->hasHandlerClass('silverback_autosave') && ($class = $entity_type->getHandlerClass('silverback_autosave'))) { + // If conflict is enabled and the entity is saved then delete only the + // current autosave session of the current user. + if ($autosave_session_id = $class::getAutosaveSessionID($entity)) { + $autosave_entity_form_storage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id(), $autosave_session_id); + } + } + } +} + +/** + * Implements hook_entity_update() for the user entity type. + * + * If the permissions of a user are changed, then we delete the autosave states + * belonging to that user. It is possible that new permissions the user is + * loosing the ability to access certain fields, but the autosave states contain + * data for them. + */ +function silverback_autosave_user_update(EntityInterface $user) { + /** @var \Drupal\user\UserInterface $user */ + $current_roles = $user->getRoles(); + $original_roles = $user->original->getRoles(); + sort($current_roles); + sort($original_roles); + + if ($current_roles !== $original_roles) { + /** @var \Drupal\silverback_autosave\Storage\AutosaveEntityFormStorageInterface $autosave_entity_form_storage */ + $autosave_entity_form_storage = \Drupal::service('silverback_autosave.entity_form_storage'); + $autosave_entity_form_storage->purgeAutosavedEntitiesStates(NULL, NULL, $user->id()); + } +} diff --git a/packages/drupal/silverback_autosave/silverback_autosave.routing.yml b/packages/drupal/silverback_autosave/silverback_autosave.routing.yml new file mode 100644 index 000000000..ad468d60a --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.routing.yml @@ -0,0 +1,7 @@ +silverback_autosave.admin_settings: + path: '/admin/config/content/silverback_autosave' + defaults: + _form: '\Drupal\silverback_autosave\Form\AutosaveFormSettingsForm' + _title: 'Silverback autosave settings' + requirements: + _permission: 'administer site configuration' diff --git a/packages/drupal/silverback_autosave/silverback_autosave.services.yml b/packages/drupal/silverback_autosave/silverback_autosave.services.yml new file mode 100644 index 000000000..8a25c019c --- /dev/null +++ b/packages/drupal/silverback_autosave/silverback_autosave.services.yml @@ -0,0 +1,52 @@ +services: + silverback_autosave.entity_form_storage: + class: \Drupal\silverback_autosave\Storage\AutosaveEntityFormDatabaseStorage + arguments: ['@database', '@serialization.phpserialize'] + + form_validator.silverback_autosave: + public: false + class: \Drupal\silverback_autosave\Form\AutosaveFormValidator + decorates: form_validator + arguments: + [ + '@form_validator.silverback_autosave.inner', + '@request_stack', + '@string_translation', + '@csrf_token', + '@logger.channel.form', + '@form_error_handler', + ] + form_builder.silverback_autosave: + public: false + class: \Drupal\silverback_autosave\Form\AutosaveFormBuilder + decorates: form_builder + # The decorated autosave form builder should be the first applied as it + # will not call further decorators, but directly override the core form + # builder by extending from it and just calling the parent methods. + decoration_priority: 10000 + arguments: + [ + '@form_builder.silverback_autosave.inner', + '@form_validator', + '@form_submitter', + '@form_cache', + '@module_handler', + '@event_dispatcher', + '@request_stack', + '@class_resolver', + '@element_info', + '@theme.manager', + '@?csrf_token', + '@silverback_autosave.entity_form_storage', + ] + form_error_handler.silverback_autosave: + public: false + class: Drupal\silverback_autosave\Form\AutosaveFormErrorHandler + decorates: form_error_handler + arguments: ['@form_error_handler.silverback_autosave.inner'] + + silverback_autosave.config_subscriber: + class: Drupal\silverback_autosave\EventSubscriber\ConfigSubscriber + tags: + - { name: event_subscriber } + arguments: ['@silverback_autosave.entity_form_storage'] diff --git a/packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php b/packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php new file mode 100644 index 000000000..7fad3b04f --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php @@ -0,0 +1,21 @@ + 'openAutosaveDisabledDialog'] + parent::render(); + } + +} diff --git a/packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php b/packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php new file mode 100644 index 000000000..ce2975c10 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php @@ -0,0 +1,22 @@ +autosaveEntityFormStorage = $autosave_entity_form_storage; + } + + /** + * Purges all autosave states in case a form-related config is saved. + * + * Deletes all autosaved states if an important property of a form related + * config is changed. We cannot know if some kind of an inline reference has + * been used and its display has been changed, which is why we purge the whole + * autosave storage. Otherwise a much more complex detection mechanism will + * be needed. We though try to prevent the purging in case a change on an + * property is made, which doesn't influence the form functionality - e.g. if + * the label of a field is changed then we don't have to purge the autosave + * states. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The configuration event. + */ + public function onConfigSave(ConfigCrudEvent $event) { + $saved_config = $event->getConfig(); + $name = $saved_config->getName(); + + // We try to prevent purging autosave states unnecessarily by defining + // properties, which are allowed to change and don't have an influence on + // the form functionality. + $allowed_changes = []; + $purge_autosave_states = TRUE; + if (strpos($name, 'field.field.') === 0) { + $allowed_changes = [ + 'dependencies', + 'label', + 'description', + 'required', + 'default_value', + 'default_value_callback', + ]; + } + elseif (strpos($name, 'field.storage.') === 0) { + $allowed_changes = [ + 'dependencies', + 'module', + 'indexes', + 'persist_with_no_fields', + 'custom_storage', + ]; + } + elseif (strpos($name, 'core.entity_form_display.') === 0) { + $allowed_changes = [ + 'hidden', + ]; + } + elseif (strpos($name, 'user.role.') === 0) { + $allowed_changes = [ + 'label', + 'weight', + ]; + } + else { + $purge_autosave_states = FALSE; + } + + if ($purge_autosave_states) { + $property_names = array_diff(array_keys($saved_config->getRawData()), $allowed_changes); + foreach ($property_names as $property_name) { + if ($event->isChanged($property_name)) { + $this->autosaveEntityFormStorage->purgeAutosavedEntitiesStates(); + break; + } + } + } + } + + /** + * Purges all autosave states in case a form-related config is deleted. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The configuration event. + */ + public function onConfigDelete(ConfigCrudEvent $event) { + $deleted_config = $event->getConfig(); + $name = $deleted_config->getName(); + + $purge_autosave_states = (strpos($name, 'field.field.') === 0) || + (strpos($name, 'field.storage.') === 0) || + (strpos($name, 'core.entity_form_display.') === 0) || + (strpos($name, 'user.role.') === 0); + + if ($purge_autosave_states) { + $this->autosaveEntityFormStorage->purgeAutosavedEntitiesStates(); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[ConfigEvents::SAVE][] = ['onConfigSave']; + $events[ConfigEvents::DELETE][] = ['onConfigDelete']; + return $events; + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php b/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php new file mode 100644 index 000000000..a51d17f7b --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php @@ -0,0 +1,72 @@ +getTriggeringElement(); + if (is_null($triggering_element)) { + $user_input = $form_state->getUserInput(); + $autosave = isset($user_input['_triggering_element_name']) && ($user_input['_triggering_element_name'] == AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME); + } + else { + $autosave = $triggering_element && !empty($triggering_element['#silverback_autosave']); + } + return $autosave; + } + + /** + * Checks if autosave restore has been triggered. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return bool + * TRUE if autosave restore has been triggered, FALSE otherwise. + */ + protected function isRestoreTriggered($form, FormStateInterface $form_state) { + $input = $form_state->getUserInput(); + // The restore submit is a non-ajax element and therefore its name will be + // contained in the user input as a key. + $triggered = isset($input[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME]); + return $triggered; + } + + /** + * Checks if autosave restore has been triggered. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return bool + * TRUE if autosave restore has been triggered, FALSE otherwise. + */ + protected function isRejectTriggered($form, FormStateInterface $form_state) { + $user_input = $form_state->getUserInput(); + // The reject submit is an ajax element and therefore its name will not be + // contained in the user input as a key, but will be contained as a value + // under the key "_triggering_element_name". + $triggered = isset($user_input['_triggering_element_name']) && ($user_input['_triggering_element_name'] == AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME); + return $triggered; + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php b/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php new file mode 100644 index 000000000..562f1b3a2 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php @@ -0,0 +1,327 @@ +entityTypeId = $entity_type->id(); + $this->entityType = $entity_type; + $this->entityStorage = $entity_type_manager->getStorage($entity_type->id()); + $this->currentUser = $current_user; + $this->autosaveEntityFormStorage = $autosave_entity_form_storage; + $this->time = $time; + $this->dateFormatter = $date_formatter; + $this->configFactory = $config_factory; + $this->keyValueExpirableFactory = $key_value_expirable_factory; + $this->conflictEnabled = $module_handler->moduleExists('conflict'); + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity_type.manager'), + $container->get('current_user'), + $container->get('silverback_autosave.entity_form_storage'), + $container->get('datetime.time'), + $container->get('date.formatter'), + $container->get('config.factory'), + $container->get('keyvalue.expirable'), + $container->get('module_handler') + ); + } + + /** + * Performs the needed alterations to the entity form. + * + * @param array $form + * The entity form to be altered to provide the autosave functionality. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function formAlter(array &$form, FormStateInterface $form_state) { + $this->traitFormAlter($form, $form_state); + // Flag the entity with the current autosave session ID. + $form['#entity_builders'][] = [static::class, 'entityFormEntityBuild']; + } + + /** + * Ajax callback for autosaving content entity forms. + */ + public function autosaveFormAjax($form, FormStateInterface $form_state) { + $response = $this->traitAutosaveFormAjax($form, $form_state); + $timestamp = $form_state->getTemporaryValue('silverback_autosave_last_autosave_timestamp'); + if ($timestamp == 'entity_saved_meanwhile') { + $input = $form_state->getUserInput(); + // Autosave has run already or this is the first run. + if (is_numeric($input['silverback_autosave_last_autosave_timestamp']) || empty($input['silverback_autosave_last_autosave_timestamp'])) { + // Alert the user about turning off autosave and deleting all the + // autosaved states. + $message = $this->configFactory->get('silverback_autosave.messages') + ->get('entity_saved_in_background_alert_message'); + + $options = [ + 'width' => 'auto', + 'closeOnEscape' => FALSE, + ]; + $response->addCommand(new OpenAutosaveDisabledDialog($this->t('Autosave has been disabled'), $message, $options)); + + // Reset the last autosaved timestamp on the page. + $response->addCommand(new InvokeCommand('input[name="silverback_autosave_last_autosave_timestamp"]', 'attr', ['value', (string) $this->t('Autosave is turned off.')])); + // Stop the autosave submissions. + $attachments = []; + $attachments['drupalSettings']['autosaveForm']['autosaveFormRunning'] = FALSE; + $response->addAttachments($attachments); + } + } + + return $response; + } + + /** + * Entity builder method. + * + * Flags the entity with the autosave form session ID. + * + * @param string $entity_type + * The type of the entity. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity whose form is being built. + */ + public static function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) { + if ($silverback_autosave_session_id = static::getAutosaveFormSessionID($form_state)) { + $entity->{static::AUTOSAVE_SESSION_ID} = $silverback_autosave_session_id; + } + } + + /** + * {@inheritdoc} + */ + public function isAutosaveSubmitValid(FormStateInterface $form_state) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + + // Check that the entity is still valid. + if ($entity instanceof EntityChangedInterface) { + $changed_time = $entity->getChangedTime(); + + $input = $form_state->getUserInput(); + $changed_form_value = $input['changed'] ?? NULL; + $entity->setChangedTime($changed_form_value ?: $changed_time); + + if (!$this->conflictEnabled && ($unchanged = $this->entityStorage->loadUnchanged($entity->id())) && ($unchanged->getChangedTimeAcrossTranslations() > $entity->getChangedTimeAcrossTranslations())) { + $form_state->setTemporaryValue('silverback_autosave_last_autosave_timestamp', 'entity_saved_meanwhile'); + return FALSE; + } + else { + $entity->setChangedTime($changed_time); + } + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getLastAutosavedFormState(FormStateInterface $form_state, $silverback_autosave_session_id, $uid) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + return $this->autosaveEntityFormStorage->getFormState($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid, $silverback_autosave_session_id); + } + + /** + * {@inheritdoc} + */ + public function storeState(FormStateInterface $form_state, $silverback_autosave_session_id, $autosave_timestamp, $uid) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + $this->autosaveEntityFormStorage->storeEntityAndFormState($form_id, $silverback_autosave_session_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid, $autosave_timestamp, $entity, $form_state); + } + + /** + * {@inheritdoc} + */ + public function getLastAutosavedTimestamp(FormStateInterface $form_state, $uid) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + return $entity->isNew() ? NULL : $this->autosaveEntityFormStorage->getLastAutosavedStateTimestamp($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid); + } + + /** + * {@inheritdoc} + */ + public function purgeCurrentAutosavedState(FormStateInterface $form_state, $uid) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + $this->autosaveEntityFormStorage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id(), $this->getAutosaveFormSessionID($form_state), $form_id, $entity->language()->getId(), $uid); + } + + /** + * {@inheritdoc} + */ + public function purgeAllAutosavedStates(FormStateInterface $form_state, $uid) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + $this->autosaveEntityFormStorage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id(), NULL, $form_id, $entity->language()->getId(), $uid); + } + + /** + * {@inheritdoc} + */ + public function isAutosaveEnabled(FormStateInterface $form_state) { + [$form_id, $entity] = $this->getFormIDandEntity($form_state); + $allowed = !$entity->isNew() && !$this->currentUser->isAnonymous(); + return $allowed; + } + + /** + * {@inheritdoc} + */ + public static function getAutosaveSessionID(EntityInterface $entity) { + if (isset($entity->{static::AUTOSAVE_SESSION_ID})) { + return $entity->{static::AUTOSAVE_SESSION_ID}; + } + else { + return NULL; + } + } + + /** + * Retrieves the form ID and the form entity object from the form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The first value is the form ID and the second the form entity object. + */ + protected function getFormIDandEntity(FormStateInterface $form_state) { + /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */ + $form_object = $form_state->getFormObject(); + $form_id = $form_object->getFormId(); + $entity = $form_object->getEntity(); + return [$form_id, $entity]; + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php b/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php new file mode 100644 index 000000000..73533bb0c --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php @@ -0,0 +1,25 @@ +isAutosaveEnabled($form_state)) { + return; + } + + $form['#attributes']['class'][] = 'autosave-form'; + $form['#attached']['library'][] = 'silverback_autosave/drupal.silverback_autosave'; + $form['#attached']['drupalSettings']['autosaveForm']['interval'] = $this->configFactory->get('silverback_autosave.settings')->get('interval'); + $form['#attached']['drupalSettings']['autosaveForm']['onlyOnFormChange'] = $this->configFactory->get('silverback_autosave.settings')->get('only_on_form_change'); + $form['#attached']['drupalSettings']['autosaveForm']['notification'] = $this->configFactory->get('silverback_autosave.settings')->get('notification'); + $input = $form_state->getUserInput(); + + // $show_restore_discard = !$form_state->isRebuilding() ?: !empty($input['autosave_restore_discard']); + $show_restore_discard = FALSE; + if ($show_restore_discard && !$form_state->get('silverback_autosave_state_timestamp') && !$form_state->get('silverback_autosave_rejected') && ($silverback_autosave_state_timestamp = $this->getLastAutosavedTimestamp($form_state, $this->currentUser->id()))) { + $form[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME] = [ + '#type' => 'submit', + '#name' => AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME, + '#value' => $this->t('Autosave restore'), + '#limit_validation_errors' => [], + // '#attributes' => ['class' => ['autosave-form-restore', 'visually-hidden']], + '#attributes' => ['class' => ['autosave-form-restore']], + '#submit' => [[$this, 'autosaveFormRestoreSubmit']], + '#silverback_autosave_state_timestamp' => $silverback_autosave_state_timestamp, + ]; + $form[AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME] = [ + '#type' => 'submit', + '#name' => 'silverback_autosave_reject', + '#value' => $this->t('Autosave reject'), + '#limit_validation_errors' => [], + '#attributes' => ['class' => ['autosave-form-reject', 'visually-hidden']], + '#submit' => [[$this, 'autosaveFormRejectSubmit']], + '#ajax' => [ + 'callback' => [$this, 'autosaveFormRejectAjax'], + ], + ]; + $form['autosave_restore_discard'] = [ + '#type' => 'hidden', + '#default_value' => 'autosave_restore_discard', + '#attributes' => ['class' => ['autosave-form-restore-discard']], + ]; + + // Add the message to be shown on the form. Our JS library will check if + // the message exist and only then offer the options for restore and + // reject, otherwise will start the auto save process. + $date = $this->dateFormatter->format($silverback_autosave_state_timestamp, 'custom', 'M d, Y H:i'); + $message = $this->t('A version of this page you were editing at @date was saved as a draft. Do you want to resume editing or discard it?', ['@date' => $date]); + $form['#attached']['drupalSettings']['autosaveForm']['message'] = (string) $message; + } + + $silverback_autosave_session_id = $this->getAutosaveFormSessionID($form_state); + if (!$silverback_autosave_session_id) { + $silverback_autosave_session_id = !empty($input['silverback_autosave_session_id']) ? $input['silverback_autosave_session_id'] : $form['#build_id']; + $this->setAutosaveFormSessionID($form_state, $silverback_autosave_session_id); + } + + $form['silverback_autosave_session_id'] = [ + '#type' => 'hidden', + '#value' => $silverback_autosave_session_id, + '#name' => 'silverback_autosave_session_id', + // Form processing and validation requires this value, so ensure the + // submitted form value appears literally, regardless of custom #tree + // and #parents being set elsewhere. + '#parents' => ['silverback_autosave_session_id'], + ]; + + $form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME] = [ + '#type' => 'submit', + '#name' => AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME, + '#value' => $this->t('Autosave save'), + '#attributes' => ['class' => ['autosave-form-save', 'visually-hidden']], + '#submit' => [[$this, 'autosaveFormSubmit']], + '#ajax' => [ + 'callback' => [$this, 'autosaveFormAjax'], + // Do not refocus to prevent losing focus of the element the user might + // be currently editing when the autosave submission is triggered. + 'disable-refocus' => TRUE, + 'progress' => FALSE, + ], + '#silverback_autosave' => TRUE, + // Retrieve the "silverback_autosave_session_id" also from the form state as on + // autosave restore the one from the restored state will be present in + // the form state storage and we want to continue using that session for + // the further autosave states after the restoration. + '#silverback_autosave_session_id' => $silverback_autosave_session_id, + ]; + + $form['silverback_autosave_last_autosave_timestamp'] = [ + '#type' => 'hidden', + '#name' => 'silverback_autosave_last_autosave_timestamp', + '#value' => $form_state->get('silverback_autosave_last_autosave_timestamp') ?: '', + ]; + } + + /** + * Form submission handler for restoring autosaved state. + */ + public function autosaveFormRestoreSubmit($form, FormStateInterface $form_state) { + $triggering_element = $form_state->getTriggeringElement(); + if (!empty($triggering_element['#silverback_autosave_state_timestamp'])) { + // Set the timestamp of the autosaved state which has to be used to + // restore the form on rebuild. + $form_state->set('silverback_autosave_state_timestamp', $triggering_element['#silverback_autosave_state_timestamp']); + $form_state->setRebuild(); + } + } + + /** + * Form submission handler for rejecting autosaved states. + */ + public function autosaveFormRejectSubmit($form, FormStateInterface $form_state) { + // As this processing might take some time we want to prevent that if the + // connection is terminated the user input will be lost. + ignore_user_abort(TRUE); + // Mark the form state with a flag indicating an action for autosave has + // been chosen. + $form_state->set('silverback_autosave_rejected', TRUE); + // Cache the form state in order for the flag to be kept. + $form_state->setCached(); + + // Purge the autosave states. + $this->purgeAllAutosavedStates($form_state, $this->currentUser->id()); + } + + /** + * Ajax callback for rejecting autosaved states. + */ + public function autosaveFormRejectAjax($form, FormStateInterface $form_state) { + return new AjaxResponse(); + } + + /** + * Form submission handler for autosaving forms. + */ + public function autosaveFormSubmit($form, FormStateInterface $form_state) { + // As this processing might take some time we want to prevent that if the + // connection is terminated the user input will be lost. + ignore_user_abort(TRUE); + if (!$this->isAutosaveSubmitValid($form_state)) { + $form_state->disableCache(); + return; + } + // Having an autosave form session id also ensures that after resuming + // editing the new autosaved entities will be saved to the same autosave + // session id. + $silverback_autosave_session_id = $this->getAutosaveFormSessionID($form_state); + $current_user_id = $this->currentUser->id(); + $autosaved_form_state = $this->getLastAutosavedFormState($form_state, $silverback_autosave_session_id, $current_user_id); + // If there is non-autosaved state for this session then we have to put the + // user input into a temporary store and on each autosave submit compare + // against it for changes and after the first change compare with the last + // autosaved state. + if (is_null($autosaved_form_state)) { + if ($initial_user_input = $this->keyValueExpirableFactory->get('silverback_autosave')->get($silverback_autosave_session_id)) { + $autosaved_form_state_input = $initial_user_input; + } + else { + // 6 hours cache life time for forms should be plenty, like the form + // cache. + $expire = 21600; + $this->keyValueExpirableFactory->get('silverback_autosave')->setWithExpire($silverback_autosave_session_id, $form_state->getUserInput(), $expire); + + // This is the first where we cache the user input initially and we are + // done. + $form_state->disableCache(); + return; + } + } + else { + $autosaved_form_state_input = $autosaved_form_state->getUserInput(); + } + + // Subsequent autosaving - compare the user input only. This should be + // sufficient to detect changes in the fields. + $form_state_input = $form_state->getUserInput(); + + $skip_from_comparison_keys = [ + 'form_build_id', + 'form_token', + 'ajax_page_state', + 'silverback_autosave_last_autosave_timestamp', + AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME, + AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME, + 'autosave_restore_discard', + ]; + foreach ($skip_from_comparison_keys as $skip_from_comparison_key) { + unset($autosaved_form_state_input[$skip_from_comparison_key]); + unset($form_state_input[$skip_from_comparison_key]); + } + + $store = $autosaved_form_state_input != $form_state_input; + + if ($store) { + $autosave_timestamp = $this->time->getRequestTime(); + $form_state->set('silverback_autosave_last_autosave_timestamp', $autosave_timestamp); + $form_state->setTemporaryValue('silverback_autosave_last_autosave_timestamp', $autosave_timestamp); + + $this->storeState($form_state, $silverback_autosave_session_id, $autosave_timestamp, $current_user_id); + $this->keyValueExpirableFactory->get('silverback_autosave')->delete($silverback_autosave_session_id); + } + + // We don't have to cache the form each time an autosave submission is + // triggered, especially when we've skipped the form validation. + $form_state->disableCache(); + } + + /** + * Ajax callback for autosaving forms. + */ + public function autosaveFormAjax($form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + + $timestamp = $form_state->getTemporaryValue('silverback_autosave_last_autosave_timestamp'); + if (is_numeric($timestamp)) { + $response->addCommand(new InvokeCommand('input[name="silverback_autosave_last_autosave_timestamp"]', 'attr', ['value', $timestamp])); + } + + return $response; + } + + /** + * Retrieves the autosave form session ID. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return string|null + * The autosave form session ID or NULL if none present yet. + */ + protected static function getAutosaveFormSessionID(FormStateInterface $form_state) { + return $form_state->get('silverback_autosave_session_id'); + } + + /** + * Sets the autosave form session ID into the form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param string $silverback_autosave_session_id + * The autosave form session ID. + */ + protected function setAutosaveFormSessionID(FormStateInterface $form_state, $silverback_autosave_session_id) { + $form_state->set('silverback_autosave_session_id', $silverback_autosave_session_id); + } + + /** + * Returns the HTTP method used by the request that is building the form. + * + * @return string + * Can be any valid HTTP method, such as GET, POST, HEAD, etc. + */ + protected function getRequestMethod() { + return \Drupal::requestStack()->getCurrentRequest()->getMethod(); + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php new file mode 100644 index 000000000..cc5896905 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php @@ -0,0 +1,254 @@ +formBuilder = $form_builder; + $this->autosaveEntityFormStorage = $autosave_entity_form_storage; + } + + /** + * + */ + public function buildForm($form_id, FormStateInterface &$form_state) { + $form = parent::buildForm($form_id, $form_state); + if ($form_state::hasAnyErrors()) { + // Under circumstances it might happen that the form is submitted but + // returned with validation errors and the form alter hooks are executed + // thus leading to the autosave form alter code being executed as well and + // putting the autosave resume/discard message to the form, which should + // not happen if the form is being returned to the browser with validation + // errors. In order to prevent this we have to add the resume/discard + // message and options only on HTTP GET requests or on POST requests if + // restore or reject submit operations have been performed or in a more + // complex case if the message has not been yet confirmed but other + // AJAX / POST requests are being triggered in the background. As we could + // not detect the last case we still put the form elements into the form, + // but on the client side we will not show the message if the form is + // returned with validation errors. + $form['#attached']['drupalSettings']['autosaveForm']['formHasErrors'] = TRUE; + + // Additionally unset the form elements and settings which might have been + // added, but aren't actually needed. + unset($form['#attached']['drupalSettings']['autosaveForm']['message']); + unset($form[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME]); + unset($form[AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME]); + unset($form['autosave_restore_discard']); + } + return $form; + } + + /** + * {@inheritdoc} + */ + public function processForm($form_id, &$form, FormStateInterface &$form_state) { + if ($this->isAutosaveTriggered($form_state)) { + // @todo should we add a condition, that the form state is already cached + // in order to stop fully processing the form? + $this->doBuildFormSkip = TRUE; + + // As we'll skip doBuildForm we have to take care of setting the + // triggering element. + $form_state->setTriggeringElement($form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME]); + + // Needed to execute the submit handler, as this will not be done if + // duBuildForm is not being executed. + $form_state->setSubmitHandlers($form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME]['#submit']); + $form_state->setProcessInput(); + $form_state->setSubmitted(); + } + + $response = parent::processForm($form_id, $form, $form_state); + $this->doBuildFormSkip = FALSE; + + return $response; + } + + /** + * {@inheritdoc} + */ + public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) { + return $this->doBuildFormSkip ? $element : parent::doBuildForm($form_id, $element, $form_state); + } + + /** + * {@inheritdoc} + */ + public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) { + $this->restoreAutosavedState($form_id, $form_state); + return parent::rebuildForm($form_id, $form_state, $old_form); + } + + /** + * Restores an autosaved form state. + * + * @param $form_id + * The form id. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function restoreAutosavedState($form_id, FormStateInterface $form_state) { + if (!$form_state->get('silverback_autosave_restored') && ($autosaved_timestamp = $form_state->get('silverback_autosave_state_timestamp'))) { + $form_object = $form_state->getFormObject(); + + // Restore entity form. + if ($form_object instanceof EntityFormInterface) { + $entity = $form_object->getEntity(); + $autosaved_state = $this->autosaveEntityFormStorage->getEntityAndFormState($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $this->currentUser()->id(), NULL, $autosaved_timestamp); + + if (is_null($autosaved_state)) { + // @todo Cover the case that the autosaved state has been purged + // meanwhile. + return; + } + + /** @var \Drupal\Core\Entity\EntityInterface $autosaved_entity */ + $autosaved_entity = $autosaved_state['entity']; + /** @var \Drupal\Core\Form\FormStateInterface $autosaved_form_state */ + $autosaved_form_state = $autosaved_state['form_state']; + + // Restore the form with the entity from the autosaved state. + $form_object->setEntity($autosaved_entity); + // Restore the user input. + $current_user_input = $form_state->getUserInput(); + $autosaved_user_input = $autosaved_form_state->getUserInput(); + // We have to rebuild the form and keep the generated form token + // instead of putting the one from the autosaved input, otherwise the + // form builder will set an form state error and, which is going to + // result into an exception, as setting form state errors after the + // validation phase is forbidden. + if (isset($current_user_input['form_token'])) { + $autosaved_user_input['form_token'] = $current_user_input['form_token']; + } + $form_state->setUserInput($autosaved_user_input); + // Recover the form state storage, which is needed to continue from the + // state at which the form was left. + $form_state->setStorage($autosaved_form_state->getStorage()); + + // Flag the form state as being restored from autosave. + $form_state->set('silverback_autosave_restored', TRUE); + } + elseif ($form_object instanceof FormInterface) { + // @todo add support for regular forms. + } + } + } + + /** + * {@inheritdoc} + */ + public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { + $prevent_hooks = FALSE; + + if ($this->isAutosaveTriggered($form_state)) { + // There is no need of generating a new form build id after triggering + // autosave. + $form['#build_id'] = $form_state->getUserInput()['form_build_id']; + + if ($form_state->isCached()) { + $prevent_hooks = TRUE; + } + } + + if ($prevent_hooks) { + // Prevent running hooks. + $module_handler = $this->moduleHandler; + $theme_manager = $this->themeManager; + $this->moduleHandler = new ModuleHandlerEmptyAlter(); + $this->themeManager = new ThemeManagerEmptyAlter(); + } + + parent::prepareForm($form_id, $form, $form_state); + + if ($prevent_hooks) { + $this->moduleHandler = $module_handler; + $this->themeManager = $theme_manager; + } + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php new file mode 100644 index 000000000..cd8689c24 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php @@ -0,0 +1,43 @@ +formErrorHandler = $form_error_handler; + } + + /** + * {@inheritdoc} + */ + public function handleFormErrors(array &$form, FormStateInterface $form_state) { + if ($this->isAutosaveTriggered($form_state)) { + return $this; + } + return $this->formErrorHandler->handleFormErrors($form, $form_state); + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php new file mode 100644 index 000000000..069e64495 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php @@ -0,0 +1,111 @@ +entityTypeManager = $entity_type_manager; + $this->entityTypeBundleInfo = $entity_type_bundle_info; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_type.manager'), + $container->get('entity_type.bundle.info') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'silverback_autosave_admin_settings'; + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return [ + 'silverback_autosave.settings', + ]; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $config = $this->config('silverback_autosave.settings'); + + $form['interval'] = [ + '#type' => 'number', + '#title' => $this->t('The interval to use for triggering autosave in milliseconds.'), + '#default_value' => $config->get('interval'), + ]; + $form['only_on_form_change'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Run only on form change.') . ' ' . $this->t('(Experimental)'), + '#description' => $this->t('If enabled an autosave submission will only occur if the form changed since the previous autosave submission.'), + '#default_value' => $config->get('only_on_form_change'), + ]; + + $form['active_on'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Active on:'), + '#tree' => TRUE, + ]; + $form['active_on']['content_entity_forms'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Content Entity Forms'), + '#default_value' => $config->get('active_on')['content_entity_forms'], + ]; + $form['active_on']['config_entity_forms'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Config Entity Forms'), + '#default_value' => $config->get('active_on')['config_entity_forms'], + ]; + + $form['notification'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Notification settings'), + '#description' => $this->t('Display a simple notification every time content is saved'), + '#tree' => TRUE, + ]; + $form['notification']['active'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Active'), + '#default_value' => $config->get('notification')['active'], + ]; + $form['notification']['message'] = [ + '#type' => 'textfield', + '#title' => $this->t('Message'), + '#default_value' => $config->get('notification')['message'], + '#states' => [ + 'visible' => [ + ':input[name="notification[active]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['notification']['delay'] = [ + '#type' => 'number', + '#title' => $this->t('The duration of the notification in milliseconds.'), + '#default_value' => $config->get('notification')['delay'], + '#states' => [ + 'visible' => [ + ':input[name="notification[active]"]' => ['checked' => TRUE], + ], + ], + ]; + + $allowed_content_entity_types = $config->get('allowed_content_entity_types'); + $form['allowed_content_entities'] = [ + '#type' => 'details', + '#open' => !empty($allowed_content_entity_types), + '#title' => $this->t('Allowed Content Entity Forms'), + '#description' => $this->t('In case no entity type is selected then autosave is enabled on all entity forms, otherwise it will be enabled only on the selected ones. Selecting only the entity type will enable all corresponding bundles and selecting only a subset of the bundles will enable autosave only for those bundles and will be disabled for the others.'), + '#tree' => TRUE, + '#states' => [ + 'visible' => [ + ':input[name="active_on[content_entity_forms]"]' => ['checked' => TRUE], + ], + ], + ]; + + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { + if ($entity_type instanceof ContentEntityTypeInterface) { + $entity_type_label = $entity_type->getLabel(); + $bundles_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); + $allowed_bundles = !empty($allowed_content_entity_types[$entity_type_id]['bundles']) ? $allowed_content_entity_types[$entity_type_id]['bundles'] : []; + $bundles = []; + foreach ($bundles_info as $key => $bundle) { + $bundles[$key] = $bundle['label']; + } + + $form['allowed_content_entities'][$entity_type_id]['active'] = [ + '#type' => 'checkbox', + '#title' => $entity_type_label, + '#default_value' => isset($allowed_content_entity_types[$entity_type_id]), + ]; + $form['allowed_content_entities'][$entity_type_id]['bundles'] = [ + '#type' => 'details', + '#open' => !empty($allowed_bundles), + '#title' => $entity_type_label . ' ' . $this->t('bundles'), + '#states' => [ + 'visible' => [ + ':input[name="allowed_content_entities[' . $entity_type_id . '][active]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['allowed_content_entities'][$entity_type_id]['bundles']['selection'] = [ + '#type' => 'checkboxes', + '#default_value' => $allowed_bundles, + '#options' => $bundles, + '#prefix' => '
', + '#suffix' => '
', + ]; + } + } + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Config\Config $config */ + $config = $this->config('silverback_autosave.settings'); + $allowed_content_entity_types = []; + foreach ($form_state->getValue('allowed_content_entities') as $entity_type_id => $data) { + if (!$data['active']) { + continue; + } + $allowed_bundles = array_filter($data['bundles']['selection']); + $allowed_content_entity_types[$entity_type_id]['bundles'] = $allowed_bundles; + } + $config->set('interval', $form_state->getValue('interval')) + ->set('only_on_form_change', $form_state->getValue('only_on_form_change')) + ->set('active_on', $form_state->getValue('active_on')) + ->set('notification', $form_state->getValue('notification')) + ->set('allowed_content_entity_types', $allowed_content_entity_types) + ->save(); + + parent::submitForm($form, $form_state); + } + +} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php new file mode 100644 index 000000000..ad0fd6e00 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php @@ -0,0 +1,80 @@ +formValidator = $form_validator; + parent::__construct($request_stack, $string_translation, $csrf_token, $logger, $form_error_handler); + } + + /** + * {@inheritdoc} + */ + public function validateForm($form_id, &$form, FormStateInterface &$form_state) { + $autosave_submission = $this->isAutosaveTriggered($form_state); + + if ($autosave_submission) { + // On subsequent autosaving we don't need to execute the form validation + // as we are not going to build the intermediate entity. However it might + // happen that between here and the autosave submission handler the + // autosaved state has been purged and therefore we have to check + // explicitly for that there instead of building the intermediate entity. + $form_state->setTemporaryValue('silverback_autosave_form_validation_skipped', TRUE); + } + else { + // We have to execute the validation in the case of autosave submission + // for the very first time as in this case we'll build the intermediate + // entity for comparison and some input values are being prepared in the + // validate functions. This is the case with e.g. autocomplete for entity + // references. + $this->formValidator->validateForm($form_id, $form, $form_state); + } + + // In order for the autosave submit callback to be executed we have to + // clear the errors caused from the validation, otherwise no submit + // callbacks will be executed. + if ($autosave_submission && $form_state::hasAnyErrors()) { + $form_state->clearErrors(); + } + } + +} diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php new file mode 100644 index 000000000..4e3fdbe74 --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -0,0 +1,310 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * {@inheritdoc} + */ + public function storeEntityAndFormState($form_id, $form_session_id, $entity_type_id, $entity_id, $langcode, $uid, $timestamp, EntityInterface $entity, FormStateInterface $form_state) { + $serialized_entity = $this->serializeEntity($entity, $form_state); + $serialized_form_state = $this->serializer->encode([ + 'storage' => $form_state->getStorage(), + 'input' => $form_state->getUserInput(), + ]); + + if (!$entity->isNew()) { + $this->connection->insert(static::AUTOSAVE_ENTITY_FORM_TABLE) + ->fields([ + 'form_id', + 'form_session_id', + 'entity_type_id', + 'entity_id', + 'langcode', + 'uid', + 'timestamp', + 'entity', + 'form_state', + ]) + ->values([ + $form_id, + $form_session_id, + $entity_type_id, + $entity_id, + $langcode, + $uid, + $timestamp, + $serialized_entity, + $serialized_form_state, + ]) + ->execute(); + } + } + + /** + * {@inheritdoc} + */ + public function getEntityAndFormState($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL, $autosaved_timestamp = NULL) { + $result = NULL; + $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') + ->fields('cefa', ['entity', 'form_state', 'timestamp']) + ->orderBy('timestamp', 'DESC') + ->condition('form_id', $form_id); + + if (isset($form_session_id)) { + $query->condition('form_session_id', $form_session_id); + } + + $query->condition('entity_type_id', $entity_type_id) + ->condition('entity_id', $entity_id) + ->condition('langcode', $langcode) + ->condition('uid', $uid); + + if (isset($autosaved_timestamp)) { + $query->condition('timestamp', $autosaved_timestamp); + } + + $data = $query->execute()->fetchAssoc(); + + if ($data) { + // Prepare the entity object. + $result['entity'] = $this->serializer->decode($data['entity']); + // Prepare the form state. + $form_state_data = $this->serializer->decode($data['form_state']); + $result['form_state'] = new FormState(); + $result['form_state']->setStorage($form_state_data['storage']); + $result['form_state']->setUserInput($form_state_data['input']); + $result['form_state']->set('silverback_autosave_state_timestamp', $data['timestamp']); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getEntity($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL, $autosaved_timestamp = NULL) { + $entity = NULL; + $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') + ->fields('cefa', ['entity']) + ->orderBy('timestamp', 'DESC') + ->condition('form_id', $form_id); + + if (isset($form_session_id)) { + $query->condition('form_session_id', $form_session_id); + } + + $query->condition('entity_type_id', $entity_type_id) + ->condition('entity_id', $entity_id) + ->condition('langcode', $langcode) + ->condition('uid', $uid); + + if (isset($autosaved_timestamp)) { + $query->condition('timestamp', $autosaved_timestamp); + } + + $data = $query->execute()->fetchAssoc(); + + if ($data) { + // Prepare the entity object. + $entity = $this->serializer->decode($data['entity']); + } + + return $entity; + } + + /** + * {@inheritdoc} + */ + public function getFormState($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL, $autosaved_timestamp = NULL) { + $form_state = NULL; + $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') + ->fields('cefa', ['form_state', 'timestamp']) + ->orderBy('timestamp', 'DESC') + ->condition('form_id', $form_id); + + if (isset($form_session_id)) { + // $query->condition('form_session_id', $form_session_id); + } + + $query->condition('entity_type_id', $entity_type_id) + ->condition('entity_id', $entity_id) + ->condition('langcode', $langcode) + ->condition('uid', $uid); + + if (isset($autosaved_timestamp)) { + $query->condition('timestamp', $autosaved_timestamp); + } + + $data = $query->execute()->fetchAssoc(); + + if ($data) { + // Prepare the form state. + $form_state_data = $this->serializer->decode($data['form_state']); + $form_state = new FormState(); + $form_state->setStorage($form_state_data['storage']); + $form_state->setUserInput($form_state_data['input']); + $form_state->set('silverback_autosave_state_timestamp', $data['timestamp']); + } + + return $form_state; + } + + /** + * {@inheritdoc} + */ + public function hasAutosavedStateForFormState(FormStateInterface $form_state, $uid) { + $result = FALSE; + if (($form_object = $form_state->getFormObject()) && ($form_object instanceof EntityFormInterface) && ($entity = $form_object->getEntity()) && !$entity->isNew()) { + $result = $this->hasAutosavedState($form_object->getFormId(), $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid, $form_state->get('silverback_autosave_session_id')); + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function hasAutosavedState($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL) { + $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') + ->condition('form_id', $form_id); + + if (isset($form_session_id)) { + $query->condition('form_session_id', $form_session_id); + } + + $query->condition('entity_type_id', $entity_type_id) + ->condition('entity_id', $entity_id) + ->condition('langcode', $langcode) + ->condition('uid', $uid); + + $count = $query->countQuery() + ->execute() + ->fetchField(); + return (bool) $count; + } + + /** + * {@inheritdoc} + */ + public function getLastAutosavedStateTimestamp($form_id, $entity_type_id, $entity_id, $langcode, $uid) { + $timestamp = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') + ->fields('cefa', ['timestamp']) + ->orderBy('timestamp', 'DESC') + ->condition('form_id', $form_id) + ->condition('entity_type_id', $entity_type_id) + ->condition('entity_id', $entity_id) + ->condition('langcode', $langcode) + ->condition('uid', $uid) + ->execute() + ->fetchField(); + + return is_bool($timestamp) ? NULL : $timestamp; + } + + /** + * {@inheritdoc} + */ + public function purgeAutosavedEntityState($entity_type_id, $entity_id, $form_session_id = NULL, $form_id = NULL, $langcode = NULL, $uid = NULL) { + $query = $this->connection->delete(static::AUTOSAVE_ENTITY_FORM_TABLE); + + if (isset($form_session_id)) { + $query->condition('form_session_id', $form_session_id); + } + + if (isset($form_id)) { + $query->condition('form_id', $form_id); + } + + $query->condition('entity_type_id', $entity_type_id) + ->condition('entity_id', $entity_id); + + if (isset($langcode)) { + $query->condition('langcode', $langcode); + } + + if (isset($uid)) { + $query->condition('uid', $uid); + } + + $query->execute(); + } + + /** + * {@inheritdoc} + */ + public function purgeAutosavedEntitiesStates($entity_type_id = NULL, $langcode = NULL, $uid = NULL) { + $query = $this->connection->delete(static::AUTOSAVE_ENTITY_FORM_TABLE); + if (isset($entity_type_id)) { + $query->condition('entity_type_id', $entity_type_id); + } + if (isset($langcode)) { + $query->condition('langcode', $langcode); + } + if (isset($uid)) { + $query->condition('uid', $uid); + } + $query->execute(); + } + + /** + * Returns the serialized entity object. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity object to serialize. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state to retrieve meta information about the serialization. + * + * @return string + * The serialized entity. + */ + protected function serializeEntity(EntityInterface $entity, FormStateInterface $form_state) { + if (($form_object = $form_state->getFormObject()) && ($form_object instanceof ContentEntityFormInterface) && method_exists($form_object, 'isEntityDeepSerializationRequired') && $form_object->isEntityDeepSerializationRequired()) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + if (method_exists($entity, 'setDeepSerialization')) { + $entity->setDeepSerialization(TRUE); + } + } + + return $this->serializer->encode($entity); + } + +} diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php new file mode 100644 index 000000000..9c14a724a --- /dev/null +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php @@ -0,0 +1,203 @@ +( // If the executor is not a function, pass null to SWR, // so it does not try to fetch. executor instanceof Function ? (arg) => executor(arg[1]) : null, - { - suspense: false, - }, + variables?.pathname?.indexOf('__preview') > 0 ? + { suspense: false, refreshInterval: 1000 } : { suspense: false }, ); return executor instanceof Function ? result From a5ef9bc90532c6c078e4001e823632b388f9b558 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Wed, 10 Apr 2024 16:24:09 +0300 Subject: [PATCH 002/221] feat(slb-146): disable copy root patches --- .lagoon/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile index 333bcd2cb..4669b403e 100644 --- a/.lagoon/Dockerfile +++ b/.lagoon/Dockerfile @@ -15,7 +15,7 @@ RUN npm install -g pnpm@8.6.12 && pnpm config set store-dir /tmp/cache/pnpm # Copy pnpm lockfile and install dependencies. COPY pnpm-lock.yaml .npmrc /app/ -COPY patches /app/patches +# COPY patches /app/patches RUN --mount=type=cache,target=/tmp/cache pnpm fetch && \ # There is a bug in pnpm: `pnpm fetch` creates _some_ node_modules folders # with _some_ packages. This can lead to an incomplete package installation. From eb673a928d8c6d05c98326efa49e252b9a684a08 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Wed, 10 Apr 2024 17:03:13 +0300 Subject: [PATCH 003/221] feat(slb-146): fix format in operations --- packages/ui/src/utils/operation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/utils/operation.ts b/packages/ui/src/utils/operation.ts index 11f56d6b2..9ec7d1742 100644 --- a/packages/ui/src/utils/operation.ts +++ b/packages/ui/src/utils/operation.ts @@ -18,8 +18,9 @@ export function useOperation( // If the executor is not a function, pass null to SWR, // so it does not try to fetch. executor instanceof Function ? (arg) => executor(arg[1]) : null, - variables?.pathname?.indexOf('__preview') > 0 ? - { suspense: false, refreshInterval: 1000 } : { suspense: false }, + variables?.pathname?.indexOf('__preview') > 0 + ? { suspense: false, refreshInterval: 1000 } + : { suspense: false }, ); return executor instanceof Function ? result From 6bb4e99524388d8739b1b60b0802aa3a45205efe Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Thu, 11 Apr 2024 13:01:52 +0300 Subject: [PATCH 004/221] feat(slb-146): restore aggregation, drush ^12.5 (devel issue) --- apps/cms/composer.json | 3 +- apps/cms/composer.lock | 354 +++++++++++++++++++- apps/cms/config/sync/system.performance.yml | 2 +- apps/cms/gatsby-node.mjs | 2 +- 4 files changed, 350 insertions(+), 11 deletions(-) diff --git a/apps/cms/composer.json b/apps/cms/composer.json index 6235b4cf4..95a18201c 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -73,7 +73,7 @@ "drupal/stage_file_proxy": "^2.0.2", "drupal/userprotect": "^1.2", "drupal/webform": "^6.1.5", - "drush/drush": "^12", + "drush/drush": "^12.5", "enyo/dropzone": "^5.7.1" }, "conflict": { @@ -155,6 +155,7 @@ }, "require-dev": { "drupal/core-dev": "^10", + "drupal/devel": "^5.2", "phpspec/prophecy-phpunit": "^2.0.2" } } diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index 07ca8aaa5..35ca468ce 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bee5aff7b7d7fdebb87c36db5dfc343a", + "content-hash": "7fdb7073ff9cc88dcbc78224e439e65f", "packages": [ { "name": "amazeeio/drupal_integrations", @@ -4481,23 +4481,23 @@ }, { "name": "drush/drush", - "version": "12.4.3", + "version": "12.5.1", "source": { "type": "git", "url": "https://github.com/drush-ops/drush.git", - "reference": "8245acede57ecc62a90aa0f19ff3b29e5f11f971" + "reference": "71fcea30a22e7336e17be18bb5945400b2c63fad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drush-ops/drush/zipball/8245acede57ecc62a90aa0f19ff3b29e5f11f971", - "reference": "8245acede57ecc62a90aa0f19ff3b29e5f11f971", + "url": "https://api.github.com/repos/drush-ops/drush/zipball/71fcea30a22e7336e17be18bb5945400b2c63fad", + "reference": "71fcea30a22e7336e17be18bb5945400b2c63fad", "shasum": "" }, "require": { "chi-teck/drupal-code-generator": "^3.0", "composer-runtime-api": "^2.2", "composer/semver": "^1.4 || ^3", - "consolidation/annotated-command": "^4.9.1", + "consolidation/annotated-command": "^4.9.2", "consolidation/config": "^2.1.2", "consolidation/filter-via-dot-access-data": "^2.0.2", "consolidation/output-formatters": "^4.3.2", @@ -4613,7 +4613,7 @@ "issues": "https://github.com/drush-ops/drush/issues", "security": "https://github.com/drush-ops/drush/security/advisories", "slack": "https://drupal.slack.com/messages/C62H9CWQM", - "source": "https://github.com/drush-ops/drush/tree/12.4.3" + "source": "https://github.com/drush-ops/drush/tree/12.5.1" }, "funding": [ { @@ -4621,7 +4621,7 @@ "type": "github" } ], - "time": "2023-11-16T22:57:24+00:00" + "time": "2024-03-20T15:03:27+00:00" }, { "name": "egulias/email-validator", @@ -11131,6 +11131,188 @@ }, "time": "2023-01-05T11:28:13+00:00" }, + { + "name": "doctrine/common", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/8b5e5650391f851ed58910b3e3d48a71062eeced", + "reference": "8b5e5650391f851ed58910b3e3d48a71062eeced", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0 || ^3.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0 || ^10.0", + "doctrine/collections": "^1", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^6.1", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.4.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2022-10-09T11:47:59+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2022-10-12T20:59:15+00:00" + }, { "name": "doctrine/instantiator", "version": "2.0.0", @@ -11201,6 +11383,104 @@ ], "time": "2022-12-30T00:23:10+00:00" }, + { + "name": "doctrine/persistence", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "477da35bd0255e032826f440b94b3e37f2d56f42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/477da35bd0255e032826f440b94b3e37f2d56f42", + "reference": "477da35bd0255e032826f440b94b3e37f2d56f42", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^7.2 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/coding-standard": "^11", + "doctrine/common": "^3.0", + "phpstan/phpstan": "1.9.4", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6.0", + "vimeo/psalm": "4.30.0 || 5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/3.3.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2024-03-12T14:54:36+00:00" + }, { "name": "drupal/coder", "version": "8.3.23", @@ -11308,6 +11588,64 @@ }, "time": "2023-12-12T22:01:45+00:00" }, + { + "name": "drupal/devel", + "version": "5.2.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/devel.git", + "reference": "5.2.0" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/devel-5.2.0.zip", + "reference": "5.2.0", + "shasum": "9b7baa45a3caea3908643ef22882205ec2ed0da1" + }, + "require": { + "doctrine/common": "^2.7 || ^3.4", + "drupal/core": ">=10.0 <12.0.0-stable", + "php": ">=8.1", + "symfony/var-dumper": "^4 || ^5 || ^6" + }, + "conflict": { + "kint-php/kint": "<3" + }, + "require-dev": { + "drush/drush": "^12.5.1" + }, + "suggest": { + "kint-php/kint": "Kint provides an informative display of arrays/objects. Useful for debugging and developing." + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "5.2.0", + "datestamp": "1711280160", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "moshe weitzman", + "homepage": "https://www.drupal.org/user/23" + } + ], + "description": "Various blocks, pages, and functions for developers.", + "homepage": "https://www.drupal.org/project/devel", + "support": { + "source": "https://gitlab.com/drupalspoons/devel", + "issues": "https://gitlab.com/drupalspoons/devel/-/issues", + "slack": "https://drupal.slack.com/archives/C012WAW1MH6" + } + }, { "name": "google/protobuf", "version": "v3.25.3", diff --git a/apps/cms/config/sync/system.performance.yml b/apps/cms/config/sync/system.performance.yml index bf0902d1c..d33d50d5d 100644 --- a/apps/cms/config/sync/system.performance.yml +++ b/apps/cms/config/sync/system.performance.yml @@ -12,5 +12,5 @@ fast_404: exclude_paths: '/\/(?:styles|imagecache)\//' html: '404 Not Found

Not Found

The requested URL "@path" was not found on this server.

' js: - preprocess: false + preprocess: true gzip: true diff --git a/apps/cms/gatsby-node.mjs b/apps/cms/gatsby-node.mjs index c5957f4c6..02e0027ff 100644 --- a/apps/cms/gatsby-node.mjs +++ b/apps/cms/gatsby-node.mjs @@ -48,7 +48,7 @@ export const createPages = async ({ actions }) => { // Additionally proxy themes and modules as they can have additional // non-aggregated assets. - ['themes', 'modules'].forEach((path) => { + ['themes', 'modules', 'core/assets'].forEach((path) => { actions.createRedirect({ fromPath: `/${path}/*`, toPath: `${process.env.GATSBY_DRUPAL_URL}/${path}/:splat`, From 6060a66d0458e8d81562be398f5fa2f981bd2874 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 11 Apr 2024 14:13:12 +0400 Subject: [PATCH 005/221] fix: add DRUPAL_EXTERNAL_URL for local Publisher So that packages/schema/src/image.ts can replace URLs properly. --- apps/website/publisher.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/publisher.config.ts b/apps/website/publisher.config.ts index abaf98f01..6c5d95797 100644 --- a/apps/website/publisher.config.ts +++ b/apps/website/publisher.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ // cannot report it. // Workaround: Do a double build on the first build. 'if test -d public; then echo "Single build" && pnpm build:gatsby; else echo "Double build" && pnpm build:gatsby && pnpm build:gatsby; fi' - : 'pnpm build:gatsby', + : 'DRUPAL_EXTERNAL_URL=http://127.0.0.1:8888 pnpm build:gatsby', outputTimeout: 1000 * 60 * 10, }, clean: 'pnpm clean', From f94d84be034dd431d8d631d883b35d1f5678f2b1 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 11 Apr 2024 14:13:51 +0400 Subject: [PATCH 006/221] fix: replace URLs in JSON properly --- packages/schema/src/image.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/schema/src/image.ts b/packages/schema/src/image.ts index 119af6332..f0190975c 100644 --- a/packages/schema/src/image.ts +++ b/packages/schema/src/image.ts @@ -26,10 +26,22 @@ export const imageProps: GraphQLFieldResolver = (source) => { // - If images are processed in Cloudinary, we don't need two CDN's (Netlify & Cloudinary) // - TODO: Once we have image processing in Drupal, it has to be handled here. if (process.env.NETLIFY_URL && process.env.DRUPAL_EXTERNAL_URL) { - return source.replaceAll( - process.env.NETLIFY_URL, - process.env.DRUPAL_EXTERNAL_URL, - ); + try { + const decoded = JSON.parse(source); + if (decoded && typeof decoded === 'object') { + for (const key in decoded) { + if (typeof decoded[key] === 'string') { + decoded[key] = decoded[key].replaceAll( + process.env.NETLIFY_URL, + process.env.DRUPAL_EXTERNAL_URL, + ); + } + } + return JSON.stringify(decoded); + } + } catch (error) { + return source; + } } return source; }; From 181822e58f366fbbf151d0dae12afe08155a1cd6 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 08:52:01 +0300 Subject: [PATCH 007/221] feat(slb-146): remove options for config forms --- .../install/silverback_autosave.settings.yml | 1 - .../schema/silverback_autosave.schema.yml | 3 - .../silverback_autosave.module | 7 - .../silverback_autosave.services.yml | 6 - .../src/EventSubscriber/ConfigSubscriber.php | 130 ------------------ .../src/Form/AutosaveFormSettingsForm.php | 6 +- 6 files changed, 1 insertion(+), 152 deletions(-) delete mode 100644 packages/drupal/silverback_autosave/src/EventSubscriber/ConfigSubscriber.php diff --git a/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml b/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml index c5a0c78aa..034332fef 100644 --- a/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml +++ b/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml @@ -2,7 +2,6 @@ interval: 10000 only_on_form_change: FALSE active_on: content_entity_forms: TRUE - config_entity_forms: FALSE notification: active: TRUE message: "Updating preview..." diff --git a/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml b/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml index 86bef3549..0fccb354c 100644 --- a/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml +++ b/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml @@ -17,9 +17,6 @@ silverback_autosave.settings: content_entity_forms: type: boolean label: 'Content entity forms' - config_entity_forms: - type: boolean - label: 'Config entity forms' notification: type: mapping label: 'Notification' diff --git a/packages/drupal/silverback_autosave/silverback_autosave.module b/packages/drupal/silverback_autosave/silverback_autosave.module index 2f3dabfdb..1f1afe85b 100644 --- a/packages/drupal/silverback_autosave/silverback_autosave.module +++ b/packages/drupal/silverback_autosave/silverback_autosave.module @@ -6,7 +6,6 @@ */ use Drupal\Core\Entity\ContentEntityFormInterface; -use Drupal\Core\Entity\EntityFormInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\silverback_autosave\Form\AutosaveEntityFormHandler; @@ -34,7 +33,6 @@ function silverback_autosave_entity_type_alter(array &$entity_types) { * Implements hook_form_alter(). */ function silverback_autosave_form_alter(&$form, FormStateInterface $form_state, $form_id) { - $active_on = \Drupal::configFactory()->get('silverback_autosave.settings')->get('active_on'); $form_object = $form_state->getFormObject(); @@ -60,11 +58,6 @@ function silverback_autosave_form_alter(&$form, FormStateInterface $form_state, } } } - elseif ($form_object instanceof EntityFormInterface) { - if (!$active_on['config_entity_forms']) { - return; - } - } else { return; } diff --git a/packages/drupal/silverback_autosave/silverback_autosave.services.yml b/packages/drupal/silverback_autosave/silverback_autosave.services.yml index 8a25c019c..b8b45034d 100644 --- a/packages/drupal/silverback_autosave/silverback_autosave.services.yml +++ b/packages/drupal/silverback_autosave/silverback_autosave.services.yml @@ -44,9 +44,3 @@ services: class: Drupal\silverback_autosave\Form\AutosaveFormErrorHandler decorates: form_error_handler arguments: ['@form_error_handler.silverback_autosave.inner'] - - silverback_autosave.config_subscriber: - class: Drupal\silverback_autosave\EventSubscriber\ConfigSubscriber - tags: - - { name: event_subscriber } - arguments: ['@silverback_autosave.entity_form_storage'] diff --git a/packages/drupal/silverback_autosave/src/EventSubscriber/ConfigSubscriber.php b/packages/drupal/silverback_autosave/src/EventSubscriber/ConfigSubscriber.php deleted file mode 100644 index dd7affb82..000000000 --- a/packages/drupal/silverback_autosave/src/EventSubscriber/ConfigSubscriber.php +++ /dev/null @@ -1,130 +0,0 @@ -autosaveEntityFormStorage = $autosave_entity_form_storage; - } - - /** - * Purges all autosave states in case a form-related config is saved. - * - * Deletes all autosaved states if an important property of a form related - * config is changed. We cannot know if some kind of an inline reference has - * been used and its display has been changed, which is why we purge the whole - * autosave storage. Otherwise a much more complex detection mechanism will - * be needed. We though try to prevent the purging in case a change on an - * property is made, which doesn't influence the form functionality - e.g. if - * the label of a field is changed then we don't have to purge the autosave - * states. - * - * @param \Drupal\Core\Config\ConfigCrudEvent $event - * The configuration event. - */ - public function onConfigSave(ConfigCrudEvent $event) { - $saved_config = $event->getConfig(); - $name = $saved_config->getName(); - - // We try to prevent purging autosave states unnecessarily by defining - // properties, which are allowed to change and don't have an influence on - // the form functionality. - $allowed_changes = []; - $purge_autosave_states = TRUE; - if (strpos($name, 'field.field.') === 0) { - $allowed_changes = [ - 'dependencies', - 'label', - 'description', - 'required', - 'default_value', - 'default_value_callback', - ]; - } - elseif (strpos($name, 'field.storage.') === 0) { - $allowed_changes = [ - 'dependencies', - 'module', - 'indexes', - 'persist_with_no_fields', - 'custom_storage', - ]; - } - elseif (strpos($name, 'core.entity_form_display.') === 0) { - $allowed_changes = [ - 'hidden', - ]; - } - elseif (strpos($name, 'user.role.') === 0) { - $allowed_changes = [ - 'label', - 'weight', - ]; - } - else { - $purge_autosave_states = FALSE; - } - - if ($purge_autosave_states) { - $property_names = array_diff(array_keys($saved_config->getRawData()), $allowed_changes); - foreach ($property_names as $property_name) { - if ($event->isChanged($property_name)) { - $this->autosaveEntityFormStorage->purgeAutosavedEntitiesStates(); - break; - } - } - } - } - - /** - * Purges all autosave states in case a form-related config is deleted. - * - * @param \Drupal\Core\Config\ConfigCrudEvent $event - * The configuration event. - */ - public function onConfigDelete(ConfigCrudEvent $event) { - $deleted_config = $event->getConfig(); - $name = $deleted_config->getName(); - - $purge_autosave_states = (strpos($name, 'field.field.') === 0) || - (strpos($name, 'field.storage.') === 0) || - (strpos($name, 'core.entity_form_display.') === 0) || - (strpos($name, 'user.role.') === 0); - - if ($purge_autosave_states) { - $this->autosaveEntityFormStorage->purgeAutosavedEntitiesStates(); - } - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() { - $events[ConfigEvents::SAVE][] = ['onConfigSave']; - $events[ConfigEvents::DELETE][] = ['onConfigDelete']; - return $events; - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormSettingsForm.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormSettingsForm.php index 2eb3bde81..4f7f5e12b 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormSettingsForm.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormSettingsForm.php @@ -80,6 +80,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['only_on_form_change'] = [ '#type' => 'checkbox', + '#access' => FALSE, '#title' => $this->t('Run only on form change.') . ' ' . $this->t('(Experimental)'), '#description' => $this->t('If enabled an autosave submission will only occur if the form changed since the previous autosave submission.'), '#default_value' => $config->get('only_on_form_change'), @@ -95,11 +96,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Content Entity Forms'), '#default_value' => $config->get('active_on')['content_entity_forms'], ]; - $form['active_on']['config_entity_forms'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Config Entity Forms'), - '#default_value' => $config->get('active_on')['config_entity_forms'], - ]; $form['notification'] = [ '#type' => 'fieldset', From 246d84e13bc1cd2babb647066ad0a901abeaa253 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 08:57:37 +0300 Subject: [PATCH 008/221] feat(slb-146): remove unused storage interface, constant definition to uppercase --- .../silverback_autosave.install | 4 +- .../AutosaveEntityFormStorageInterface.php | 2 +- .../Storage/AutosaveFormBackendInterface.php | 83 ------------------- 3 files changed, 3 insertions(+), 86 deletions(-) delete mode 100644 packages/drupal/silverback_autosave/src/Storage/AutosaveFormBackendInterface.php diff --git a/packages/drupal/silverback_autosave/silverback_autosave.install b/packages/drupal/silverback_autosave/silverback_autosave.install index 79270393c..6cde0cbdf 100644 --- a/packages/drupal/silverback_autosave/silverback_autosave.install +++ b/packages/drupal/silverback_autosave/silverback_autosave.install @@ -21,7 +21,7 @@ function silverback_autosave_schema() { 'fields' => [ 'form_id' => [ 'type' => 'varchar_ascii', - 'length' => AutosaveEntityFormStorageInterface::silverback_autosave_FORM_ID_LENGTH, + 'length' => AutosaveEntityFormStorageInterface::SILVERBACK_AUTOSAVE_FORM_ID_LENGTH, 'not null' => TRUE, ], // We need the form session id as it is possible that the user opens the @@ -31,7 +31,7 @@ function silverback_autosave_schema() { // case. 'form_session_id' => [ 'type' => 'varchar_ascii', - 'length' => AutosaveEntityFormStorageInterface::silverback_autosave_FORM_ID_LENGTH, + 'length' => AutosaveEntityFormStorageInterface::SILVERBACK_AUTOSAVE_FORM_ID_LENGTH, 'not null' => TRUE, ], 'entity_type_id' => [ diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php index 9c14a724a..36cf6b0f7 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php @@ -12,7 +12,7 @@ interface AutosaveEntityFormStorageInterface { const AUTOSAVE_ENTITY_FORM_TABLE = 'silverback_autosave_entity_form'; - const silverback_autosave_FORM_ID_LENGTH = 512; + const SILVERBACK_AUTOSAVE_FORM_ID_LENGTH = 512; /** * Stores the entity together with the form state. diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveFormBackendInterface.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveFormBackendInterface.php deleted file mode 100644 index 167df5831..000000000 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveFormBackendInterface.php +++ /dev/null @@ -1,83 +0,0 @@ - Date: Fri, 12 Apr 2024 09:06:42 +0300 Subject: [PATCH 009/221] feat(slb-146): add short readme --- packages/drupal/silverback_autosave/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/drupal/silverback_autosave/README.md diff --git a/packages/drupal/silverback_autosave/README.md b/packages/drupal/silverback_autosave/README.md new file mode 100644 index 000000000..33a8d4f82 --- /dev/null +++ b/packages/drupal/silverback_autosave/README.md @@ -0,0 +1,5 @@ +## Silverback autosave + +This module provides an autosave functionality on Guteneberg enabled nodes. + +On node edit, the content under edit is automatically saved for use in real time preview. From cdeb628b884516857ccf882dabb0cb69180d537b Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 09:19:04 +0300 Subject: [PATCH 010/221] feat(slb-146): remove obsolete restore/reject functionality --- .../js/silverback_autosave.js | 66 +------------------ .../src/Form/AutosaveButtonClickedTrait.php | 39 ----------- .../src/Form/AutosaveFormAlterTrait.php | 41 ------------ .../src/Form/AutosaveFormBuilder.php | 3 - .../src/Form/AutosaveFormInterface.php | 2 - 5 files changed, 1 insertion(+), 150 deletions(-) diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js index be91b640e..3c2777f02 100644 --- a/packages/drupal/silverback_autosave/js/silverback_autosave.js +++ b/packages/drupal/silverback_autosave/js/silverback_autosave.js @@ -124,71 +124,7 @@ .appendTo('body') .append(Drupal.autosaveForm.notification.message); - // Show the resume/discard confirmation message only if the form does - // not contain any errors, otherwise continue with normal autosave - // submissions. This is for the use case where a form is submitted but - // returned to the user with validation errors in which case we should - // not show the resume/discard message but continue the autosave - // submissions. - if (Drupal.autosaveForm.message && !Drupal.autosaveForm.formHasErrors) { - var dialogOptions = { - buttons: { - button_confirm: { - text: Drupal.t('Resume editing'), - class: 'autosave-form-resume-button', - click: function () { - // Non ajax buttons are bound to click. - // autosave-form-restore - $('.' + Drupal.autosaveForm.autosave_restore_class).trigger( - 'click', - ); - }, - }, - button_reject: { - text: Drupal.t('Discard'), - class: 'autosave-form-reject-button', - click: function () { - triggerAjaxSubmitWithoutProgressIndication( - Drupal.autosaveForm.autosave_reject_class, - true, - ); - $(this).dialog('close'); - }, - primary: true, - }, - }, - close: function (event, ui) { - $(this).remove(); - $(context) - .find('.' + Drupal.autosaveForm.autosave_restore_class) - .remove(); - $(context) - .find('.' + Drupal.autosaveForm.autosave_reject_class) - .remove(); - $(context).find('.autosave-form-restore-discard').remove(); - autosavePeriodic(); - }, - }; - - $.extend( - true, - dialogOptions, - Drupal.autosaveForm.defaultDialogOptions, - Drupal.autosaveForm.dialog_options, - ); - - $('
') - .appendTo('body') - .html('
' + Drupal.autosaveForm.message + '
') - .dialog(dialogOptions); - - // Temp - const btn = $('.autosave-form-restore'); - $('.ui-dialog-buttonset').append(btn[0]); - // .... - } else { - autosavePeriodic(); - } + autosavePeriodic(); } /** diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php b/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php index a51d17f7b..6cd709b10 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php @@ -30,43 +30,4 @@ protected function isAutosaveTriggered(FormStateInterface $form_state) { return $autosave; } - /** - * Checks if autosave restore has been triggered. - * - * @param array $form - * The form array. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return bool - * TRUE if autosave restore has been triggered, FALSE otherwise. - */ - protected function isRestoreTriggered($form, FormStateInterface $form_state) { - $input = $form_state->getUserInput(); - // The restore submit is a non-ajax element and therefore its name will be - // contained in the user input as a key. - $triggered = isset($input[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME]); - return $triggered; - } - - /** - * Checks if autosave restore has been triggered. - * - * @param array $form - * The form array. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return bool - * TRUE if autosave restore has been triggered, FALSE otherwise. - */ - protected function isRejectTriggered($form, FormStateInterface $form_state) { - $user_input = $form_state->getUserInput(); - // The reject submit is an ajax element and therefore its name will not be - // contained in the user input as a key, but will be contained as a value - // under the key "_triggering_element_name". - $triggered = isset($user_input['_triggering_element_name']) && ($user_input['_triggering_element_name'] == AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME); - return $triggered; - } - } diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php index 38df546e0..bd571d276 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php @@ -35,44 +35,6 @@ public function formAlter(array &$form, FormStateInterface $form_state) { $form['#attached']['drupalSettings']['autosaveForm']['notification'] = $this->configFactory->get('silverback_autosave.settings')->get('notification'); $input = $form_state->getUserInput(); - // $show_restore_discard = !$form_state->isRebuilding() ?: !empty($input['autosave_restore_discard']); - $show_restore_discard = FALSE; - if ($show_restore_discard && !$form_state->get('silverback_autosave_state_timestamp') && !$form_state->get('silverback_autosave_rejected') && ($silverback_autosave_state_timestamp = $this->getLastAutosavedTimestamp($form_state, $this->currentUser->id()))) { - $form[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME] = [ - '#type' => 'submit', - '#name' => AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME, - '#value' => $this->t('Autosave restore'), - '#limit_validation_errors' => [], - // '#attributes' => ['class' => ['autosave-form-restore', 'visually-hidden']], - '#attributes' => ['class' => ['autosave-form-restore']], - '#submit' => [[$this, 'autosaveFormRestoreSubmit']], - '#silverback_autosave_state_timestamp' => $silverback_autosave_state_timestamp, - ]; - $form[AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME] = [ - '#type' => 'submit', - '#name' => 'silverback_autosave_reject', - '#value' => $this->t('Autosave reject'), - '#limit_validation_errors' => [], - '#attributes' => ['class' => ['autosave-form-reject', 'visually-hidden']], - '#submit' => [[$this, 'autosaveFormRejectSubmit']], - '#ajax' => [ - 'callback' => [$this, 'autosaveFormRejectAjax'], - ], - ]; - $form['autosave_restore_discard'] = [ - '#type' => 'hidden', - '#default_value' => 'autosave_restore_discard', - '#attributes' => ['class' => ['autosave-form-restore-discard']], - ]; - - // Add the message to be shown on the form. Our JS library will check if - // the message exist and only then offer the options for restore and - // reject, otherwise will start the auto save process. - $date = $this->dateFormatter->format($silverback_autosave_state_timestamp, 'custom', 'M d, Y H:i'); - $message = $this->t('A version of this page you were editing at @date was saved as a draft. Do you want to resume editing or discard it?', ['@date' => $date]); - $form['#attached']['drupalSettings']['autosaveForm']['message'] = (string) $message; - } - $silverback_autosave_session_id = $this->getAutosaveFormSessionID($form_state); if (!$silverback_autosave_session_id) { $silverback_autosave_session_id = !empty($input['silverback_autosave_session_id']) ? $input['silverback_autosave_session_id'] : $form['#build_id']; @@ -204,9 +166,6 @@ public function autosaveFormSubmit($form, FormStateInterface $form_state) { 'form_token', 'ajax_page_state', 'silverback_autosave_last_autosave_timestamp', - AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME, - AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME, - 'autosave_restore_discard', ]; foreach ($skip_from_comparison_keys as $skip_from_comparison_key) { unset($autosaved_form_state_input[$skip_from_comparison_key]); diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php index cc5896905..3c8bd0b7f 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php @@ -116,9 +116,6 @@ public function buildForm($form_id, FormStateInterface &$form_state) { // Additionally unset the form elements and settings which might have been // added, but aren't actually needed. unset($form['#attached']['drupalSettings']['autosaveForm']['message']); - unset($form[AutosaveFormInterface::AUTOSAVE_RESTORE_ELEMENT_NAME]); - unset($form[AutosaveFormInterface::AUTOSAVE_REJECT_ELEMENT_NAME]); - unset($form['autosave_restore_discard']); } return $form; } diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php index 069e64495..d08030316 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php @@ -10,8 +10,6 @@ interface AutosaveFormInterface { const AUTOSAVE_ELEMENT_NAME = 'silverback_autosave_save'; - const AUTOSAVE_RESTORE_ELEMENT_NAME = 'silverback_autosave_restore'; - const AUTOSAVE_REJECT_ELEMENT_NAME = 'silverback_autosave_reject'; /** * Performs the needed alterations to the form. From 44fe208d9eaca1258991f28760514ef2f60ca132 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 10:34:25 +0300 Subject: [PATCH 011/221] feat(slb-146): purge previous entities and keep only one each time --- packages/drupal/silverback_autosave/README.md | 2 + .../js/silverback_autosave.js | 6 +-- .../src/Form/AutosaveFormAlterTrait.php | 41 ++----------------- .../AutosaveEntityFormDatabaseStorage.php | 9 +++- 4 files changed, 15 insertions(+), 43 deletions(-) diff --git a/packages/drupal/silverback_autosave/README.md b/packages/drupal/silverback_autosave/README.md index 33a8d4f82..625f34115 100644 --- a/packages/drupal/silverback_autosave/README.md +++ b/packages/drupal/silverback_autosave/README.md @@ -3,3 +3,5 @@ This module provides an autosave functionality on Guteneberg enabled nodes. On node edit, the content under edit is automatically saved for use in real time preview. + +The module is based on the [https://www.drupal.org/project/autosave_form](https://www.drupal.org/project/autosave_form). diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js index 3c2777f02..8d421518d 100644 --- a/packages/drupal/silverback_autosave/js/silverback_autosave.js +++ b/packages/drupal/silverback_autosave/js/silverback_autosave.js @@ -20,8 +20,6 @@ message: '', dialog_options: [], autosave_submit_class: 'autosave-form-save', - autosave_restore_class: 'autosave-form-restore', - autosave_reject_class: 'autosave-form-reject', notification: { active: true, message: Drupal.t('Updating preview...'), @@ -188,8 +186,8 @@ return; } - // If there are gutenberg fields, we need to make sure the textarea value - // is updated before autosaving. + // If there are gutenberg fields, we need to make sure + // the textarea value is updated before autosaving. const $gutenbergFields = $('.field--gutenberg textarea').first(); if ($gutenbergFields.length) { const { data } = window.wp; diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php index bd571d276..6466d4187 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php @@ -37,7 +37,9 @@ public function formAlter(array &$form, FormStateInterface $form_state) { $silverback_autosave_session_id = $this->getAutosaveFormSessionID($form_state); if (!$silverback_autosave_session_id) { - $silverback_autosave_session_id = !empty($input['silverback_autosave_session_id']) ? $input['silverback_autosave_session_id'] : $form['#build_id']; + $silverback_autosave_session_id = !empty($input['silverback_autosave_session_id']) ? + $input['silverback_autosave_session_id'] : + $form['#build_id']; $this->setAutosaveFormSessionID($form_state, $silverback_autosave_session_id); } @@ -79,43 +81,6 @@ public function formAlter(array &$form, FormStateInterface $form_state) { ]; } - /** - * Form submission handler for restoring autosaved state. - */ - public function autosaveFormRestoreSubmit($form, FormStateInterface $form_state) { - $triggering_element = $form_state->getTriggeringElement(); - if (!empty($triggering_element['#silverback_autosave_state_timestamp'])) { - // Set the timestamp of the autosaved state which has to be used to - // restore the form on rebuild. - $form_state->set('silverback_autosave_state_timestamp', $triggering_element['#silverback_autosave_state_timestamp']); - $form_state->setRebuild(); - } - } - - /** - * Form submission handler for rejecting autosaved states. - */ - public function autosaveFormRejectSubmit($form, FormStateInterface $form_state) { - // As this processing might take some time we want to prevent that if the - // connection is terminated the user input will be lost. - ignore_user_abort(TRUE); - // Mark the form state with a flag indicating an action for autosave has - // been chosen. - $form_state->set('silverback_autosave_rejected', TRUE); - // Cache the form state in order for the flag to be kept. - $form_state->setCached(); - - // Purge the autosave states. - $this->purgeAllAutosavedStates($form_state, $this->currentUser->id()); - } - - /** - * Ajax callback for rejecting autosaved states. - */ - public function autosaveFormRejectAjax($form, FormStateInterface $form_state) { - return new AjaxResponse(); - } - /** * Form submission handler for autosaving forms. */ diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index 4e3fdbe74..574d0934e 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -53,6 +53,13 @@ public function storeEntityAndFormState($form_id, $form_session_id, $entity_type ]); if (!$entity->isNew()) { + + // Purge previous stored states for the entity, + // e.g. when user is navigating away from the page. + // Also, this approach ensures that there will be always + // a stored form state in the db, in case we need to restore. + $this->purgeAutosavedEntityState($entity_type_id, $entity_id, NULL, $form_id, $langcode, $uid); + // Now, enter the new values. $this->connection->insert(static::AUTOSAVE_ENTITY_FORM_TABLE) ->fields([ 'form_id', @@ -163,7 +170,7 @@ public function getFormState($form_id, $entity_type_id, $entity_id, $langcode, $ ->condition('form_id', $form_id); if (isset($form_session_id)) { - // $query->condition('form_session_id', $form_session_id); + $query->condition('form_session_id', $form_session_id); } $query->condition('entity_type_id', $entity_type_id) From c8c8276fa70ef68ff4a9cdd2932b257e4dedc7d0 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 11:21:34 +0300 Subject: [PATCH 012/221] feat(slb-146): revert purge of previous states (build error) --- .../src/Storage/AutosaveEntityFormDatabaseStorage.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index 574d0934e..21dae10ad 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -58,7 +58,7 @@ public function storeEntityAndFormState($form_id, $form_session_id, $entity_type // e.g. when user is navigating away from the page. // Also, this approach ensures that there will be always // a stored form state in the db, in case we need to restore. - $this->purgeAutosavedEntityState($entity_type_id, $entity_id, NULL, $form_id, $langcode, $uid); + // $this->purgeAutosavedEntityState($entity_type_id, $entity_id, NULL, $form_id, $langcode, $uid); // Now, enter the new values. $this->connection->insert(static::AUTOSAVE_ENTITY_FORM_TABLE) ->fields([ @@ -169,9 +169,10 @@ public function getFormState($form_id, $entity_type_id, $entity_id, $langcode, $ ->orderBy('timestamp', 'DESC') ->condition('form_id', $form_id); - if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } + // Use this for future restore state. + /* if (isset($form_session_id)) { + $query->condition('form_session_id', $form_session_id); + } */ $query->condition('entity_type_id', $entity_type_id) ->condition('entity_id', $entity_id) From 53b3a50c0eaa44330adeccc161d7aa8677164450 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 12:52:21 +0300 Subject: [PATCH 013/221] feat(slb-146): workaround: purghe stored states on beforeupload --- .../js/silverback_autosave.js | 8 ++++- .../src/Form/AutosaveFormAlterTrait.php | 30 +++++++++++++++++++ .../AutosaveEntityFormDatabaseStorage.php | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js index 8d421518d..e9c9438b3 100644 --- a/packages/drupal/silverback_autosave/js/silverback_autosave.js +++ b/packages/drupal/silverback_autosave/js/silverback_autosave.js @@ -32,7 +32,13 @@ * Add a variable which determines if the window is being unloaded. */ Drupal.autosaveForm.beforeUnloadCalled = false; - $(window).on('beforeunload pagehide', function () { + + $(window).on('pagehide', function () { + Drupal.autosaveForm.beforeUnloadCalled = true; + }); + + $(window).on('beforeunload', function () { + $('#purge-button').trigger('click'); Drupal.autosaveForm.beforeUnloadCalled = true; }); diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php index 6466d4187..90399ad35 100644 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php +++ b/packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php @@ -74,6 +74,20 @@ public function formAlter(array &$form, FormStateInterface $form_state) { '#silverback_autosave_session_id' => $silverback_autosave_session_id, ]; + $form['purge'] = [ + '#type' => 'submit', + '#id' => 'purge-button', + '#name' => 'autosave_form_purge', + '#value' => $this->t('Autosave purge'), + '#limit_validation_errors' => [], + '#attributes' => ['class' => ['autosave-form-purge', 'visually-hidden']], + '#submit' => [[$this, 'autosaveFormPurgeSubmit']], + '#ajax' => [ + 'callback' => [$this, 'autosaveFormPurgeAjax'], + 'event' => 'click', + ], + ]; + $form['silverback_autosave_last_autosave_timestamp'] = [ '#type' => 'hidden', '#name' => 'silverback_autosave_last_autosave_timestamp', @@ -81,6 +95,22 @@ public function formAlter(array &$form, FormStateInterface $form_state) { ]; } + /** + * Form submission handler for rejecting autosaved states. + */ + public function autosaveFormPurgeSubmit($form, FormStateInterface $form_state) { + \Drupal::logger('debug')->debug(__METHOD__); + $this->purgeAllAutosavedStates($form_state, $this->currentUser->id()); + } + + /** + * Ajax callback for rejecting autosaved states. + */ + public function autosaveFormPurgeAjax($form, FormStateInterface $form_state) { + \Drupal::logger('debug')->debug(__METHOD__); + return new AjaxResponse(); + } + /** * Form submission handler for autosaving forms. */ diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index 21dae10ad..7ab4b5576 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -54,12 +54,12 @@ public function storeEntityAndFormState($form_id, $form_session_id, $entity_type if (!$entity->isNew()) { + // @todo FIX This causes new previews to restore the saved one (instead of current form state). // Purge previous stored states for the entity, // e.g. when user is navigating away from the page. // Also, this approach ensures that there will be always // a stored form state in the db, in case we need to restore. // $this->purgeAutosavedEntityState($entity_type_id, $entity_id, NULL, $form_id, $langcode, $uid); - // Now, enter the new values. $this->connection->insert(static::AUTOSAVE_ENTITY_FORM_TABLE) ->fields([ 'form_id', From 86e489bde138cea6675532adbf2422a499a3279f Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Fri, 12 Apr 2024 13:00:18 +0300 Subject: [PATCH 014/221] feat(slb-146): add form session id on get form state condition (optional) --- .../src/Storage/AutosaveEntityFormDatabaseStorage.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index 7ab4b5576..0098464b7 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -169,10 +169,9 @@ public function getFormState($form_id, $entity_type_id, $entity_id, $langcode, $ ->orderBy('timestamp', 'DESC') ->condition('form_id', $form_id); - // Use this for future restore state. - /* if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } */ + if (isset($form_session_id)) { + $query->condition('form_session_id', $form_session_id); + } $query->condition('entity_type_id', $entity_type_id) ->condition('entity_id', $entity_id) From 3d0495296472a9c1b0c7f1fe9952de6a4dddbed9 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:03:53 +0200 Subject: [PATCH 015/221] feat: directive to load all translations of a menu Necessary to load them into the preview. --- packages/drupal/custom/custom.services.yml | 3 ++ packages/drupal/custom/src/Menus.php | 33 ++++++++++++++++++++++ packages/schema/src/schema.graphql | 15 ++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 packages/drupal/custom/src/Menus.php diff --git a/packages/drupal/custom/custom.services.yml b/packages/drupal/custom/custom.services.yml index fd6d0a918..5adbb9068 100644 --- a/packages/drupal/custom/custom.services.yml +++ b/packages/drupal/custom/custom.services.yml @@ -7,3 +7,6 @@ services: custom.webform: class: Drupal\custom\Webform arguments: ['@renderer', '@entity_type.manager', '@serializer'] + + custom.menus: + class: Drupal\custom\Menus diff --git a/packages/drupal/custom/src/Menus.php b/packages/drupal/custom/src/Menus.php new file mode 100644 index 000000000..fbaaf4aa0 --- /dev/null +++ b/packages/drupal/custom/src/Menus.php @@ -0,0 +1,33 @@ +getStorage('menu')->load($args->args['menu_id']); + $languages = \Drupal::languageManager()->getLanguages(); + $translations = []; + foreach ($languages as $language) { + $translation = clone $menu; + $translation->set('langcode', $language->getId()); + $translations[] = $translation; + } + return $translations; + } + +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 232210302..289d4000b 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -3,6 +3,11 @@ scalar Markup @default @value(string: "") scalar ImageSource @default @value(string: "") scalar JSONString @default @value(string: "{}") +""" +implementation(drupal): custom.menus::getMenuTranslations +""" +directive @menuTranslations(menu_id: String!) on FIELD_DEFINITION + """ implementation(drupal): custom.webform::url """ @@ -246,8 +251,14 @@ type Query { previewDecapPage: DecapPage previewDrupalPage(id: ID!, rid: ID, locale: String!): DrupalPage @fetchEntity(type: "node", id: "$id", rid: "$rid", language: "$locale") - mainNavigations: [MainNavigation] @gatsbyNodes(type: "MainNavigation") - footerNavigations: [FooterNavigation] @gatsbyNodes(type: "FooterNavigation") + + mainNavigations: [MainNavigation] + @gatsbyNodes(type: "MainNavigation") + @menuTranslations(menu_id: "main") + + footerNavigations: [FooterNavigation] + @gatsbyNodes(type: "FooterNavigation") + @menuTranslations(menu_id: "footer") allPages: [Page] @gatsbyNodes(type: "Page") websiteSettings: WebsiteSettings From a7aa166b0f424baead5a1ee129b291f73de3b66b Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:13:31 +0200 Subject: [PATCH 016/221] fix(SLB-209): remove refetch interval --- packages/ui/src/utils/operation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/utils/operation.ts b/packages/ui/src/utils/operation.ts index 9ec7d1742..3c8872c77 100644 --- a/packages/ui/src/utils/operation.ts +++ b/packages/ui/src/utils/operation.ts @@ -19,7 +19,10 @@ export function useOperation( // so it does not try to fetch. executor instanceof Function ? (arg) => executor(arg[1]) : null, variables?.pathname?.indexOf('__preview') > 0 - ? { suspense: false, refreshInterval: 1000 } + ? { + suspense: false, + keepPreviousData: true, + } : { suspense: false }, ); return executor instanceof Function From 5c9eccc4c2fec537f290cb430a9165980b03a22e Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:14:18 +0200 Subject: [PATCH 017/221] feat(SLB-209): expose function to invalidate an swr query --- packages/ui/src/utils/operation.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/utils/operation.ts b/packages/ui/src/utils/operation.ts index 3c8872c77..f05499b3f 100644 --- a/packages/ui/src/utils/operation.ts +++ b/packages/ui/src/utils/operation.ts @@ -4,9 +4,16 @@ import { OperationVariables, useExecutor, } from '@custom/schema'; -import useSwr, { SWRResponse } from 'swr'; +import useSwr, { mutate, SWRResponse } from 'swr'; import useSWRMutation, { SWRMutationResponse } from 'swr/mutation'; +export function clear( + operation: TOperation, + variables?: OperationVariables, +) { + mutate([operation, variables]); +} + export function useOperation( operation: TOperation, variables?: OperationVariables, From 77665a6c04c64dc8413ae0288a8ded8e0e037be2 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:14:54 +0200 Subject: [PATCH 018/221] feat(SLB-209): dedicated preview rout that allows to handle refreshes --- packages/ui/src/components/Routes/Preview.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/ui/src/components/Routes/Preview.tsx diff --git a/packages/ui/src/components/Routes/Preview.tsx b/packages/ui/src/components/Routes/Preview.tsx new file mode 100644 index 000000000..3e6fc0e8f --- /dev/null +++ b/packages/ui/src/components/Routes/Preview.tsx @@ -0,0 +1,29 @@ +import type { OperationVariables } from '@custom/schema'; +import { PreviewDrupalPageQuery, useLocation } from '@custom/schema'; +import React from 'react'; + +import { clear, useOperation } from '../../utils/operation'; +import { PageDisplay } from '../Organisms/PageDisplay'; + +function usePreviewParameters(): OperationVariables< + typeof PreviewDrupalPageQuery +> { + const [location] = useLocation(); + + const nid = location.searchParams.get('nid'); + const rid = location.searchParams.get('rid'); + const lang = location.searchParams.get('lang'); + return { id: nid || '', rid: rid || '', locale: lang || 'en' }; +} + +export function usePreviewRefresh() { + const params = usePreviewParameters(); + return () => clear(PreviewDrupalPageQuery, params); +} + +export function Preview() { + const { data } = useOperation(PreviewDrupalPageQuery, usePreviewParameters()); + if (data?.preview) { + return ; + } +} From 1846283a7282c1aeaca0f6436b262d78c7b7b2c2 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:15:26 +0200 Subject: [PATCH 019/221] feat(SLB-209): dedicated preview app with websocket server --- apps/preview/.eslintrc.json | 25 ++++++++++++ apps/preview/.gitignore | 25 ++++++++++++ apps/preview/.swcrc | 11 ++++++ apps/preview/index.html | 15 ++++++++ apps/preview/package.json | 41 ++++++++++++++++++++ apps/preview/server/index.ts | 34 +++++++++++++++++ apps/preview/src/App.tsx | 33 ++++++++++++++++ apps/preview/src/drupal-executor.ts | 59 +++++++++++++++++++++++++++++ apps/preview/src/main.tsx | 12 ++++++ apps/preview/src/vite-env.d.ts | 1 + apps/preview/tsconfig.json | 25 ++++++++++++ apps/preview/tsconfig.node.json | 12 ++++++ apps/preview/turbo.json | 20 ++++++++++ apps/preview/vite.config.ts | 7 ++++ 14 files changed, 320 insertions(+) create mode 100644 apps/preview/.eslintrc.json create mode 100644 apps/preview/.gitignore create mode 100644 apps/preview/.swcrc create mode 100644 apps/preview/index.html create mode 100644 apps/preview/package.json create mode 100644 apps/preview/server/index.ts create mode 100644 apps/preview/src/App.tsx create mode 100644 apps/preview/src/drupal-executor.ts create mode 100644 apps/preview/src/main.tsx create mode 100644 apps/preview/src/vite-env.d.ts create mode 100644 apps/preview/tsconfig.json create mode 100644 apps/preview/tsconfig.node.json create mode 100644 apps/preview/turbo.json create mode 100644 apps/preview/vite.config.ts diff --git a/apps/preview/.eslintrc.json b/apps/preview/.eslintrc.json new file mode 100644 index 000000000..4039eeeb2 --- /dev/null +++ b/apps/preview/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/eslintrc.json", + "extends": ["plugin:storybook/recommended", "plugin:tailwindcss/recommended"], + "plugins": ["tailwindcss", "formatjs"], + "rules": { + "react/jsx-no-literals": [ + "warn", + { + "noStrings": true, + "ignoreProps": true, + "noAttributeStrings": false + } + ], + "formatjs/enforce-default-message": "error", + "formatjs/enforce-id": [ + "error", + { + "idInterpolationPattern": "[sha512:contenthash:base64:6]" + } + ], + "formatjs/enforce-placeholders": "error", + "formatjs/no-camel-case": "error", + "tailwindcss/classnames-order": [0] + } +} diff --git a/apps/preview/.gitignore b/apps/preview/.gitignore new file mode 100644 index 000000000..bd98e77ad --- /dev/null +++ b/apps/preview/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +build +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/preview/.swcrc b/apps/preview/.swcrc new file mode 100644 index 000000000..fb32da49a --- /dev/null +++ b/apps/preview/.swcrc @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "jsc": { + "parser": { + "syntax": "typescript" + } + }, + "module": { + "type": "es6" + } +} diff --git a/apps/preview/index.html b/apps/preview/index.html new file mode 100644 index 000000000..99e1a3605 --- /dev/null +++ b/apps/preview/index.html @@ -0,0 +1,15 @@ + + + + + + + Preview + + + +
+ + + + \ No newline at end of file diff --git a/apps/preview/package.json b/apps/preview/package.json new file mode 100644 index 000000000..058d74c2a --- /dev/null +++ b/apps/preview/package.json @@ -0,0 +1,41 @@ +{ + "name": "custom/preview", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "test:static": "tsc --noEmit && eslint \"**/*.{ts,tsx,js,jsx}\" --ignore-path=\"./.gitignore\"", + "dev:vite": "vite", + "dev:hono": "tsx watch src/index.ts", + "prep:app": "vite build", + "prep:server": "swc ./server -d ./build", + "start": "node build/index.js" + }, + "dependencies": { + "@custom/schema": "workspace:*", + "@custom/ui": "workspace:*", + "express": "^4.19.2", + "express-ws": "^5.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@swc/cli": "^0.1.63", + "@swc/core": "^1.3.102", + "@types/express": "^4.17.21", + "@types/express-ws": "^3.0.4", + "@types/node": "^20.11.17", + "@types/react": "^18.2.46", + "@types/react-dom": "^18.2.18", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "tsx": "^4.7.1", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts new file mode 100644 index 000000000..e9db6c288 --- /dev/null +++ b/apps/preview/server/index.ts @@ -0,0 +1,34 @@ +import express from 'express'; +import expressWs from 'express-ws'; +import { Subject } from 'rxjs'; + +const expressServer = express(); +const expressWsInstance = expressWs(expressServer); +const { app } = expressWsInstance; + +const updates$ = new Subject(); + +// TODO: Protect endpoints and preview with Drupal authentication. +app.post('/__preview', (req, res) => { + updates$.next({ body: req.body }); + res.json(true); +}); + +app.ws('/__preview', (ws) => { + // TODO: Separate updates per user session. + const sub = updates$.subscribe((payload) => { + ws.send(JSON.stringify(payload)); + }); + ws.on('close', sub.unsubscribe); +}); + +app.get('/__preview/*', (req, _, next) => { + req.url = '/'; + next(); +}); + +app.use(express.static('./dist')); +const port = process.env.PREVIEW_PORT || 8001; +console.log(`Server is running on port ${port}`); + +app.listen({ port }); diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx new file mode 100644 index 000000000..e63dd4861 --- /dev/null +++ b/apps/preview/src/App.tsx @@ -0,0 +1,33 @@ +import { OperationExecutor } from '@custom/schema'; +import { Frame } from '@custom/ui/routes/Frame'; +import { Preview, usePreviewRefresh } from '@custom/ui/routes/Preview'; +import { useEffect } from 'react'; +import { webSocket } from 'rxjs/webSocket'; + +import { drupalExecutor } from './drupal-executor'; + +const updates$ = webSocket({ + url: `${window.location.origin.replace('http', 'ws')}/__preview`, +}); + +function App() { + const refresh = usePreviewRefresh(); + useEffect(() => { + const sub = updates$.subscribe(refresh); + return sub.unsubscribe; + }, [refresh]); + return ( + + + + + + ); +} + +export default App; diff --git a/apps/preview/src/drupal-executor.ts b/apps/preview/src/drupal-executor.ts new file mode 100644 index 000000000..cfe9a009c --- /dev/null +++ b/apps/preview/src/drupal-executor.ts @@ -0,0 +1,59 @@ +import { AnyOperationId, OperationVariables } from '@custom/schema'; + +/** + * Create an executor that operates against a Drupal endpoint. + */ +export function drupalExecutor(endpoint: string, forward: boolean = true) { + return async function ( + id: OperationId, + variables?: OperationVariables, + ) { + const url = new URL(endpoint, window.location.origin); + const isMutation = id.includes('Mutation:'); + if (isMutation) { + const { data, errors } = await ( + await fetch(url, { + method: 'POST', + credentials: 'include', + body: JSON.stringify({ + queryId: id, + variables: variables || {}, + }), + headers: forward + ? { + 'SLB-Forwarded-Proto': window.location.protocol.slice(0, -1), + 'SLB-Forwarded-Host': window.location.hostname, + 'SLB-Forwarded-Port': window.location.port, + 'Content-Type': 'application/json', + } + : { + 'Content-Type': 'application/json', + }, + }) + ).json(); + if (errors) { + throw errors; + } + return data; + } else { + url.searchParams.set('queryId', id); + url.searchParams.set('variables', JSON.stringify(variables || {})); + const { data, errors } = await ( + await fetch(url, { + credentials: 'include', + headers: forward + ? { + 'SLB-Forwarded-Proto': window.location.protocol.slice(0, -1), + 'SLB-Forwarded-Host': window.location.hostname, + 'SLB-Forwarded-Port': window.location.port, + } + : {}, + }) + ).json(); + if (errors) { + throw errors; + } + return data; + } + }; +} diff --git a/apps/preview/src/main.tsx b/apps/preview/src/main.tsx new file mode 100644 index 000000000..407d3ea7e --- /dev/null +++ b/apps/preview/src/main.tsx @@ -0,0 +1,12 @@ +import '@custom/ui/styles.css'; + +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import App from './App.tsx'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/apps/preview/src/vite-env.d.ts b/apps/preview/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/apps/preview/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/preview/tsconfig.json b/apps/preview/tsconfig.json new file mode 100644 index 000000000..a7fc6fbf2 --- /dev/null +++ b/apps/preview/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/apps/preview/tsconfig.node.json b/apps/preview/tsconfig.node.json new file mode 100644 index 000000000..1a555ac37 --- /dev/null +++ b/apps/preview/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/apps/preview/turbo.json b/apps/preview/turbo.json new file mode 100644 index 000000000..1b2fdd568 --- /dev/null +++ b/apps/preview/turbo.json @@ -0,0 +1,20 @@ +{ + "extends": ["//"], + "pipeline": { + "prep": { + "dependsOn": ["prep:app", "prep:server"] + }, + "prep:app": { + "dependsOn": ["^prep"], + "inputs": ["src/**", "index.html", "vite.config.ts"], + "outputs": ["dist/**"] + }, + "prep:server": { + "inputs": ["server/**"], + "outputs": ["build/**"] + }, + "test:static": { + "inputs": ["src/**", "!src/gatsby-fragments.js"] + } + } +} diff --git a/apps/preview/vite.config.ts b/apps/preview/vite.config.ts new file mode 100644 index 000000000..e1b1b7638 --- /dev/null +++ b/apps/preview/vite.config.ts @@ -0,0 +1,7 @@ +import react from '@vitejs/plugin-react-swc'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}); From 2ba54ae1da35c42ed765d8765d3dd44a4989e07b Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:17:09 +0200 Subject: [PATCH 020/221] feat(SLB-209): lagoon service for preview app --- .lagoon.yml | 6 ++++++ .lagoon/Dockerfile | 12 ++++++++++++ docker-compose.yml | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/.lagoon.yml b/.lagoon.yml index 8261b6b63..22179218c 100644 --- a/.lagoon.yml +++ b/.lagoon.yml @@ -36,6 +36,8 @@ environments: - example.cms.amazeelabs.dev - build: - example.build.amazeelabs.dev + - preview: + - example.preview.amazeelabs.dev cronjobs: - name: drush cron schedule: '*/15 * * * *' @@ -47,6 +49,8 @@ environments: - stage-example.cms.amazeelabs.dev - build: - stage-example.build.amazeelabs.dev + - preview: + - stage-example.preview.amazeelabs.dev cronjobs: - name: drush cron schedule: '*/15 * * * *' @@ -58,6 +62,8 @@ environments: - dev-example.cms.amazeelabs.dev - build: - dev-example.build.amazeelabs.dev + - preview: + - dev-example.preview.amazeelabs.dev cronjobs: - name: drush cron schedule: '*/15 * * * *' diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile index 4669b403e..686974441 100644 --- a/.lagoon/Dockerfile +++ b/.lagoon/Dockerfile @@ -47,6 +47,7 @@ RUN --mount=type=cache,target=/tmp/cache pnpm i && \ # Deploy apps. RUN --mount=type=cache,target=/tmp/cache pnpm deploy --filter "@custom/cms" /tmp/.deploy/cms --prod RUN --mount=type=cache,target=/tmp/cache pnpm deploy --filter "@custom/website" /tmp/.deploy/website --prod +RUN --mount=type=cache,target=/tmp/cache pnpm deploy --filter "@custom/preview" /tmp/.deploy/preview --prod # ==================================================================================================== # CLI IMAGE @@ -87,6 +88,17 @@ COPY .lagoon/nginx-conf/redirects-map.conf /etc/nginx/redirects-map.conf WORKDIR /app ENV WEBROOT=web +# ==================================================================================================== +# PREVIEW IMAGE +# ==================================================================================================== + +FROM uselagoon/node-18:23.12.0 as publisher + +RUN npm install -g pnpm@8.6.0 +COPY --from=builder /tmp/.deploy/preview /app +ENV PREVIEW_PORT=3000 +CMD pnpm start + # ==================================================================================================== # PUBLISHER IMAGE # ==================================================================================================== diff --git a/docker-compose.yml b/docker-compose.yml index 59d904129..4b85acf86 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -109,6 +109,18 @@ services: lagoon.persistent: /app/persisted-store lagoon.name: build + preview: + build: + context: . + target: preview + dockerfile: .lagoon/Dockerfile + environment: + <<: *default-environment + LAGOON_LOCALDEV_URL: preview-${COMPOSE_PROJECT_NAME:-slbtemplate}.docker.amazee.io + networks: + - amazeeio-network + - default + networks: amazeeio-network: external: true From b643ac26fa232e88d4d55ea7bf980088f4303fc9 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:17:35 +0200 Subject: [PATCH 021/221] feat(SLB-209): use preview app in drupal --- apps/cms/.lagoon.env | 1 + apps/cms/scaffold/settings.php.append.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/cms/.lagoon.env b/apps/cms/.lagoon.env index a2fa48dbc..0309eb662 100644 --- a/apps/cms/.lagoon.env +++ b/apps/cms/.lagoon.env @@ -1,6 +1,7 @@ PROJECT_NAME=example PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" +PREVIEW_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.preview.amazeelabs.dev" # Used to set the original client secret. PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/cms/scaffold/settings.php.append.txt b/apps/cms/scaffold/settings.php.append.txt index 7627baaa7..00575f853 100644 --- a/apps/cms/scaffold/settings.php.append.txt +++ b/apps/cms/scaffold/settings.php.append.txt @@ -5,9 +5,10 @@ $settings['file_private_path'] = $app_root . '/sites/default/files/private'; $publisherUrl = getenv('PUBLISHER_URL') ?: 'http://127.0.0.1:8000'; $netlifyUrl = getenv('NETLIFY_URL') ?: 'http://127.0.0.1:8000'; +$previewUrl = getenv('PREVIEW_URL') ?: 'http://127.0.0.1:8001'; $config['silverback_external_preview.settings'] = [ - 'preview_host' => $netlifyUrl, + 'preview_host' => $previewUrl, 'live_host' => $netlifyUrl, ]; From d8ca9a7f6d773fdb49868dabc8d78ab15c232d69 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 17:39:49 +0200 Subject: [PATCH 022/221] fix(SLB-209): lagoon type for preview service --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 4b85acf86..c5ad2e374 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -120,6 +120,9 @@ services: networks: - amazeeio-network - default + labels: + lagoon.type: node + lagoon.name: preview networks: amazeeio-network: From 85f919afc8cdd93e24e0a51cb9377d41f05fddac Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 18:08:14 +0200 Subject: [PATCH 023/221] fix(SLB-209): docker build stage name --- .lagoon/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile index 686974441..269dfbead 100644 --- a/.lagoon/Dockerfile +++ b/.lagoon/Dockerfile @@ -92,7 +92,7 @@ ENV WEBROOT=web # PREVIEW IMAGE # ==================================================================================================== -FROM uselagoon/node-18:23.12.0 as publisher +FROM uselagoon/node-18:23.12.0 as preview RUN npm install -g pnpm@8.6.0 COPY --from=builder /tmp/.deploy/preview /app From 2d4c5b41b06e9ff8efbe8b1b60385afa909d9d08 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 18:09:18 +0200 Subject: [PATCH 024/221] fix(SLB-209): proxy graphql requests So it is possible to change Drupal's url via environment variables, after build. --- apps/preview/.lagoon.env | 1 + apps/preview/package.json | 2 + apps/preview/server/index.ts | 9 + apps/preview/src/App.tsx | 7 +- pnpm-lock.yaml | 437 ++++++++++++++++++++++++++++++++--- 5 files changed, 422 insertions(+), 34 deletions(-) create mode 100644 apps/preview/.lagoon.env diff --git a/apps/preview/.lagoon.env b/apps/preview/.lagoon.env new file mode 100644 index 000000000..2c80dc9c1 --- /dev/null +++ b/apps/preview/.lagoon.env @@ -0,0 +1 @@ +DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" diff --git a/apps/preview/package.json b/apps/preview/package.json index 058d74c2a..b3f160641 100644 --- a/apps/preview/package.json +++ b/apps/preview/package.json @@ -16,6 +16,7 @@ "@custom/ui": "workspace:*", "express": "^4.19.2", "express-ws": "^5.0.2", + "http-proxy-middleware": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "rxjs": "^7.8.1" @@ -25,6 +26,7 @@ "@swc/core": "^1.3.102", "@types/express": "^4.17.21", "@types/express-ws": "^3.0.4", + "@types/http-proxy-middleware": "^1.0.0", "@types/node": "^20.11.17", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index e9db6c288..4556dc41b 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -1,5 +1,6 @@ import express from 'express'; import expressWs from 'express-ws'; +import { createProxyMiddleware } from 'http-proxy-middleware'; import { Subject } from 'rxjs'; const expressServer = express(); @@ -8,6 +9,14 @@ const { app } = expressWsInstance; const updates$ = new Subject(); +app.use( + '/graphql', + createProxyMiddleware({ + target: process.env.DRUPAL_URL || 'http://localhost:8888', + changeOrigin: true, + }), +); + // TODO: Protect endpoints and preview with Drupal authentication. app.post('/__preview', (req, res) => { updates$.next({ body: req.body }); diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index e63dd4861..ba86b75d2 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -17,12 +17,7 @@ function App() { return sub.unsubscribe; }, [refresh]); return ( - + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aca358f2..5281ba170 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^18.4.3 - version: 18.4.3(@types/node@18.19.31)(typescript@5.3.3) + version: 18.4.3(@types/node@20.12.7)(typescript@5.3.3) '@commitlint/config-conventional': specifier: ^18.4.3 version: 18.4.3 @@ -64,7 +64,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@18.19.31) + version: 1.1.1(@types/node@20.12.7) apps/cms: dependencies: @@ -184,6 +184,85 @@ importers: specifier: ^5.0.10 version: 5.0.10(@types/node@18.0.0) + apps/preview: + dependencies: + '@custom/schema': + specifier: workspace:* + version: link:../../packages/schema + '@custom/ui': + specifier: workspace:* + version: link:../../packages/ui + express: + specifier: ^4.19.2 + version: 4.19.2 + express-ws: + specifier: ^5.0.2 + version: 5.0.2(express@4.19.2) + http-proxy-middleware: + specifier: ^3.0.0 + version: 3.0.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + devDependencies: + '@swc/cli': + specifier: ^0.1.63 + version: 0.1.63(@swc/core@1.4.13) + '@swc/core': + specifier: ^1.3.102 + version: 1.4.13 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/express-ws': + specifier: ^3.0.4 + version: 3.0.4 + '@types/http-proxy-middleware': + specifier: ^1.0.0 + version: 1.0.0 + '@types/node': + specifier: ^20.11.17 + version: 20.12.7 + '@types/react': + specifier: ^18.2.46 + version: 18.2.46 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.18 + '@typescript-eslint/eslint-plugin': + specifier: ^7.2.0 + version: 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': + specifier: ^7.2.0 + version: 7.6.0(eslint@7.32.0)(typescript@5.4.4) + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.5.0(vite@5.2.8) + eslint: + specifier: '7' + version: 7.32.0 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@7.32.0) + eslint-plugin-react-refresh: + specifier: ^0.4.6 + version: 0.4.6(eslint@7.32.0) + tsx: + specifier: ^4.7.1 + version: 4.7.2 + typescript: + specifier: ^5.2.2 + version: 5.4.4 + vite: + specifier: ^5.2.0 + version: 5.2.8(@types/node@20.12.7) + apps/website: dependencies: '@amazeelabs/bridge-gatsby': @@ -2608,14 +2687,14 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - /@commitlint/cli@18.4.3(@types/node@18.19.31)(typescript@5.3.3): + /@commitlint/cli@18.4.3(@types/node@20.12.7)(typescript@5.3.3): resolution: {integrity: sha512-zop98yfB3A6NveYAZ3P1Mb6bIXuCeWgnUfVNkH4yhIMQpQfzFwseadazOuSn0OOfTt0lWuFauehpm9GcqM5lww==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.6.1 '@commitlint/lint': 18.6.1 - '@commitlint/load': 18.6.1(@types/node@18.19.31)(typescript@5.3.3) + '@commitlint/load': 18.6.1(@types/node@20.12.7)(typescript@5.3.3) '@commitlint/read': 18.6.1 '@commitlint/types': 18.6.1 execa: 5.1.1 @@ -2686,7 +2765,7 @@ packages: '@commitlint/types': 18.6.1 dev: true - /@commitlint/load@18.6.1(@types/node@18.19.31)(typescript@5.3.3): + /@commitlint/load@18.6.1(@types/node@20.12.7)(typescript@5.3.3): resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} engines: {node: '>=v18'} dependencies: @@ -2696,7 +2775,7 @@ packages: '@commitlint/types': 18.6.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.3.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.7)(cosmiconfig@8.3.6)(typescript@5.3.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -8958,6 +9037,26 @@ packages: slash: 3.0.0 source-map: 0.7.4 + /@swc/cli@0.1.63(@swc/core@1.4.13): + resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==} + engines: {node: '>= 12.13'} + hasBin: true + peerDependencies: + '@swc/core': ^1.2.66 + chokidar: ^3.5.1 + peerDependenciesMeta: + chokidar: + optional: true + dependencies: + '@mole-inc/bin-wrapper': 8.0.1 + '@swc/core': 1.4.13 + commander: 7.2.0 + fast-glob: 3.3.2 + semver: 7.6.0 + slash: 3.0.0 + source-map: 0.7.4 + dev: true + /@swc/core-darwin-arm64@1.3.102: resolution: {integrity: sha512-CJDxA5Wd2cUMULj3bjx4GEoiYyyiyL8oIOu4Nhrs9X+tlg8DnkCm4nI57RJGP8Mf6BaXPIJkHX8yjcefK2RlDA==} engines: {node: '>=10'} @@ -9418,7 +9517,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 18.19.31 + '@types/node': 20.12.7 dev: true /@types/cacheable-request@6.0.3: @@ -9426,7 +9525,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 18.19.31 + '@types/node': 20.12.7 '@types/responselike': 1.0.3 /@types/chai-subset@1.3.5: @@ -9450,7 +9549,7 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 18.19.31 + '@types/node': 20.12.7 dev: true /@types/cookie@0.4.1: @@ -9532,12 +9631,20 @@ packages: /@types/express-serve-static-core@4.19.0: resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} dependencies: - '@types/node': 18.19.31 + '@types/node': 20.12.7 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 dev: true + /@types/express-ws@3.0.4: + resolution: {integrity: sha512-Yjj18CaivG5KndgcvzttWe8mPFinPCHJC2wvyQqVzA7hqeufM8EtWMj6mpp5omg3s8XALUexhOu8aXAyi/DyJQ==} + dependencies: + '@types/express': 4.17.21 + '@types/express-serve-static-core': 4.19.0 + '@types/ws': 8.5.10 + dev: true + /@types/express@4.17.21: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: @@ -9598,10 +9705,19 @@ packages: resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} dev: true + /@types/http-proxy-middleware@1.0.0: + resolution: {integrity: sha512-/s8lFX6rT43hSPqjjD8KNuu0SkPKY7uIdR6u9DCxVqCRhAvfKxGbVOixJsAT2mdpSnCyrGFAGoB39KFh6tmRxw==} + deprecated: This is a stub types definition. http-proxy-middleware provides its own type definitions, so you do not need this installed. + dependencies: + http-proxy-middleware: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /@types/http-proxy@1.17.14: resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==} dependencies: - '@types/node': 18.0.0 + '@types/node': 20.12.7 /@types/image-size@0.8.0: resolution: {integrity: sha512-hMlhu25ji75dXQk2uZkN3pTJ+lWrgKr8M1fTpyyFvuu+SJZBdGa5gDm4BVNobWXHZbOU11mBj0vciYp7qOfAFg==} @@ -9644,7 +9760,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.0.0 + '@types/node': 20.12.7 /@types/lodash@4.17.0: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} @@ -9710,6 +9826,11 @@ packages: dependencies: undici-types: 5.26.5 + /@types/node@20.12.7: + resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + dependencies: + undici-types: 5.26.5 + /@types/node@8.10.66: resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} @@ -9796,7 +9917,7 @@ packages: /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 20.12.7 /@types/retry@0.12.1: resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==} @@ -9828,7 +9949,7 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 18.19.31 + '@types/node': 20.12.7 dev: true /@types/serve-static@1.15.5: @@ -9843,7 +9964,7 @@ packages: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 18.19.31 + '@types/node': 20.12.7 '@types/send': 0.17.4 dev: true @@ -9999,7 +10120,7 @@ packages: /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 18.19.31 + '@types/node': 20.12.7 /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -10170,6 +10291,35 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@7.6.0(@typescript-eslint/parser@7.6.0)(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.6.0(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/scope-manager': 7.6.0 + '@typescript-eslint/type-utils': 7.6.0(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/utils': 7.6.0(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/visitor-keys': 7.6.0 + debug: 4.3.4 + eslint: 7.32.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10270,6 +10420,27 @@ packages: transitivePeerDependencies: - supports-color + /@typescript-eslint/parser@7.6.0(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 7.6.0 + '@typescript-eslint/types': 7.6.0 + '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) + '@typescript-eslint/visitor-keys': 7.6.0 + debug: 4.3.4 + eslint: 7.32.0 + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10292,6 +10463,14 @@ packages: '@typescript-eslint/visitor-keys': 6.21.0 dev: true + /@typescript-eslint/scope-manager@7.6.0: + resolution: {integrity: sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.6.0 + '@typescript-eslint/visitor-keys': 7.6.0 + dev: true + /@typescript-eslint/type-utils@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10391,6 +10570,26 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils@7.6.0(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) + '@typescript-eslint/utils': 7.6.0(eslint@7.32.0)(typescript@5.4.4) + debug: 4.3.4 + eslint: 7.32.0 + ts-api-utils: 1.3.0(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@5.62.0: resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10404,6 +10603,11 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true + /@typescript-eslint/types@7.6.0: + resolution: {integrity: sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + /@typescript-eslint/typescript-estree@5.62.0(supports-color@9.4.0)(typescript@5.4.4): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10529,6 +10733,28 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.4): + resolution: {integrity: sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.6.0 + '@typescript-eslint/visitor-keys': 7.6.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10645,6 +10871,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@7.6.0(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@7.32.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.6.0 + '@typescript-eslint/types': 7.6.0 + '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) + eslint: 7.32.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10667,6 +10912,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@7.6.0: + resolution: {integrity: sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.6.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@vercel/nft@0.23.1: resolution: {integrity: sha512-NE0xSmGWVhgHF1OIoir71XAd0W0C1UE3nzFyhpFiMr3rVhetww7NvM1kc41trBsPG37Bh+dE5FYCTMzM/gBu0w==} engines: {node: '>=14'} @@ -10769,6 +11022,17 @@ packages: - '@swc/helpers' dev: true + /@vitejs/plugin-react-swc@3.5.0(vite@5.2.8): + resolution: {integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==} + peerDependencies: + vite: ^4 || ^5 + dependencies: + '@swc/core': 1.4.13 + vite: 5.2.8(@types/node@20.12.7) + transitivePeerDependencies: + - '@swc/helpers' + dev: true + /@vitejs/plugin-react@3.1.0(vite@5.0.10): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -14162,7 +14426,7 @@ packages: object-assign: 4.1.1 vary: 1.1.2 - /cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3): + /cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.7)(cosmiconfig@8.3.6)(typescript@5.3.3): resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: @@ -14170,7 +14434,7 @@ packages: cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 18.19.31 + '@types/node': 20.12.7 cosmiconfig: 8.3.6(typescript@5.3.3) jiti: 1.21.0 typescript: 5.3.3 @@ -17028,6 +17292,14 @@ packages: dependencies: eslint: 7.32.0 + /eslint-plugin-react-refresh@0.4.6(eslint@7.32.0): + resolution: {integrity: sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==} + peerDependencies: + eslint: '>=7' + dependencies: + eslint: 7.32.0 + dev: true + /eslint-plugin-react@7.33.2(eslint@7.0.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} @@ -17365,7 +17637,6 @@ packages: /eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -17538,6 +17809,19 @@ packages: - utf-8-validate dev: false + /express-ws@5.0.2(express@4.19.2): + resolution: {integrity: sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==} + engines: {node: '>=4.5.0'} + peerDependencies: + express: ^4.0.0 || ^5.0.0-alpha.1 + dependencies: + express: 4.19.2 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -19813,6 +20097,12 @@ packages: es-errors: 1.3.0 get-intrinsic: 1.2.4 + /get-tsconfig@4.7.3: + resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /get-value@2.0.6: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} engines: {node: '>=0.10.0'} @@ -20880,6 +21170,19 @@ packages: - debug dev: false + /http-proxy-middleware@3.0.0: + resolution: {integrity: sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/http-proxy': 1.17.14 + debug: 4.3.4 + http-proxy: 1.18.1(debug@4.3.4) + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.5 + transitivePeerDependencies: + - supports-color + /http-proxy@1.18.1: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} @@ -20900,7 +21203,6 @@ packages: requires-port: 1.0.0 transitivePeerDependencies: - debug - dev: false /http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} @@ -21677,7 +21979,6 @@ packages: /is-plain-obj@3.0.0: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} - dev: false /is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} @@ -28680,7 +28981,6 @@ packages: /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: false /reselect@4.1.8: resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} @@ -28722,6 +29022,10 @@ packages: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} dev: false + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} dependencies: @@ -31027,6 +31331,15 @@ packages: dependencies: typescript: 5.3.3 + /ts-api-utils@1.3.0(typescript@5.4.4): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.4.4 + dev: true + /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -31176,6 +31489,17 @@ packages: typescript: 5.4.4 dev: false + /tsx@4.7.2: + resolution: {integrity: sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.19.12 + get-tsconfig: 4.7.3 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -32203,6 +32527,27 @@ packages: - terser dev: true + /vite-node@1.1.1(@types/node@20.12.7): + resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.2.8(@types/node@20.12.7) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-plugin-istanbul@3.0.4: resolution: {integrity: sha512-DJy3cq6yOFbsM3gLQf/3zeuaJNJsfBv5dLFdZdv8sUV30xLtZI+66QeYfHUyP/5vBUYyLA+xNUCSG5uHY6w+5g==} dependencies: @@ -32360,6 +32705,42 @@ packages: optionalDependencies: fsevents: 2.3.3 + /vite@5.2.8(@types/node@20.12.7): + resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.12.7 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.14.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vitest@0.34.6: resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} @@ -32552,7 +32933,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31): + /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -32588,6 +32969,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 + happy-dom: 12.10.3 local-pkg: 0.5.0 magic-string: 0.30.9 pathe: 1.1.2 @@ -32609,7 +32991,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): + /vitest@1.1.1(@types/node@20.12.7): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -32634,7 +33016,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.12.7 '@vitest/expect': 1.1.1 '@vitest/runner': 1.1.1 '@vitest/snapshot': 1.1.1 @@ -32645,7 +33027,6 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 - happy-dom: 12.10.3 local-pkg: 0.5.0 magic-string: 0.30.9 pathe: 1.1.2 @@ -32654,8 +33035,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.8(@types/node@18.19.31) - vite-node: 1.1.1(@types/node@18.19.31) + vite: 5.2.8(@types/node@20.12.7) + vite-node: 1.1.1(@types/node@20.12.7) why-is-node-running: 2.2.2 transitivePeerDependencies: - less From 5f12709c7a56bc49e078d7ac0b953e95e38c4a9f Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 18:38:04 +0200 Subject: [PATCH 025/221] fix(SLB-209): wrong preview package name --- apps/preview/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/preview/package.json b/apps/preview/package.json index b3f160641..4466ca535 100644 --- a/apps/preview/package.json +++ b/apps/preview/package.json @@ -1,5 +1,5 @@ { - "name": "custom/preview", + "name": "@custom/preview", "private": true, "version": "0.0.0", "type": "module", From b785a6066c90ad55925b12991e1a530e3226af44 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 20:14:25 +0200 Subject: [PATCH 026/221] fix(SLB-209): dev server commands --- apps/preview/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/preview/package.json b/apps/preview/package.json index 4466ca535..7e02c7199 100644 --- a/apps/preview/package.json +++ b/apps/preview/package.json @@ -5,8 +5,8 @@ "type": "module", "scripts": { "test:static": "tsc --noEmit && eslint \"**/*.{ts,tsx,js,jsx}\" --ignore-path=\"./.gitignore\"", - "dev:vite": "vite", - "dev:hono": "tsx watch src/index.ts", + "dev:app": "vite", + "dev:server": "tsx watch server/index.ts", "prep:app": "vite build", "prep:server": "swc ./server -d ./build", "start": "node build/index.js" From d26877da518c957c3507864e7c35ca97aa52fbe4 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 20:15:04 +0200 Subject: [PATCH 027/221] Revert "fix(SLB-209): proxy graphql requests" This reverts commit 2d4c5b41b06e9ff8efbe8b1b60385afa909d9d08. --- apps/preview/.lagoon.env | 1 - apps/preview/package.json | 2 - apps/preview/server/index.ts | 9 - apps/preview/src/App.tsx | 7 +- pnpm-lock.yaml | 437 +++-------------------------------- 5 files changed, 34 insertions(+), 422 deletions(-) delete mode 100644 apps/preview/.lagoon.env diff --git a/apps/preview/.lagoon.env b/apps/preview/.lagoon.env deleted file mode 100644 index 2c80dc9c1..000000000 --- a/apps/preview/.lagoon.env +++ /dev/null @@ -1 +0,0 @@ -DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" diff --git a/apps/preview/package.json b/apps/preview/package.json index 7e02c7199..e5ec902c5 100644 --- a/apps/preview/package.json +++ b/apps/preview/package.json @@ -16,7 +16,6 @@ "@custom/ui": "workspace:*", "express": "^4.19.2", "express-ws": "^5.0.2", - "http-proxy-middleware": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "rxjs": "^7.8.1" @@ -26,7 +25,6 @@ "@swc/core": "^1.3.102", "@types/express": "^4.17.21", "@types/express-ws": "^3.0.4", - "@types/http-proxy-middleware": "^1.0.0", "@types/node": "^20.11.17", "@types/react": "^18.2.46", "@types/react-dom": "^18.2.18", diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index 4556dc41b..e9db6c288 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -1,6 +1,5 @@ import express from 'express'; import expressWs from 'express-ws'; -import { createProxyMiddleware } from 'http-proxy-middleware'; import { Subject } from 'rxjs'; const expressServer = express(); @@ -9,14 +8,6 @@ const { app } = expressWsInstance; const updates$ = new Subject(); -app.use( - '/graphql', - createProxyMiddleware({ - target: process.env.DRUPAL_URL || 'http://localhost:8888', - changeOrigin: true, - }), -); - // TODO: Protect endpoints and preview with Drupal authentication. app.post('/__preview', (req, res) => { updates$.next({ body: req.body }); diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index ba86b75d2..e63dd4861 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -17,7 +17,12 @@ function App() { return sub.unsubscribe; }, [refresh]); return ( - + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5281ba170..8aca358f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^18.4.3 - version: 18.4.3(@types/node@20.12.7)(typescript@5.3.3) + version: 18.4.3(@types/node@18.19.31)(typescript@5.3.3) '@commitlint/config-conventional': specifier: ^18.4.3 version: 18.4.3 @@ -64,7 +64,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@20.12.7) + version: 1.1.1(@types/node@18.19.31) apps/cms: dependencies: @@ -184,85 +184,6 @@ importers: specifier: ^5.0.10 version: 5.0.10(@types/node@18.0.0) - apps/preview: - dependencies: - '@custom/schema': - specifier: workspace:* - version: link:../../packages/schema - '@custom/ui': - specifier: workspace:* - version: link:../../packages/ui - express: - specifier: ^4.19.2 - version: 4.19.2 - express-ws: - specifier: ^5.0.2 - version: 5.0.2(express@4.19.2) - http-proxy-middleware: - specifier: ^3.0.0 - version: 3.0.0 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - rxjs: - specifier: ^7.8.1 - version: 7.8.1 - devDependencies: - '@swc/cli': - specifier: ^0.1.63 - version: 0.1.63(@swc/core@1.4.13) - '@swc/core': - specifier: ^1.3.102 - version: 1.4.13 - '@types/express': - specifier: ^4.17.21 - version: 4.17.21 - '@types/express-ws': - specifier: ^3.0.4 - version: 3.0.4 - '@types/http-proxy-middleware': - specifier: ^1.0.0 - version: 1.0.0 - '@types/node': - specifier: ^20.11.17 - version: 20.12.7 - '@types/react': - specifier: ^18.2.46 - version: 18.2.46 - '@types/react-dom': - specifier: ^18.2.18 - version: 18.2.18 - '@typescript-eslint/eslint-plugin': - specifier: ^7.2.0 - version: 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@7.32.0)(typescript@5.4.4) - '@typescript-eslint/parser': - specifier: ^7.2.0 - version: 7.6.0(eslint@7.32.0)(typescript@5.4.4) - '@vitejs/plugin-react-swc': - specifier: ^3.5.0 - version: 3.5.0(vite@5.2.8) - eslint: - specifier: '7' - version: 7.32.0 - eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.0(eslint@7.32.0) - eslint-plugin-react-refresh: - specifier: ^0.4.6 - version: 0.4.6(eslint@7.32.0) - tsx: - specifier: ^4.7.1 - version: 4.7.2 - typescript: - specifier: ^5.2.2 - version: 5.4.4 - vite: - specifier: ^5.2.0 - version: 5.2.8(@types/node@20.12.7) - apps/website: dependencies: '@amazeelabs/bridge-gatsby': @@ -2687,14 +2608,14 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - /@commitlint/cli@18.4.3(@types/node@20.12.7)(typescript@5.3.3): + /@commitlint/cli@18.4.3(@types/node@18.19.31)(typescript@5.3.3): resolution: {integrity: sha512-zop98yfB3A6NveYAZ3P1Mb6bIXuCeWgnUfVNkH4yhIMQpQfzFwseadazOuSn0OOfTt0lWuFauehpm9GcqM5lww==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.6.1 '@commitlint/lint': 18.6.1 - '@commitlint/load': 18.6.1(@types/node@20.12.7)(typescript@5.3.3) + '@commitlint/load': 18.6.1(@types/node@18.19.31)(typescript@5.3.3) '@commitlint/read': 18.6.1 '@commitlint/types': 18.6.1 execa: 5.1.1 @@ -2765,7 +2686,7 @@ packages: '@commitlint/types': 18.6.1 dev: true - /@commitlint/load@18.6.1(@types/node@20.12.7)(typescript@5.3.3): + /@commitlint/load@18.6.1(@types/node@18.19.31)(typescript@5.3.3): resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} engines: {node: '>=v18'} dependencies: @@ -2775,7 +2696,7 @@ packages: '@commitlint/types': 18.6.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.3.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.7)(cosmiconfig@8.3.6)(typescript@5.3.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -9037,26 +8958,6 @@ packages: slash: 3.0.0 source-map: 0.7.4 - /@swc/cli@0.1.63(@swc/core@1.4.13): - resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==} - engines: {node: '>= 12.13'} - hasBin: true - peerDependencies: - '@swc/core': ^1.2.66 - chokidar: ^3.5.1 - peerDependenciesMeta: - chokidar: - optional: true - dependencies: - '@mole-inc/bin-wrapper': 8.0.1 - '@swc/core': 1.4.13 - commander: 7.2.0 - fast-glob: 3.3.2 - semver: 7.6.0 - slash: 3.0.0 - source-map: 0.7.4 - dev: true - /@swc/core-darwin-arm64@1.3.102: resolution: {integrity: sha512-CJDxA5Wd2cUMULj3bjx4GEoiYyyiyL8oIOu4Nhrs9X+tlg8DnkCm4nI57RJGP8Mf6BaXPIJkHX8yjcefK2RlDA==} engines: {node: '>=10'} @@ -9517,7 +9418,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.12.7 + '@types/node': 18.19.31 dev: true /@types/cacheable-request@6.0.3: @@ -9525,7 +9426,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.12.7 + '@types/node': 18.19.31 '@types/responselike': 1.0.3 /@types/chai-subset@1.3.5: @@ -9549,7 +9450,7 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.12.7 + '@types/node': 18.19.31 dev: true /@types/cookie@0.4.1: @@ -9631,20 +9532,12 @@ packages: /@types/express-serve-static-core@4.19.0: resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} dependencies: - '@types/node': 20.12.7 + '@types/node': 18.19.31 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 dev: true - /@types/express-ws@3.0.4: - resolution: {integrity: sha512-Yjj18CaivG5KndgcvzttWe8mPFinPCHJC2wvyQqVzA7hqeufM8EtWMj6mpp5omg3s8XALUexhOu8aXAyi/DyJQ==} - dependencies: - '@types/express': 4.17.21 - '@types/express-serve-static-core': 4.19.0 - '@types/ws': 8.5.10 - dev: true - /@types/express@4.17.21: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: @@ -9705,19 +9598,10 @@ packages: resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} dev: true - /@types/http-proxy-middleware@1.0.0: - resolution: {integrity: sha512-/s8lFX6rT43hSPqjjD8KNuu0SkPKY7uIdR6u9DCxVqCRhAvfKxGbVOixJsAT2mdpSnCyrGFAGoB39KFh6tmRxw==} - deprecated: This is a stub types definition. http-proxy-middleware provides its own type definitions, so you do not need this installed. - dependencies: - http-proxy-middleware: 3.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /@types/http-proxy@1.17.14: resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==} dependencies: - '@types/node': 20.12.7 + '@types/node': 18.0.0 /@types/image-size@0.8.0: resolution: {integrity: sha512-hMlhu25ji75dXQk2uZkN3pTJ+lWrgKr8M1fTpyyFvuu+SJZBdGa5gDm4BVNobWXHZbOU11mBj0vciYp7qOfAFg==} @@ -9760,7 +9644,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.12.7 + '@types/node': 18.0.0 /@types/lodash@4.17.0: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} @@ -9826,11 +9710,6 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@20.12.7: - resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} - dependencies: - undici-types: 5.26.5 - /@types/node@8.10.66: resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} @@ -9917,7 +9796,7 @@ packages: /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 20.12.7 + '@types/node': 18.0.0 /@types/retry@0.12.1: resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==} @@ -9949,7 +9828,7 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.12.7 + '@types/node': 18.19.31 dev: true /@types/serve-static@1.15.5: @@ -9964,7 +9843,7 @@ packages: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.12.7 + '@types/node': 18.19.31 '@types/send': 0.17.4 dev: true @@ -10120,7 +9999,7 @@ packages: /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.12.7 + '@types/node': 18.19.31 /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -10291,35 +10170,6 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin@7.6.0(@typescript-eslint/parser@7.6.0)(eslint@7.32.0)(typescript@5.4.4): - resolution: {integrity: sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.6.0(eslint@7.32.0)(typescript@5.4.4) - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/type-utils': 7.6.0(eslint@7.32.0)(typescript@5.4.4) - '@typescript-eslint/utils': 7.6.0(eslint@7.32.0)(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 7.6.0 - debug: 4.3.4 - eslint: 7.32.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10420,27 +10270,6 @@ packages: transitivePeerDependencies: - supports-color - /@typescript-eslint/parser@7.6.0(eslint@7.32.0)(typescript@5.4.4): - resolution: {integrity: sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 7.6.0 - debug: 4.3.4 - eslint: 7.32.0 - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10463,14 +10292,6 @@ packages: '@typescript-eslint/visitor-keys': 6.21.0 dev: true - /@typescript-eslint/scope-manager@7.6.0: - resolution: {integrity: sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==} - engines: {node: ^18.18.0 || >=20.0.0} - dependencies: - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/visitor-keys': 7.6.0 - dev: true - /@typescript-eslint/type-utils@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10570,26 +10391,6 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@7.6.0(eslint@7.32.0)(typescript@5.4.4): - resolution: {integrity: sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) - '@typescript-eslint/utils': 7.6.0(eslint@7.32.0)(typescript@5.4.4) - debug: 4.3.4 - eslint: 7.32.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/types@5.62.0: resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10603,11 +10404,6 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/types@7.6.0: - resolution: {integrity: sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==} - engines: {node: ^18.18.0 || >=20.0.0} - dev: true - /@typescript-eslint/typescript-estree@5.62.0(supports-color@9.4.0)(typescript@5.4.4): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10733,28 +10529,6 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.4): - resolution: {integrity: sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/visitor-keys': 7.6.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.4) - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color - dev: true - /@typescript-eslint/utils@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10871,25 +10645,6 @@ packages: - typescript dev: true - /@typescript-eslint/utils@7.6.0(eslint@7.32.0)(typescript@5.4.4): - resolution: {integrity: sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@7.32.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.4) - eslint: 7.32.0 - semver: 7.6.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10912,14 +10667,6 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@typescript-eslint/visitor-keys@7.6.0: - resolution: {integrity: sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==} - engines: {node: ^18.18.0 || >=20.0.0} - dependencies: - '@typescript-eslint/types': 7.6.0 - eslint-visitor-keys: 3.4.3 - dev: true - /@vercel/nft@0.23.1: resolution: {integrity: sha512-NE0xSmGWVhgHF1OIoir71XAd0W0C1UE3nzFyhpFiMr3rVhetww7NvM1kc41trBsPG37Bh+dE5FYCTMzM/gBu0w==} engines: {node: '>=14'} @@ -11022,17 +10769,6 @@ packages: - '@swc/helpers' dev: true - /@vitejs/plugin-react-swc@3.5.0(vite@5.2.8): - resolution: {integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==} - peerDependencies: - vite: ^4 || ^5 - dependencies: - '@swc/core': 1.4.13 - vite: 5.2.8(@types/node@20.12.7) - transitivePeerDependencies: - - '@swc/helpers' - dev: true - /@vitejs/plugin-react@3.1.0(vite@5.0.10): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -14426,7 +14162,7 @@ packages: object-assign: 4.1.1 vary: 1.1.2 - /cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.7)(cosmiconfig@8.3.6)(typescript@5.3.3): + /cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3): resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: @@ -14434,7 +14170,7 @@ packages: cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 20.12.7 + '@types/node': 18.19.31 cosmiconfig: 8.3.6(typescript@5.3.3) jiti: 1.21.0 typescript: 5.3.3 @@ -17292,14 +17028,6 @@ packages: dependencies: eslint: 7.32.0 - /eslint-plugin-react-refresh@0.4.6(eslint@7.32.0): - resolution: {integrity: sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==} - peerDependencies: - eslint: '>=7' - dependencies: - eslint: 7.32.0 - dev: true - /eslint-plugin-react@7.33.2(eslint@7.0.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} @@ -17637,6 +17365,7 @@ packages: /eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -17809,19 +17538,6 @@ packages: - utf-8-validate dev: false - /express-ws@5.0.2(express@4.19.2): - resolution: {integrity: sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==} - engines: {node: '>=4.5.0'} - peerDependencies: - express: ^4.0.0 || ^5.0.0-alpha.1 - dependencies: - express: 4.19.2 - ws: 7.5.9 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -20097,12 +19813,6 @@ packages: es-errors: 1.3.0 get-intrinsic: 1.2.4 - /get-tsconfig@4.7.3: - resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: true - /get-value@2.0.6: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} engines: {node: '>=0.10.0'} @@ -21170,19 +20880,6 @@ packages: - debug dev: false - /http-proxy-middleware@3.0.0: - resolution: {integrity: sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/http-proxy': 1.17.14 - debug: 4.3.4 - http-proxy: 1.18.1(debug@4.3.4) - is-glob: 4.0.3 - is-plain-obj: 3.0.0 - micromatch: 4.0.5 - transitivePeerDependencies: - - supports-color - /http-proxy@1.18.1: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} @@ -21203,6 +20900,7 @@ packages: requires-port: 1.0.0 transitivePeerDependencies: - debug + dev: false /http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} @@ -21979,6 +21677,7 @@ packages: /is-plain-obj@3.0.0: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} + dev: false /is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} @@ -28981,6 +28680,7 @@ packages: /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: false /reselect@4.1.8: resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} @@ -29022,10 +28722,6 @@ packages: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} dev: false - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true - /resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} dependencies: @@ -31331,15 +31027,6 @@ packages: dependencies: typescript: 5.3.3 - /ts-api-utils@1.3.0(typescript@5.4.4): - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 5.4.4 - dev: true - /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -31489,17 +31176,6 @@ packages: typescript: 5.4.4 dev: false - /tsx@4.7.2: - resolution: {integrity: sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==} - engines: {node: '>=18.0.0'} - hasBin: true - dependencies: - esbuild: 0.19.12 - get-tsconfig: 4.7.3 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -32527,27 +32203,6 @@ packages: - terser dev: true - /vite-node@1.1.1(@types/node@20.12.7): - resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.4 - pathe: 1.1.2 - picocolors: 1.0.0 - vite: 5.2.8(@types/node@20.12.7) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /vite-plugin-istanbul@3.0.4: resolution: {integrity: sha512-DJy3cq6yOFbsM3gLQf/3zeuaJNJsfBv5dLFdZdv8sUV30xLtZI+66QeYfHUyP/5vBUYyLA+xNUCSG5uHY6w+5g==} dependencies: @@ -32705,42 +32360,6 @@ packages: optionalDependencies: fsevents: 2.3.3 - /vite@5.2.8(@types/node@20.12.7): - resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 20.12.7 - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.14.1 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /vitest@0.34.6: resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} @@ -32933,7 +32552,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): + /vitest@1.1.1(@types/node@18.19.31): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -32969,7 +32588,6 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 - happy-dom: 12.10.3 local-pkg: 0.5.0 magic-string: 0.30.9 pathe: 1.1.2 @@ -32991,7 +32609,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@20.12.7): + /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -33016,7 +32634,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.12.7 + '@types/node': 18.19.31 '@vitest/expect': 1.1.1 '@vitest/runner': 1.1.1 '@vitest/snapshot': 1.1.1 @@ -33027,6 +32645,7 @@ packages: chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 + happy-dom: 12.10.3 local-pkg: 0.5.0 magic-string: 0.30.9 pathe: 1.1.2 @@ -33035,8 +32654,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.8(@types/node@20.12.7) - vite-node: 1.1.1(@types/node@20.12.7) + vite: 5.2.8(@types/node@18.19.31) + vite-node: 1.1.1(@types/node@18.19.31) why-is-node-running: 2.2.2 transitivePeerDependencies: - less From d1cda27c9ae386e33e97014b79f58969f65b7ff4 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 20:58:50 +0200 Subject: [PATCH 028/221] fix(SLB-209): inject graphql endpoint via script tag its not available during build time --- apps/preview/index.html | 1 + apps/preview/server/index.ts | 5 +++++ apps/preview/src/App.tsx | 11 +++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/preview/index.html b/apps/preview/index.html index 99e1a3605..639d1df33 100644 --- a/apps/preview/index.html +++ b/apps/preview/index.html @@ -5,6 +5,7 @@ Preview + diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index e9db6c288..7eff08ca3 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -8,6 +8,11 @@ const { app } = expressWsInstance; const updates$ = new Subject(); +app.get('/endpoint.js', (_, res) => { + res.send( + `window.GRAPHQL_ENDPOINT = "${process.env.DRUPAL_URL || 'http://localhost:8888'}/graphql";`, + ); +}); // TODO: Protect endpoints and preview with Drupal authentication. app.post('/__preview', (req, res) => { updates$.next({ body: req.body }); diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index e63dd4861..3435448e5 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -6,6 +6,12 @@ import { webSocket } from 'rxjs/webSocket'; import { drupalExecutor } from './drupal-executor'; +declare global { + interface Window { + GRAPHQL_ENDPOINT: string; + } +} + const updates$ = webSocket({ url: `${window.location.origin.replace('http', 'ws')}/__preview`, }); @@ -18,10 +24,7 @@ function App() { }, [refresh]); return ( From 3923671fcfe17d00c315d3794d15323a477b1dcb Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 20:59:16 +0200 Subject: [PATCH 029/221] fix(SLB-209): use fixed ports based on LAGOON env variable --- .lagoon/Dockerfile | 1 - apps/preview/server/index.ts | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile index 269dfbead..55590a84d 100644 --- a/.lagoon/Dockerfile +++ b/.lagoon/Dockerfile @@ -96,7 +96,6 @@ FROM uselagoon/node-18:23.12.0 as preview RUN npm install -g pnpm@8.6.0 COPY --from=builder /tmp/.deploy/preview /app -ENV PREVIEW_PORT=3000 CMD pnpm start # ==================================================================================================== diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index 7eff08ca3..eb869e45b 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -13,6 +13,7 @@ app.get('/endpoint.js', (_, res) => { `window.GRAPHQL_ENDPOINT = "${process.env.DRUPAL_URL || 'http://localhost:8888'}/graphql";`, ); }); + // TODO: Protect endpoints and preview with Drupal authentication. app.post('/__preview', (req, res) => { updates$.next({ body: req.body }); @@ -33,7 +34,9 @@ app.get('/__preview/*', (req, _, next) => { }); app.use(express.static('./dist')); -const port = process.env.PREVIEW_PORT || 8001; + +const isLagoon = !!process.env.LAGOON; +const port = isLagoon ? 3000 : 8001; console.log(`Server is running on port ${port}`); app.listen({ port }); From 1620139b6f71b3bf316e452dd1983a01bdea4044 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 21:07:06 +0200 Subject: [PATCH 030/221] fix(SLB-209): lagoon drupal url --- apps/preview/.lagoon.env | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/preview/.lagoon.env diff --git a/apps/preview/.lagoon.env b/apps/preview/.lagoon.env new file mode 100644 index 000000000..2c80dc9c1 --- /dev/null +++ b/apps/preview/.lagoon.env @@ -0,0 +1 @@ +DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" From 00a6c60a630211d4e444b9315ea086e27142ab96 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 12 Apr 2024 21:22:47 +0200 Subject: [PATCH 031/221] fix(SLB-209): make preview listen to all hosts --- apps/preview/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index eb869e45b..7b17121d1 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -39,4 +39,4 @@ const isLagoon = !!process.env.LAGOON; const port = isLagoon ? 3000 : 8001; console.log(`Server is running on port ${port}`); -app.listen({ port }); +app.listen({ port, host: '0.0.0.0' }); From 3189edc84a822f6d8ae0deec92560ea5474cd618 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sat, 13 Apr 2024 05:33:50 +0200 Subject: [PATCH 032/221] fix(SLB-209): make preview a dependency of cms so it gets properly prepared --- apps/cms/package.json | 3 +- pnpm-lock.yaml | 676 +++++++++++++++++++++++++++++++++++------- 2 files changed, 574 insertions(+), 105 deletions(-) diff --git a/apps/cms/package.json b/apps/cms/package.json index 40bcfda7c..97c65dafc 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -37,6 +37,7 @@ "@custom/gutenberg_blocks": "workspace:*", "@custom/schema": "workspace:*", "@custom/test_content": "workspace:*", - "@custom/ui": "workspace:*" + "@custom/ui": "workspace:*", + "@custom/preview": "workspace:*" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aca358f2..15cde4b6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: '@custom/gutenberg_blocks': specifier: workspace:* version: link:../../packages/drupal/gutenberg_blocks + '@custom/preview': + specifier: workspace:* + version: link:../preview '@custom/schema': specifier: workspace:* version: link:../../packages/schema @@ -184,6 +187,79 @@ importers: specifier: ^5.0.10 version: 5.0.10(@types/node@18.0.0) + apps/preview: + dependencies: + '@custom/schema': + specifier: workspace:* + version: link:../../packages/schema + '@custom/ui': + specifier: workspace:* + version: link:../../packages/ui + express: + specifier: ^4.19.2 + version: 4.19.2 + express-ws: + specifier: ^5.0.2 + version: 5.0.2(express@4.19.2) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + devDependencies: + '@swc/cli': + specifier: ^0.1.63 + version: 0.1.63(@swc/core@1.3.102) + '@swc/core': + specifier: ^1.3.102 + version: 1.3.102 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/express-ws': + specifier: ^3.0.4 + version: 3.0.4 + '@types/node': + specifier: ^20.11.17 + version: 20.11.17 + '@types/react': + specifier: ^18.2.46 + version: 18.2.46 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.18 + '@typescript-eslint/eslint-plugin': + specifier: ^7.2.0 + version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@7.0.0)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: ^7.2.0 + version: 7.2.0(eslint@7.0.0)(typescript@5.3.3) + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.5.0(vite@5.2.8) + eslint: + specifier: '7' + version: 7.0.0 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@7.0.0) + eslint-plugin-react-refresh: + specifier: ^0.4.6 + version: 0.4.6(eslint@7.0.0) + tsx: + specifier: ^4.7.1 + version: 4.7.1 + typescript: + specifier: ^5.2.2 + version: 5.3.3 + vite: + specifier: ^5.2.0 + version: 5.2.8(@types/node@20.11.17) + apps/website: dependencies: '@amazeelabs/bridge-gatsby': @@ -2993,6 +3069,7 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true + dev: true optional: true /@esbuild/aix-ppc64@0.20.0: @@ -3036,6 +3113,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm64@0.20.0: @@ -3079,6 +3157,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.20.0: @@ -3122,6 +3201,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.20.0: @@ -3165,6 +3245,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.20.0: @@ -3208,6 +3289,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.20.0: @@ -3251,6 +3333,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.20.0: @@ -3294,6 +3377,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.20.0: @@ -3337,6 +3421,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.20.0: @@ -3380,6 +3465,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.20.0: @@ -3423,6 +3509,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.20.0: @@ -3466,6 +3553,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.20.0: @@ -3509,6 +3597,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.20.0: @@ -3552,6 +3641,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.20.0: @@ -3595,6 +3685,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.20.0: @@ -3638,6 +3729,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.20.0: @@ -3681,6 +3773,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.20.0: @@ -3724,6 +3817,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.20.0: @@ -3767,6 +3861,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.20.0: @@ -3810,6 +3905,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.20.0: @@ -3853,6 +3949,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.20.0: @@ -3896,6 +3993,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.20.0: @@ -3939,6 +4037,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.20.0: @@ -5956,7 +6055,7 @@ packages: '@opentelemetry/api': 1.8.0 '@sindresorhus/slugify': 2.2.1 ansi-escapes: 6.2.1 - chalk: 5.2.0 + chalk: 5.3.0 clean-stack: 4.2.0 execa: 6.1.0 fdir: 6.1.1 @@ -6027,7 +6126,7 @@ packages: hasBin: true dependencies: '@iarna/toml': 2.2.5 - chalk: 5.2.0 + chalk: 5.3.0 cron-parser: 4.8.1 deepmerge: 4.3.1 dot-prop: 7.2.0 @@ -7147,7 +7246,7 @@ packages: react-refresh: 0.14.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /@pnpm/config.env-replace@1.1.0: resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} @@ -8847,8 +8946,8 @@ packages: '@storybook/csf': 0.1.3 '@storybook/csf-tools': 7.6.17 '@storybook/preview-api': 7.6.17 - '@swc/core': 1.3.102 - '@swc/jest': 0.2.36(@swc/core@1.3.102) + '@swc/core': 1.4.13 + '@swc/jest': 0.2.36(@swc/core@1.4.13) can-bind-to-host: 1.1.2 commander: 9.5.0 expect-playwright: 0.8.0 @@ -9191,14 +9290,14 @@ packages: legacy-swc-helpers: /@swc/helpers@0.4.14 tslib: 2.6.2 - /@swc/jest@0.2.36(@swc/core@1.3.102): + /@swc/jest@0.2.36(@swc/core@1.4.13): resolution: {integrity: sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==} engines: {npm: '>= 7.0.0'} peerDependencies: '@swc/core': '*' dependencies: '@jest/create-cache-key-function': 29.7.0 - '@swc/core': 1.3.102 + '@swc/core': 1.4.13 '@swc/counter': 0.1.3 jsonc-parser: 3.2.1 dev: true @@ -9418,7 +9517,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 18.19.31 + '@types/node': 20.11.17 dev: true /@types/cacheable-request@6.0.3: @@ -9450,7 +9549,7 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 dev: true /@types/cookie@0.4.1: @@ -9459,7 +9558,7 @@ packages: /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.19.31 /@types/cross-spawn@6.0.6: resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} @@ -9532,12 +9631,20 @@ packages: /@types/express-serve-static-core@4.19.0: resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 dev: true + /@types/express-ws@3.0.4: + resolution: {integrity: sha512-Yjj18CaivG5KndgcvzttWe8mPFinPCHJC2wvyQqVzA7hqeufM8EtWMj6mpp5omg3s8XALUexhOu8aXAyi/DyJQ==} + dependencies: + '@types/express': 4.17.21 + '@types/express-serve-static-core': 4.19.0 + '@types/ws': 8.5.10 + dev: true + /@types/express@4.17.21: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: @@ -9558,7 +9665,7 @@ packages: resolution: {integrity: sha512-rTtf75rwyP9G2qO5yRpYtdJ6aU1QqEhWbtW55qEgquEDa6bXW0s2TWZfDm02GuppjEozOWG/F2UnPq5hAQb+gw==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.0.0 + '@types/node': 18.19.31 /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -9601,7 +9708,7 @@ packages: /@types/http-proxy@1.17.14: resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.19.31 /@types/image-size@0.8.0: resolution: {integrity: sha512-hMlhu25ji75dXQk2uZkN3pTJ+lWrgKr8M1fTpyyFvuu+SJZBdGa5gDm4BVNobWXHZbOU11mBj0vciYp7qOfAFg==} @@ -9644,7 +9751,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.0.0 + '@types/node': 20.11.17 /@types/lodash@4.17.0: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} @@ -9683,7 +9790,7 @@ packages: /@types/mkdirp@0.5.2: resolution: {integrity: sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.19.31 /@types/mousetrap@1.6.15: resolution: {integrity: sha512-qL0hyIMNPow317QWW/63RvL1x5MVMV+Ru3NaY9f/CuEpCqrmb7WeuK2071ZY5hczOnm38qExWM2i2WtkXLSqFw==} @@ -9710,6 +9817,11 @@ packages: dependencies: undici-types: 5.26.5 + /@types/node@20.11.17: + resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==} + dependencies: + undici-types: 5.26.5 + /@types/node@8.10.66: resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} @@ -9796,7 +9908,7 @@ packages: /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 20.11.17 /@types/retry@0.12.1: resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==} @@ -9806,7 +9918,7 @@ packages: resolution: {integrity: sha512-YyP+VfeaqAyFmXoTh3HChxOQMyjByRMsHU7kc5KOJkSlXudhMhQIALbYV7rHh/l8d2lX3VUQzprrcAgWdRuU8g==} dependencies: '@types/glob': 5.0.38 - '@types/node': 18.0.0 + '@types/node': 18.19.31 /@types/sax@1.2.7: resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -9828,7 +9940,7 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 18.19.31 + '@types/node': 20.11.17 dev: true /@types/serve-static@1.15.5: @@ -9843,7 +9955,7 @@ packages: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 18.19.31 + '@types/node': 20.11.17 '@types/send': 0.17.4 dev: true @@ -9999,7 +10111,7 @@ packages: /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -10082,6 +10194,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} @@ -10110,7 +10223,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /@typescript-eslint/eslint-plugin@6.17.0(@typescript-eslint/parser@6.17.0)(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==} @@ -10170,6 +10282,35 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@7.0.0)(typescript@5.3.3): + resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.2.0(eslint@7.0.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/type-utils': 7.2.0(eslint@7.0.0)(typescript@5.3.3) + '@typescript-eslint/utils': 7.2.0(eslint@7.0.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.2.0 + debug: 4.3.4 + eslint: 7.0.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10226,8 +10367,6 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color - dev: false - optional: true /@typescript-eslint/parser@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==} @@ -10269,6 +10408,28 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true + + /@typescript-eslint/parser@7.2.0(eslint@7.0.0)(typescript@5.3.3): + resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.2.0 + debug: 4.3.4 + eslint: 7.0.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} @@ -10283,6 +10444,7 @@ packages: dependencies: '@typescript-eslint/types': 6.17.0 '@typescript-eslint/visitor-keys': 6.17.0 + dev: true /@typescript-eslint/scope-manager@6.21.0: resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} @@ -10292,6 +10454,14 @@ packages: '@typescript-eslint/visitor-keys': 6.21.0 dev: true + /@typescript-eslint/scope-manager@7.2.0: + resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/visitor-keys': 7.2.0 + dev: true + /@typescript-eslint/type-utils@5.62.0(eslint@7.32.0)(typescript@4.9.5): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10329,6 +10499,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/type-utils@5.62.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} @@ -10349,7 +10520,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /@typescript-eslint/type-utils@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==} @@ -10391,6 +10561,26 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils@7.2.0(eslint@7.0.0)(typescript@5.3.3): + resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.3.3) + '@typescript-eslint/utils': 7.2.0(eslint@7.0.0)(typescript@5.3.3) + debug: 4.3.4 + eslint: 7.0.0 + ts-api-utils: 1.3.0(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types@5.62.0: resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10398,12 +10588,18 @@ packages: /@typescript-eslint/types@6.17.0: resolution: {integrity: sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==} engines: {node: ^16.0.0 || >=18.0.0} + dev: true /@typescript-eslint/types@6.21.0: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} dev: true + /@typescript-eslint/types@7.2.0: + resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + /@typescript-eslint/typescript-estree@5.62.0(supports-color@9.4.0)(typescript@5.4.4): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10484,7 +10680,6 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color - dev: false /@typescript-eslint/typescript-estree@6.17.0(typescript@5.3.3): resolution: {integrity: sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==} @@ -10506,8 +10701,9 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true - /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3): + /@typescript-eslint/typescript-estree@6.21.0(typescript@5.4.4): resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -10523,6 +10719,28 @@ packages: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@7.2.0(typescript@5.3.3): + resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/visitor-keys': 7.2.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.0 ts-api-utils: 1.3.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -10566,6 +10784,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@typescript-eslint/utils@5.62.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} @@ -10586,7 +10805,6 @@ packages: - supports-color - typescript dev: false - optional: true /@typescript-eslint/utils@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==} @@ -10626,7 +10844,7 @@ packages: - typescript dev: true - /@typescript-eslint/utils@6.21.0(eslint@7.32.0)(typescript@5.3.3): + /@typescript-eslint/utils@6.21.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -10637,7 +10855,7 @@ packages: '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.4) eslint: 7.32.0 semver: 7.6.0 transitivePeerDependencies: @@ -10645,6 +10863,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@7.2.0(eslint@7.0.0)(typescript@5.3.3): + resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@7.0.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.2.0 + '@typescript-eslint/types': 7.2.0 + '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.3.3) + eslint: 7.0.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -10658,6 +10895,7 @@ packages: dependencies: '@typescript-eslint/types': 6.17.0 eslint-visitor-keys: 3.4.3 + dev: true /@typescript-eslint/visitor-keys@6.21.0: resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} @@ -10667,6 +10905,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@7.2.0: + resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 7.2.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@vercel/nft@0.23.1: resolution: {integrity: sha512-NE0xSmGWVhgHF1OIoir71XAd0W0C1UE3nzFyhpFiMr3rVhetww7NvM1kc41trBsPG37Bh+dE5FYCTMzM/gBu0w==} engines: {node: '>=14'} @@ -10769,6 +11015,17 @@ packages: - '@swc/helpers' dev: true + /@vitejs/plugin-react-swc@3.5.0(vite@5.2.8): + resolution: {integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==} + peerDependencies: + vite: ^4 || ^5 + dependencies: + '@swc/core': 1.4.13 + vite: 5.2.8(@types/node@20.11.17) + transitivePeerDependencies: + - '@swc/helpers' + dev: true + /@vitejs/plugin-react@3.1.0(vite@5.0.10): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -11477,7 +11734,7 @@ packages: /@wry/context@0.4.4: resolution: {integrity: sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.19.31 tslib: 1.14.1 /@wry/equality@0.1.11: @@ -12524,7 +12781,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /babel-plugin-add-module-exports@1.0.4: resolution: {integrity: sha512-g+8yxHUZ60RcyaUpfNzy56OtWW+x9cyEe9j+CranqLiqbju2yf/Cy6ZtYK40EZxtrdHllzlVZgLmcOUCTlJ7Jg==} @@ -12630,7 +12887,7 @@ packages: '@babel/core': 7.24.4 '@babel/runtime': 7.24.4 '@babel/types': 7.24.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 dev: false @@ -12947,7 +13204,7 @@ packages: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.0.1 + chalk: 5.3.0 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -12961,7 +13218,7 @@ packages: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.2.0 + chalk: 5.3.0 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -13338,7 +13595,6 @@ packages: /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true /change-case-all@1.0.14: resolution: {integrity: sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==} @@ -13784,7 +14040,7 @@ packages: resolution: {integrity: sha512-DP3FpjsiDDvnQC1OJBsdOJZPuy7r0o6sepY2T5M3L/d2nrE23O/ErFkEqyY3ngVL1ZhTj/H0pCMNObZGkEOaaQ==} engines: {node: '>=12.20.0'} dependencies: - chalk: 5.2.0 + chalk: 5.3.0 filter-obj: 3.0.0 is-plain-obj: 4.1.0 jest-validate: 27.5.1 @@ -13794,7 +14050,7 @@ packages: resolution: {integrity: sha512-Soe5lerRg3erMRgYC0EC696/8dMCGpBzcQchFfi55Yrkja8F+P7cUt0LVTIg7u5ob5BexLZ/F1kO+ejmv+nq8w==} engines: {node: '>=14.18.0'} dependencies: - chalk: 5.2.0 + chalk: 5.3.0 is-plain-obj: 4.1.0 dev: false @@ -14417,7 +14673,7 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.6.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /css-minimizer-webpack-plugin@2.0.0(webpack@5.91.0): resolution: {integrity: sha512-cG/uc94727tx5pBNtb1Sd7gvUPzwmcQi1lkpfqTpdkuNq75hJCw7bIVsCNijLm4dhDcr1atvuysl2rZqOG8Txw==} @@ -14439,7 +14695,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 5.0.1 source-map: 0.6.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -16206,7 +16462,7 @@ packages: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 18.0.0 + '@types/node': 18.19.31 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -16570,6 +16826,7 @@ packages: '@esbuild/win32-arm64': 0.19.12 '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + dev: true /esbuild@0.20.0: resolution: {integrity: sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==} @@ -16751,17 +17008,55 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3) - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + babel-eslint: 10.1.0(eslint@7.32.0) + confusing-browser-globals: 1.0.11 + eslint: 7.32.0 + eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.17.0)(eslint@7.32.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) + eslint-plugin-react: 7.34.1(eslint@7.32.0) + eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) + typescript: 5.3.3 + dev: true + + /eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^4.0.0 + '@typescript-eslint/parser': ^4.0.0 + babel-eslint: ^10.0.0 + eslint: ^7.5.0 + eslint-plugin-flowtype: ^5.2.0 + eslint-plugin-import: ^2.22.0 + eslint-plugin-jest: ^24.0.0 + eslint-plugin-jsx-a11y: ^6.3.1 + eslint-plugin-react: ^7.20.3 + eslint-plugin-react-hooks: ^4.0.8 + eslint-plugin-testing-library: ^3.9.0 + typescript: '*' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true + eslint-plugin-testing-library: + optional: true + typescript: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) babel-eslint: 10.1.0(eslint@7.32.0) confusing-browser-globals: 1.0.11 eslint: 7.32.0 eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.17.0)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) - typescript: 5.3.3 + typescript: 5.4.4 + dev: false /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -16772,6 +17067,34 @@ packages: transitivePeerDependencies: - supports-color + /eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + debug: 3.2.7 + eslint: 7.32.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + /eslint-module-utils@2.8.1(@typescript-eslint/parser@6.17.0)(eslint-import-resolver-node@0.3.9)(eslint@7.0.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} @@ -16828,6 +17151,7 @@ packages: eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color + dev: true /eslint-plugin-flowtype@5.10.0(eslint@7.32.0): resolution: {integrity: sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==} @@ -16848,13 +17172,13 @@ packages: '@formatjs/ts-transformer': 3.13.9 '@types/eslint': 8.56.7 '@types/picomatch': 2.3.3 - '@typescript-eslint/utils': 6.21.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@7.32.0)(typescript@5.4.4) emoji-regex: 10.3.0 eslint: 7.32.0 magic-string: 0.30.9 picomatch: 2.3.1 tslib: 2.6.2 - typescript: 5.3.3 + typescript: 5.4.4 unicode-emoji-utils: 1.2.0 transitivePeerDependencies: - supports-color @@ -16871,7 +17195,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -16880,7 +17204,7 @@ packages: doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.17.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -16963,6 +17287,7 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true /eslint-plugin-jsx-a11y@6.8.0(eslint@7.32.0): resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} @@ -17028,6 +17353,14 @@ packages: dependencies: eslint: 7.32.0 + /eslint-plugin-react-refresh@0.4.6(eslint@7.0.0): + resolution: {integrity: sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==} + peerDependencies: + eslint: '>=7' + dependencies: + eslint: 7.0.0 + dev: true + /eslint-plugin-react@7.33.2(eslint@7.0.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} @@ -17175,7 +17508,7 @@ packages: micromatch: 4.0.5 normalize-path: 3.0.0 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /eslint@7.0.0: resolution: {integrity: sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg==} @@ -17538,6 +17871,19 @@ packages: - utf-8-validate dev: false + /express-ws@5.0.2(express@4.19.2): + resolution: {integrity: sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==} + engines: {node: '>=4.5.0'} + peerDependencies: + express: ^4.0.0 || ^5.0.0-alpha.1 + dependencies: + express: 4.19.2 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -17979,7 +18325,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /file-system-cache@2.3.0: resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} @@ -18349,6 +18695,39 @@ packages: tapable: 1.1.3 typescript: 5.3.3 webpack: 5.91.0(esbuild@0.19.12) + dev: true + + /fork-ts-checker-webpack-plugin@6.5.3(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0): + resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} + engines: {node: '>=10', yarn: '>=1.0.0'} + peerDependencies: + eslint: '>= 6' + typescript: '>= 2.7' + vue-template-compiler: '*' + webpack: '>= 4' + peerDependenciesMeta: + eslint: + optional: true + vue-template-compiler: + optional: true + dependencies: + '@babel/code-frame': 7.24.2 + '@types/json-schema': 7.0.15 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 6.0.0 + deepmerge: 4.3.1 + eslint: 7.32.0 + fs-extra: 9.1.0 + glob: 7.2.3 + memfs: 3.5.3 + minimatch: 3.1.2 + schema-utils: 2.7.0 + semver: 7.6.0 + tapable: 1.1.3 + typescript: 5.4.4 + webpack: 5.91.0 + dev: false /form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} @@ -18623,7 +19002,7 @@ packages: dependencies: '@types/node-fetch': 2.6.11 fs-extra: 9.1.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) lodash: 4.17.21 node-fetch: 2.7.0 p-queue: 6.6.2 @@ -18785,7 +19164,7 @@ packages: chokidar: 3.6.0 fs-exists-cached: 1.0.0 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-page-utils: 3.13.1 gatsby-plugin-utils: 4.13.1(gatsby@5.13.3)(graphql@16.8.1) @@ -18854,7 +19233,7 @@ packages: debug: 4.3.4 filenamify: 4.3.0 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-plugin-utils: 4.13.1(gatsby@5.13.3)(graphql@16.8.1) lodash: 4.17.21 @@ -18913,7 +19292,7 @@ packages: '@babel/preset-typescript': 7.24.1(@babel/core@7.24.4) '@babel/runtime': 7.24.4 babel-plugin-remove-graphql-queries: 5.13.1(@babel/core@7.24.4)(gatsby@5.13.3) - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) transitivePeerDependencies: - supports-color dev: false @@ -18951,7 +19330,7 @@ packages: '@babel/runtime': 7.24.4 fastq: 1.17.1 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-sharp: 1.13.0 graphql: 16.8.1 @@ -19453,7 +19832,7 @@ packages: - webpack-hot-middleware - webpack-plugin-serve - /gatsby@5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3): + /gatsby@5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4): resolution: {integrity: sha512-SSnGpjswK20BQORcvTbtK8eI+W4QUG+u8rdVswB4suva6BfvTakW2wiktj7E2MdO4NjRvlgJjF5dUUncU5nldA==} engines: {node: '>=18.0.0'} hasBin: true @@ -19487,8 +19866,8 @@ packages: '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(webpack@5.91.0) '@sigmacomputing/babel-plugin-lodash': 3.3.5 '@types/http-proxy': 1.17.14 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3) - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) '@vercel/webpack-asset-relocator-loader': 1.7.3 acorn-loose: 8.4.0 acorn-walk: 8.3.2 @@ -19526,9 +19905,9 @@ packages: enhanced-resolve: 5.16.0 error-stack-parser: 2.1.4 eslint: 7.32.0 - eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.3.3) + eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.4.4) eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.17.0)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) @@ -19600,7 +19979,7 @@ packages: query-string: 6.14.1 raw-loader: 4.0.2(webpack@5.91.0) react: 18.2.0 - react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.3.3)(webpack@5.91.0) + react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0) react-dom: 18.2.0(react@18.2.0) react-refresh: 0.14.0 react-server-dom-webpack: 0.0.0-experimental-c8b778b7f-20220825(react@18.2.0)(webpack@5.91.0) @@ -19618,13 +19997,13 @@ packages: strip-ansi: 6.0.1 style-loader: 2.0.0(webpack@5.91.0) style-to-object: 0.4.4 - terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(webpack@5.91.0) tmp: 0.2.3 true-case-path: 2.2.1 type-of: 2.0.1 url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.91.0) uuid: 8.3.2 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 webpack-dev-middleware: 4.3.0(webpack@5.91.0) webpack-merge: 5.10.0 webpack-stats-plugin: 1.1.3 @@ -19813,6 +20192,12 @@ packages: es-errors: 1.3.0 get-intrinsic: 1.2.4 + /get-tsconfig@4.7.3: + resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /get-value@2.0.6: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} engines: {node: '>=0.10.0'} @@ -22531,7 +22916,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.19.31 merge-stream: 2.0.0 supports-color: 7.2.0 @@ -22539,7 +22924,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.19.31 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -23260,7 +23645,7 @@ packages: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} engines: {node: '>=12'} dependencies: - chalk: 5.2.0 + chalk: 5.3.0 is-unicode-supported: 1.3.0 dev: false @@ -24269,7 +24654,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 webpack-sources: 1.4.3 /mini-svg-data-uri@1.4.4: @@ -24299,6 +24684,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true /minimatch@9.0.4: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} @@ -25121,7 +25507,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -25410,7 +25796,7 @@ packages: resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - chalk: 5.2.0 + chalk: 5.3.0 cli-cursor: 4.0.0 cli-spinners: 2.9.2 is-interactive: 2.0.0 @@ -26376,7 +26762,7 @@ packages: klona: 2.0.6 postcss: 8.4.38 semver: 7.6.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /postcss-merge-longhand@5.1.7(postcss@8.4.38): resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} @@ -27371,7 +27757,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /rbush@3.0.1: resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} @@ -27537,6 +27923,49 @@ packages: - eslint - supports-color - vue-template-compiler + dev: true + + /react-dev-utils@12.0.1(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0): + resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=2.7' + webpack: '>=4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@babel/code-frame': 7.24.2 + address: 1.2.2 + browserslist: 4.23.0 + chalk: 4.1.2 + cross-spawn: 7.0.3 + detect-port-alt: 1.1.6 + escape-string-regexp: 4.0.0 + filesize: 8.0.7 + find-up: 5.0.0 + fork-ts-checker-webpack-plugin: 6.5.3(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0) + global-modules: 2.0.0 + globby: 11.1.0 + gzip-size: 6.0.0 + immer: 9.0.21 + is-root: 2.1.0 + loader-utils: 3.2.1 + open: 8.4.2 + pkg-up: 3.1.0 + prompts: 2.4.2 + react-error-overlay: 6.0.11 + recursive-readdir: 2.2.3 + shell-quote: 1.8.1 + strip-ansi: 6.0.1 + text-table: 0.2.0 + typescript: 5.4.4 + webpack: 5.91.0 + transitivePeerDependencies: + - eslint + - supports-color + - vue-template-compiler + dev: false /react-dnd-html5-backend@14.1.0: resolution: {integrity: sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==} @@ -27936,7 +28365,7 @@ packages: loose-envify: 1.4.0 neo-async: 2.6.2 react: 18.2.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /react-split-pane@0.1.92(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==} @@ -28722,6 +29151,10 @@ packages: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} dev: false + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} dependencies: @@ -30290,7 +30723,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /style-to-object@0.3.0: resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} @@ -30682,6 +31115,7 @@ packages: serialize-javascript: 6.0.2 terser: 5.30.3 webpack: 5.91.0(esbuild@0.19.12) + dev: true /terser-webpack-plugin@5.3.10(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -31026,6 +31460,16 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.3.3 + dev: true + + /ts-api-utils@1.3.0(typescript@5.4.4): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.4.4 + dev: true /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -31174,7 +31618,17 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.4.4 - dev: false + + /tsx@4.7.1: + resolution: {integrity: sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.19.12 + get-tsconfig: 4.7.3 + optionalDependencies: + fsevents: 2.3.3 + dev: true /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -31789,7 +32243,7 @@ packages: engines: {node: '>=14.16'} dependencies: boxen: 7.1.1 - chalk: 5.2.0 + chalk: 5.3.0 configstore: 6.0.0 has-yarn: 3.0.0 import-lazy: 4.0.0 @@ -31846,7 +32300,7 @@ packages: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} @@ -32114,30 +32568,6 @@ packages: - rollup dev: true - /vite-node@0.34.6(@types/node@18.0.0): - resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} - engines: {node: '>=v14.18.0'} - hasBin: true - requiresBuild: true - dependencies: - cac: 6.7.14 - debug: 4.3.4 - mlly: 1.6.1 - pathe: 1.1.2 - picocolors: 1.0.0 - vite: 5.2.8(@types/node@18.0.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - dev: false - optional: true - /vite-node@0.34.6(@types/node@18.19.31): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} @@ -32324,6 +32754,7 @@ packages: rollup: 4.14.1 optionalDependencies: fsevents: 2.3.3 + dev: true /vite@5.2.8(@types/node@18.19.31): resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} @@ -32360,6 +32791,42 @@ packages: optionalDependencies: fsevents: 2.3.3 + /vite@5.2.8(@types/node@20.11.17): + resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.11.17 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.14.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vitest@0.34.6: resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} @@ -32394,7 +32861,7 @@ packages: dependencies: '@types/chai': 4.3.14 '@types/chai-subset': 1.3.5 - '@types/node': 18.0.0 + '@types/node': 18.19.31 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -32413,8 +32880,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.7.0 - vite: 5.2.8(@types/node@18.0.0) - vite-node: 0.34.6(@types/node@18.0.0) + vite: 5.2.8(@types/node@18.19.31) + vite-node: 0.34.6(@types/node@18.19.31) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -32817,7 +33284,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /webpack-merge@5.10.0: resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} @@ -32964,6 +33431,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /website-scraper@5.3.1: resolution: {integrity: sha512-gogqPXD2gVsxoyd2yRiympw3rA5GuEpD1CaDEJ/J8zzanx7hkbTtneoO1SGs436PpLbWVcUge+6APGLhzsuZPA==} From b643648734d482026cf1df34f9bc20b1909e0a94 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sat, 13 Apr 2024 09:41:02 +0200 Subject: [PATCH 033/221] refactor: improve lagoon environment variables Generic default variables that work with stock hostnames and specifics for dev/stage/prod that allow custom domains. --- apps/cms/.lagoon.env | 7 +++---- apps/cms/.lagoon.env.prod | 7 +++++++ apps/cms/.lagoon.env.stage | 7 +++++++ apps/cms/lagoon.env.dev | 7 +++++++ apps/preview/.lagoon.env | 2 +- apps/preview/.lagoon.env.dev | 1 + apps/preview/.lagoon.env.prod | 1 + apps/preview/.lagoon.env.stage | 1 + apps/website/.lagoon.env | 5 ++--- apps/website/.lagoon.env.dev | 28 ++++++++++++++++++++++++++++ apps/website/.lagoon.env.prod | 28 ++++++++++++++++++++++++++++ apps/website/.lagoon.env.stage | 28 ++++++++++++++++++++++++++++ 12 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 apps/cms/.lagoon.env.prod create mode 100644 apps/cms/.lagoon.env.stage create mode 100644 apps/cms/lagoon.env.dev create mode 100644 apps/preview/.lagoon.env.dev create mode 100644 apps/preview/.lagoon.env.prod create mode 100644 apps/preview/.lagoon.env.stage create mode 100644 apps/website/.lagoon.env.dev create mode 100644 apps/website/.lagoon.env.prod create mode 100644 apps/website/.lagoon.env.stage diff --git a/apps/cms/.lagoon.env b/apps/cms/.lagoon.env index 0309eb662..3c8c78842 100644 --- a/apps/cms/.lagoon.env +++ b/apps/cms/.lagoon.env @@ -1,7 +1,6 @@ -PROJECT_NAME=example -PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" -NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" -PREVIEW_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.preview.amazeelabs.dev" +PUBLISHER_URL="https://build.${LAGOON_ENVIRONMENT}.${LAGOON_PROJECT}.ch4.amazee.io" +NETLIFY_URL="https://build.${LAGOON_ENVIRONMENT}.${LAGOON_PROJECT}.ch4.amazee.io" +PREVIEW_URL="https://preview.${LAGOON_ENVIRONMENT}.${LAGOON_PROJECT}.ch4.amazee.io" # Used to set the original client secret. PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/cms/.lagoon.env.prod b/apps/cms/.lagoon.env.prod new file mode 100644 index 000000000..0309eb662 --- /dev/null +++ b/apps/cms/.lagoon.env.prod @@ -0,0 +1,7 @@ +PROJECT_NAME=example +PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" +PREVIEW_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.preview.amazeelabs.dev" + +# Used to set the original client secret. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/cms/.lagoon.env.stage b/apps/cms/.lagoon.env.stage new file mode 100644 index 000000000..0309eb662 --- /dev/null +++ b/apps/cms/.lagoon.env.stage @@ -0,0 +1,7 @@ +PROJECT_NAME=example +PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" +PREVIEW_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.preview.amazeelabs.dev" + +# Used to set the original client secret. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/cms/lagoon.env.dev b/apps/cms/lagoon.env.dev new file mode 100644 index 000000000..0309eb662 --- /dev/null +++ b/apps/cms/lagoon.env.dev @@ -0,0 +1,7 @@ +PROJECT_NAME=example +PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" +PREVIEW_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.preview.amazeelabs.dev" + +# Used to set the original client secret. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/preview/.lagoon.env b/apps/preview/.lagoon.env index 2c80dc9c1..752e3109d 100644 --- a/apps/preview/.lagoon.env +++ b/apps/preview/.lagoon.env @@ -1 +1 @@ -DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" +DRUPAL_URL="https://nginx.${LAGOON_ENVIRONMENT}.${LAGOON_PROJECT}.ch4.amazee.io" diff --git a/apps/preview/.lagoon.env.dev b/apps/preview/.lagoon.env.dev new file mode 100644 index 000000000..2c80dc9c1 --- /dev/null +++ b/apps/preview/.lagoon.env.dev @@ -0,0 +1 @@ +DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" diff --git a/apps/preview/.lagoon.env.prod b/apps/preview/.lagoon.env.prod new file mode 100644 index 000000000..2c80dc9c1 --- /dev/null +++ b/apps/preview/.lagoon.env.prod @@ -0,0 +1 @@ +DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" diff --git a/apps/preview/.lagoon.env.stage b/apps/preview/.lagoon.env.stage new file mode 100644 index 000000000..2c80dc9c1 --- /dev/null +++ b/apps/preview/.lagoon.env.stage @@ -0,0 +1 @@ +DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" diff --git a/apps/website/.lagoon.env b/apps/website/.lagoon.env index 89379ad89..1f5bcd4b8 100644 --- a/apps/website/.lagoon.env +++ b/apps/website/.lagoon.env @@ -1,7 +1,6 @@ -PROJECT_NAME=example DRUPAL_INTERNAL_URL="http://nginx:8080" -DRUPAL_EXTERNAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" -NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" +DRUPAL_EXTERNAL_URL="https://nginx.${LAGOON_ENVIRONMENT}.${LAGOON_PROJECT}.ch4.amazee.io" +NETLIFY_URL="https://build.${LAGOON_ENVIRONMENT}.${LAGOON_PROJECT}.ch4.amazee.io" # ----------------------------------------------- # Publisher authentication with Drupal (OAuth2). diff --git a/apps/website/.lagoon.env.dev b/apps/website/.lagoon.env.dev new file mode 100644 index 000000000..89379ad89 --- /dev/null +++ b/apps/website/.lagoon.env.dev @@ -0,0 +1,28 @@ +PROJECT_NAME=example +DRUPAL_INTERNAL_URL="http://nginx:8080" +DRUPAL_EXTERNAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" + +# ----------------------------------------------- +# Publisher authentication with Drupal (OAuth2). +# See main ./README.md for more information. +# ----------------------------------------------- +# Set to true to fully skip authentication. +PUBLISHER_SKIP_AUTHENTICATION=false + +# Secret from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME + +# Client id from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_ID=publisher + +# A random string, used to encrypt the session. +PUBLISHER_OAUTH2_SESSION_SECRET=REPLACE_ME + +# "development" or "production", production will trust first proxy +# and serve secure cookies. +PUBLISHER_OAUTH2_ENVIRONMENT_TYPE=production + +# DRUPAL_EXTERNAL_URL is used by default, but can be overridden +# to match the Drupal production url, without the nginx prefix. +PUBLISHER_OAUTH2_TOKEN_HOST="${DRUPAL_EXTERNAL_URL}" diff --git a/apps/website/.lagoon.env.prod b/apps/website/.lagoon.env.prod new file mode 100644 index 000000000..89379ad89 --- /dev/null +++ b/apps/website/.lagoon.env.prod @@ -0,0 +1,28 @@ +PROJECT_NAME=example +DRUPAL_INTERNAL_URL="http://nginx:8080" +DRUPAL_EXTERNAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" + +# ----------------------------------------------- +# Publisher authentication with Drupal (OAuth2). +# See main ./README.md for more information. +# ----------------------------------------------- +# Set to true to fully skip authentication. +PUBLISHER_SKIP_AUTHENTICATION=false + +# Secret from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME + +# Client id from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_ID=publisher + +# A random string, used to encrypt the session. +PUBLISHER_OAUTH2_SESSION_SECRET=REPLACE_ME + +# "development" or "production", production will trust first proxy +# and serve secure cookies. +PUBLISHER_OAUTH2_ENVIRONMENT_TYPE=production + +# DRUPAL_EXTERNAL_URL is used by default, but can be overridden +# to match the Drupal production url, without the nginx prefix. +PUBLISHER_OAUTH2_TOKEN_HOST="${DRUPAL_EXTERNAL_URL}" diff --git a/apps/website/.lagoon.env.stage b/apps/website/.lagoon.env.stage new file mode 100644 index 000000000..89379ad89 --- /dev/null +++ b/apps/website/.lagoon.env.stage @@ -0,0 +1,28 @@ +PROJECT_NAME=example +DRUPAL_INTERNAL_URL="http://nginx:8080" +DRUPAL_EXTERNAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" +NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" + +# ----------------------------------------------- +# Publisher authentication with Drupal (OAuth2). +# See main ./README.md for more information. +# ----------------------------------------------- +# Set to true to fully skip authentication. +PUBLISHER_SKIP_AUTHENTICATION=false + +# Secret from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME + +# Client id from the Drupal Publisher Consumer. +PUBLISHER_OAUTH2_CLIENT_ID=publisher + +# A random string, used to encrypt the session. +PUBLISHER_OAUTH2_SESSION_SECRET=REPLACE_ME + +# "development" or "production", production will trust first proxy +# and serve secure cookies. +PUBLISHER_OAUTH2_ENVIRONMENT_TYPE=production + +# DRUPAL_EXTERNAL_URL is used by default, but can be overridden +# to match the Drupal production url, without the nginx prefix. +PUBLISHER_OAUTH2_TOKEN_HOST="${DRUPAL_EXTERNAL_URL}" From b50369131345a008e03cb5bd8c92a9c21dcf6db4 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sat, 13 Apr 2024 16:39:15 +0200 Subject: [PATCH 034/221] chore(SLB-209): increase autosave interval for demonstration purposes --- apps/cms/config/sync/silverback_autosave.settings.yml | 2 +- packages/drupal/silverback_autosave/js/silverback_autosave.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/cms/config/sync/silverback_autosave.settings.yml b/apps/cms/config/sync/silverback_autosave.settings.yml index 3bd7d1556..9fc203c20 100644 --- a/apps/cms/config/sync/silverback_autosave.settings.yml +++ b/apps/cms/config/sync/silverback_autosave.settings.yml @@ -1,6 +1,6 @@ _core: default_config_hash: HaTNbtQe6_A0WF-ZElHNWcv0VK_aqOj5lBEFhAgY85s -interval: 10000 +interval: 3000 only_on_form_change: false active_on: content_entity_forms: true diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js index e9c9438b3..cfebc5704 100644 --- a/packages/drupal/silverback_autosave/js/silverback_autosave.js +++ b/packages/drupal/silverback_autosave/js/silverback_autosave.js @@ -307,6 +307,8 @@ ); }, 500); } + // TODO: Implement a mechanism that takes user interaction into account. + // E.g. autosave after the user did not interact for 2 seconds. Drupal.autosaveForm.timer = setInterval(function () { if (!Drupal.ajax.instances.some(isAjaxing)) { From 7dd097f165b992fab9c7ad5359141c4ac8feb2e6 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sat, 13 Apr 2024 16:42:07 +0200 Subject: [PATCH 035/221] feat(SLB-209): trigger preview refresh on autosaves --- .../src/Storage/AutosaveEntityFormDatabaseStorage.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index 0098464b7..ae41e5c82 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -84,6 +84,9 @@ public function storeEntityAndFormState($form_id, $form_session_id, $entity_type $serialized_form_state, ]) ->execute(); + // @todo Clean up this prototype. + // @todo pass entity information so clients can filter if they have to update. + \Drupal::service('http_client')->post((getenv('PREVIEW_URL') ?? 'http://localhost:8001') . '/__preview'); } } From 004a708e933e95313e6d48ba5a18bea9ebeedd16 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sun, 14 Apr 2024 08:52:28 +0200 Subject: [PATCH 036/221] feat(SLB-209): debounce-based autosave --- .../sync/silverback_autosave.settings.yml | 2 +- .../js/silverback_autosave.js | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/cms/config/sync/silverback_autosave.settings.yml b/apps/cms/config/sync/silverback_autosave.settings.yml index 9fc203c20..c67f05571 100644 --- a/apps/cms/config/sync/silverback_autosave.settings.yml +++ b/apps/cms/config/sync/silverback_autosave.settings.yml @@ -1,6 +1,6 @@ _core: default_config_hash: HaTNbtQe6_A0WF-ZElHNWcv0VK_aqOj5lBEFhAgY85s -interval: 3000 +interval: 2000 only_on_form_change: false active_on: content_entity_forms: true diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js index cfebc5704..c4501987f 100644 --- a/packages/drupal/silverback_autosave/js/silverback_autosave.js +++ b/packages/drupal/silverback_autosave/js/silverback_autosave.js @@ -84,7 +84,7 @@ Drupal.autosaveForm.form.submit(function () { if (Drupal.autosaveForm.autosaveFormRunning) { Drupal.autosaveForm.autosaveFormRunning = false; - clearInterval(Drupal.autosaveForm.timer); + clearTimeout(Drupal.autosaveForm.timer); Drupal.autosaveForm.timer = null; } }); @@ -100,7 +100,7 @@ !Drupal.autosaveForm.autosaveFormRunning && Drupal.autosaveForm.timer ) { - clearInterval(Drupal.autosaveForm.timer); + clearTimeout(Drupal.autosaveForm.timer); Drupal.autosaveForm.timer = null; } else { return; @@ -237,7 +237,7 @@ ) { if (xmlhttprequest.status === 0 || xmlhttprequest.status >= 400) { Drupal.autosaveForm.autosaveFormRunning = false; - clearInterval(Drupal.autosaveForm.timer); + clearTimeout(Drupal.autosaveForm.timer); Drupal.autosaveForm.timer = null; if (!Drupal.autosaveForm.beforeUnloadCalled) { @@ -307,16 +307,17 @@ ); }, 500); } - // TODO: Implement a mechanism that takes user interaction into account. - // E.g. autosave after the user did not interact for 2 seconds. - Drupal.autosaveForm.timer = setInterval(function () { - if (!Drupal.ajax.instances.some(isAjaxing)) { - triggerAjaxSubmitWithoutProgressIndication( - Drupal.autosaveForm.autosave_submit_class, - ); - } - }, Drupal.autosaveForm.interval); + $('body').on('click keyup', () => { + clearTimeout(Drupal.autosaveForm.timer); + Drupal.autosaveForm.timer = setTimeout(function () { + if (!Drupal.ajax.instances.some(isAjaxing)) { + triggerAjaxSubmitWithoutProgressIndication( + Drupal.autosaveForm.autosave_submit_class, + ); + } + }, Drupal.autosaveForm.interval); + }); } } From 00743e34fc7cce3042d79d710cddf7f32c6fad1d Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Tue, 16 Apr 2024 11:04:49 +0200 Subject: [PATCH 037/221] feat(SLB-287): create BlockBackgroundImageCards component and stories --- .../BlockBackgroundImageCards.stories.tsx | 36 ++++++++++++ .../PageContent/BlockBackgroundImageCards.tsx | 56 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.stories.tsx create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx diff --git a/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.stories.tsx new file mode 100644 index 000000000..316056a66 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.stories.tsx @@ -0,0 +1,36 @@ +import { Url } from '@custom/schema'; +import Landscape from '@stories/landscape.jpg?as=metadata'; +import Portrait from '@stories/portrait.jpg?as=metadata'; +import { Meta, StoryObj } from '@storybook/react'; + +import { image } from '../../../helpers/image'; +import { BlockBackgroundImageCards } from './BlockBackgroundImageCards'; + +export default { + component: BlockBackgroundImageCards, +} satisfies Meta; + +export const Default = { + args: { + teasers: [ + { + title: 'Title', + ctaText: 'Call to action', + ctaUrl: '/test' as Url, + image: { + source: image(Landscape), + alt: 'Alt text', + }, + }, + { + title: 'Title', + ctaText: 'Call to action', + ctaUrl: '/test' as Url, + image: { + source: image(Portrait), + alt: 'Alt text', + }, + }, + ], + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx new file mode 100644 index 000000000..b8133f8d0 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx @@ -0,0 +1,56 @@ +import { + BlockImageTeaser, + BlockImageTeasers, + Image, + Link, +} from '@custom/schema'; +import React from 'react'; + +import { isTruthy } from '../../../utils/isTruthy'; + +export function BlockBackgroundImageCards(props: BlockImageTeasers) { + return ( + // eslint-disable-next-line tailwindcss/no-custom-classname +
+
+
+ {props.teasers.filter(isTruthy).map((teaser, index) => ( + + ))} +
+
+
+ ); +} + +export function BlockBackgroundImageCard(props: BlockImageTeaser) { + return ( +
+ {props.image ? ( + {props.image.alt} + ) : null} + +
+ {props.title ? ( +

+ {props.title} +

+ ) : null} + + {props.ctaUrl && props.ctaText ? ( + + {props.ctaText} + + ) : null} +
+
+ ); +} From 7c5bcff38aa009934fbc0a3630f045d3e60d58eb Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Tue, 16 Apr 2024 13:02:49 +0200 Subject: [PATCH 038/221] fix: copy iframe.css on lagoon --- .lagoon/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile index 55590a84d..214c1fdbb 100644 --- a/.lagoon/Dockerfile +++ b/.lagoon/Dockerfile @@ -83,6 +83,7 @@ FROM uselagoon/nginx-drupal as nginx COPY --from=cli /app /app RUN cd /app/web && cp ../node_modules/@custom/ui/build/gutenberg.css . +RUN cd /app/web && cp ../node_modules/@custom/ui/build/iframe.css . COPY .lagoon/nginx-conf/redirects-map.conf /etc/nginx/redirects-map.conf WORKDIR /app From 0ecf9d5e9745a60da749eb96bfea4062079047ea Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Tue, 16 Apr 2024 17:16:59 +0200 Subject: [PATCH 039/221] fix: don't set drupal external url for publisher test builds --- apps/website/publisher.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/publisher.config.ts b/apps/website/publisher.config.ts index 6c5d95797..abaf98f01 100644 --- a/apps/website/publisher.config.ts +++ b/apps/website/publisher.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ // cannot report it. // Workaround: Do a double build on the first build. 'if test -d public; then echo "Single build" && pnpm build:gatsby; else echo "Double build" && pnpm build:gatsby && pnpm build:gatsby; fi' - : 'DRUPAL_EXTERNAL_URL=http://127.0.0.1:8888 pnpm build:gatsby', + : 'pnpm build:gatsby', outputTimeout: 1000 * 60 * 10, }, clean: 'pnpm clean', From 0abbe64bf7b27c64ef8fd86e5fd37f5720f124c1 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 07:36:06 +0200 Subject: [PATCH 040/221] chore: prettier formatting --- apps/preview/index.html | 24 +++++++++++------------- apps/preview/server/index.ts | 4 +++- packages/schema/src/schema.graphql | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/preview/index.html b/apps/preview/index.html index 639d1df33..86c766277 100644 --- a/apps/preview/index.html +++ b/apps/preview/index.html @@ -1,16 +1,14 @@ + + + + Preview + + - - - - Preview - - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index 7b17121d1..46bf8cfc9 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -10,7 +10,9 @@ const updates$ = new Subject(); app.get('/endpoint.js', (_, res) => { res.send( - `window.GRAPHQL_ENDPOINT = "${process.env.DRUPAL_URL || 'http://localhost:8888'}/graphql";`, + `window.GRAPHQL_ENDPOINT = "${ + process.env.DRUPAL_URL || 'http://localhost:8888' + }/graphql";`, ); }); diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 289d4000b..d3c404ee7 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -199,7 +199,7 @@ type Hero { } union PageContent @resolveEditorBlockType = - BlockMarkup + | BlockMarkup | BlockMedia | BlockForm | BlockImageTeasers From d359edf42c534b541b9aad9155fab9abf593f626 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 07:56:05 +0200 Subject: [PATCH 041/221] chore: more prettier fixes --- packages/schema/src/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 4bb75e6f6..ab0821e57 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -199,7 +199,7 @@ type Hero { } union PageContent @resolveEditorBlockType = - | BlockMarkup + BlockMarkup | BlockMedia | BlockForm | BlockImageTeasers From 49142d16830a5b779d4d276e3521d27b49c087f2 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 08:45:04 +0200 Subject: [PATCH 042/221] fix(SLB-146): preview url fallback --- .../src/Storage/AutosaveEntityFormDatabaseStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index ae41e5c82..5334a408c 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -86,7 +86,7 @@ public function storeEntityAndFormState($form_id, $form_session_id, $entity_type ->execute(); // @todo Clean up this prototype. // @todo pass entity information so clients can filter if they have to update. - \Drupal::service('http_client')->post((getenv('PREVIEW_URL') ?? 'http://localhost:8001') . '/__preview'); + \Drupal::service('http_client')->post((getenv('PREVIEW_URL') ?: 'http://localhost:8001') . '/__preview'); } } From 22cf7dafd3f8d7969e5bd49d8deb87ffae9fba5a Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 08:45:30 +0200 Subject: [PATCH 043/221] fix(SLB-209): reconnect websocket on close --- apps/preview/src/App.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index 3435448e5..981244d51 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -2,6 +2,7 @@ import { OperationExecutor } from '@custom/schema'; import { Frame } from '@custom/ui/routes/Frame'; import { Preview, usePreviewRefresh } from '@custom/ui/routes/Preview'; import { useEffect } from 'react'; +import { retry } from 'rxjs'; import { webSocket } from 'rxjs/webSocket'; import { drupalExecutor } from './drupal-executor'; @@ -14,7 +15,11 @@ declare global { const updates$ = webSocket({ url: `${window.location.origin.replace('http', 'ws')}/__preview`, -}); +}).pipe( + retry({ + delay: 3000, + }), +); function App() { const refresh = usePreviewRefresh(); From ede54635e938fd42295320d9cbac9c8ec334a59c Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 09:31:22 +0200 Subject: [PATCH 044/221] fix(SLB-209): default preview host to loopback --- apps/preview/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index 46bf8cfc9..2f3c88d36 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -11,7 +11,7 @@ const updates$ = new Subject(); app.get('/endpoint.js', (_, res) => { res.send( `window.GRAPHQL_ENDPOINT = "${ - process.env.DRUPAL_URL || 'http://localhost:8888' + process.env.DRUPAL_URL || 'http://127.0.0.1:8888' }/graphql";`, ); }); From 085ea7e2ca5d1b243ecbc6e4a3b53f4be57556a0 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 09:31:50 +0200 Subject: [PATCH 045/221] test(SLB-209): re-enable preview integration test --- tests/e2e/specs/drupal/preview.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/drupal/preview.spec.ts b/tests/e2e/specs/drupal/preview.spec.ts index 89ae402e8..78865a978 100644 --- a/tests/e2e/specs/drupal/preview.spec.ts +++ b/tests/e2e/specs/drupal/preview.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; import { cmsUrl } from '../../helpers/url'; -test.describe.fixme('instant preview', () => { +test.describe('instant preview', () => { test.use({ storageState: '.auth/admin.json' }); test('display an unpublished page in an iframe', async ({ page }) => { await page.goto(cmsUrl('/admin/content')); From 5c531bbb5dbef658438f50c41bba61bdc2211840 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 14:47:35 +0200 Subject: [PATCH 046/221] feat(SLB-146): filter preview updates by node id --- apps/preview/server/index.ts | 3 +- .../silverback_autosave.services.yml | 7 +++- .../AutosaveEntityFormDatabaseStorage.php | 36 ++++++++++++++++--- packages/ui/src/components/Routes/Preview.tsx | 15 +++++++- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/apps/preview/server/index.ts b/apps/preview/server/index.ts index 2f3c88d36..333df04a0 100644 --- a/apps/preview/server/index.ts +++ b/apps/preview/server/index.ts @@ -7,6 +7,7 @@ const expressWsInstance = expressWs(expressServer); const { app } = expressWsInstance; const updates$ = new Subject(); +app.use(express.json()); app.get('/endpoint.js', (_, res) => { res.send( @@ -18,7 +19,7 @@ app.get('/endpoint.js', (_, res) => { // TODO: Protect endpoints and preview with Drupal authentication. app.post('/__preview', (req, res) => { - updates$.next({ body: req.body }); + updates$.next(req.body || {}); res.json(true); }); diff --git a/packages/drupal/silverback_autosave/silverback_autosave.services.yml b/packages/drupal/silverback_autosave/silverback_autosave.services.yml index b8b45034d..f09b27af4 100644 --- a/packages/drupal/silverback_autosave/silverback_autosave.services.yml +++ b/packages/drupal/silverback_autosave/silverback_autosave.services.yml @@ -1,7 +1,12 @@ services: silverback_autosave.entity_form_storage: class: \Drupal\silverback_autosave\Storage\AutosaveEntityFormDatabaseStorage - arguments: ['@database', '@serialization.phpserialize'] + arguments: [ + '@database', + '@serialization.phpserialize', + '@http_client', + '@logger.channel.default', + ] form_validator.silverback_autosave: public: false diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php index 5334a408c..d03b89365 100644 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php @@ -9,6 +9,10 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Logger\LoggerChannelInterface; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\RequestOptions; /** * A database backend for autosave of entity forms. @@ -29,6 +33,16 @@ class AutosaveEntityFormDatabaseStorage implements AutosaveEntityFormStorageInte */ protected $serializer; + /** + * @var \GuzzleHttp\ClientInterface + */ + protected $httpClient; + + /** + * @var \Drupal\Core\Logger\LoggerChannelInterface + */ + protected $logger; + /** * Constructs an AutosaveEntityStorage. * @@ -37,9 +51,11 @@ class AutosaveEntityFormDatabaseStorage implements AutosaveEntityFormStorageInte * @param \Drupal\Component\Serialization\SerializationInterface $serializer * The serializer to use. */ - public function __construct(Connection $connection, SerializationInterface $serializer) { + public function __construct(Connection $connection, SerializationInterface $serializer, ClientInterface $httpClient, LoggerChannelInterface $logger) { $this->connection = $connection; $this->serializer = $serializer; + $this->httpClient = $httpClient; + $this->logger = $logger; } /** @@ -84,9 +100,21 @@ public function storeEntityAndFormState($form_id, $form_session_id, $entity_type $serialized_form_state, ]) ->execute(); - // @todo Clean up this prototype. - // @todo pass entity information so clients can filter if they have to update. - \Drupal::service('http_client')->post((getenv('PREVIEW_URL') ?: 'http://localhost:8001') . '/__preview'); + try { + $this->httpClient->post((getenv('PREVIEW_URL') ?: 'http://localhost:8001') . '/__preview', [ + RequestOptions::HEADERS => [ + 'Content-Type' => 'application/json', + ], + RequestOptions::JSON => [ + 'entity_type_id' => $entity_type_id, + 'entity_id' => $entity_id, + 'langcode' => $langcode, + ], + ]); + } catch (GuzzleException $exc) { + $this->logger->critical('Error while to update preview.'); + $this->logger->critical($exc->getMessage()); + } } } diff --git a/packages/ui/src/components/Routes/Preview.tsx b/packages/ui/src/components/Routes/Preview.tsx index 3e6fc0e8f..61c6dcbb7 100644 --- a/packages/ui/src/components/Routes/Preview.tsx +++ b/packages/ui/src/components/Routes/Preview.tsx @@ -18,7 +18,20 @@ function usePreviewParameters(): OperationVariables< export function usePreviewRefresh() { const params = usePreviewParameters(); - return () => clear(PreviewDrupalPageQuery, params); + return (input: { + entity_type_id?: string; + entity_id?: string; + langcode?: string; + }) => { + if ( + // TODO: Extend for non-node entities? + input.entity_type_id === 'node' && + input.entity_id === params.id && + input.langcode === params.locale + ) { + clear(PreviewDrupalPageQuery, params); + } + }; } export function Preview() { From 6beb83e0e26452cbfc4adb5fb33840146e96b9a1 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 15:08:28 +0200 Subject: [PATCH 047/221] test(SLB-209): start preview server for drupal tests --- tests/e2e/playwright.config.drupal.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/playwright.config.drupal.ts b/tests/e2e/playwright.config.drupal.ts index 11295776f..5679e7450 100644 --- a/tests/e2e/playwright.config.drupal.ts +++ b/tests/e2e/playwright.config.drupal.ts @@ -22,6 +22,11 @@ export default defineConfig({ port: 8000, reuseExistingServer: !process.env.CI, }, + { + command: 'pnpm run --filter "@custom/preview" start', + port: 8001, + reuseExistingServer: !process.env.CI, + }, ], /* Run tests in files in parallel */ fullyParallel: true, From fc2b7e81b8e41db73bd2ab5b4d3a02f5ae41a8bc Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Wed, 17 Apr 2024 15:27:27 +0200 Subject: [PATCH 048/221] refactor(SLB-209): clean up gatsby preview files --- apps/website/gatsby-config.mjs | 7 +---- apps/website/src/pages/__preview/page.tsx | 11 ------- apps/website/src/preview/page.tsx | 37 ----------------------- apps/website/src/utils/preview.ts | 14 --------- 4 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 apps/website/src/pages/__preview/page.tsx delete mode 100644 apps/website/src/preview/page.tsx delete mode 100644 apps/website/src/utils/preview.ts diff --git a/apps/website/gatsby-config.mjs b/apps/website/gatsby-config.mjs index 74bc5a9fc..842076dff 100644 --- a/apps/website/gatsby-config.mjs +++ b/apps/website/gatsby-config.mjs @@ -42,17 +42,12 @@ const plugins = [ { resolve: 'gatsby-plugin-netlify', options: { - // To avoid "X-Frame-Options: DENY" and let it work in the preview - // iframe. + // To avoid "X-Frame-Options: DENY" in Drupal iframes. mergeSecurityHeaders: false, }, }, { - // TODO: Move preview to Drupal and remove this. resolve: 'gatsby-plugin-sitemap', - options: { - excludes: ['/__preview/**'], - }, }, { resolve: 'gatsby-plugin-robots-txt', diff --git a/apps/website/src/pages/__preview/page.tsx b/apps/website/src/pages/__preview/page.tsx deleted file mode 100644 index 9ca14376a..000000000 --- a/apps/website/src/pages/__preview/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -import PagePreview from '../../preview/page'; - -export function Head() { - return Page preview; -} - -export default function PagePreviewTemplate() { - return ; -} diff --git a/apps/website/src/preview/page.tsx b/apps/website/src/preview/page.tsx deleted file mode 100644 index dc2a1dac5..000000000 --- a/apps/website/src/preview/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { - OperationExecutor, - PreviewDrupalPageQuery, - ViewPageQuery, -} from '@custom/schema'; -import { Page } from '@custom/ui/routes/Page'; -import React from 'react'; - -import { drupalExecutor } from '../utils/drupal-executor'; -import { usePreviewParameters } from '../utils/preview'; - -const previewExecutor = drupalExecutor( - `${process.env.GATSBY_DRUPAL_URL}/graphql`, - false, -); - -export default function PagePreview() { - const { nid, rid, lang } = usePreviewParameters(); - if (nid && rid && lang) { - return ( - { - const data = await previewExecutor(PreviewDrupalPageQuery, { - id: nid, - locale: lang, - rid, - }); - return { page: data.preview }; - }} - > - - - ); - } - return null; -} diff --git a/apps/website/src/utils/preview.ts b/apps/website/src/utils/preview.ts deleted file mode 100644 index a6ee02d5d..000000000 --- a/apps/website/src/utils/preview.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useEffect, useState } from 'react'; - -export function usePreviewParameters() { - const [search, setState] = useState(''); - useEffect(() => { - setState(window.location.search); - }, [setState]); - - const args = new URLSearchParams(search); - const nid = args.get('nid'); - const rid = args.get('rid'); - const lang = args.get('lang'); - return { nid, rid, lang }; -} From e9317a6279ccc06fc0f42071da9b171bc6cc87a5 Mon Sep 17 00:00:00 2001 From: Dan Lemon Date: Wed, 17 Apr 2024 18:25:49 +0200 Subject: [PATCH 049/221] fix: spelling gutenberg --- packages/drupal/silverback_autosave/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drupal/silverback_autosave/README.md b/packages/drupal/silverback_autosave/README.md index 625f34115..73b5da62f 100644 --- a/packages/drupal/silverback_autosave/README.md +++ b/packages/drupal/silverback_autosave/README.md @@ -1,6 +1,6 @@ ## Silverback autosave -This module provides an autosave functionality on Guteneberg enabled nodes. +This module provides an autosave functionality on Gutenberg enabled nodes. On node edit, the content under edit is automatically saved for use in real time preview. From 6a0533f524eca00be640fa90cc0d60a5d2923922 Mon Sep 17 00:00:00 2001 From: Dan Lemon Date: Wed, 17 Apr 2024 18:26:06 +0200 Subject: [PATCH 050/221] fix: spelling losing --- packages/drupal/silverback_autosave/silverback_autosave.module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drupal/silverback_autosave/silverback_autosave.module b/packages/drupal/silverback_autosave/silverback_autosave.module index 1f1afe85b..7cc29d715 100644 --- a/packages/drupal/silverback_autosave/silverback_autosave.module +++ b/packages/drupal/silverback_autosave/silverback_autosave.module @@ -122,7 +122,7 @@ function silverback_autosave_entity_update(EntityInterface $entity) { * * If the permissions of a user are changed, then we delete the autosave states * belonging to that user. It is possible that new permissions the user is - * loosing the ability to access certain fields, but the autosave states contain + * losing the ability to access certain fields, but the autosave states contain * data for them. */ function silverback_autosave_user_update(EntityInterface $user) { From 5f076d80e8237831c163044f3d4f83678d538e00 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Thu, 18 Apr 2024 09:08:41 +0200 Subject: [PATCH 051/221] feat(SLB-146): upgrade amazeelabs/silverback_gutenberg to avoid preview-button ambiguity --- apps/cms/composer.lock | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index 35ca468ce..ef2a0aa18 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -495,16 +495,16 @@ }, { "name": "amazeelabs/silverback_gutenberg", - "version": "2.5.12", + "version": "2.5.15", "source": { "type": "git", "url": "https://github.com/AmazeeLabs/silverback_gutenberg.git", - "reference": "ccd955f455a6cd55ee8ce3063876e0d707b816a0" + "reference": "413248bb07ca02405bf84f01dfd55b86f121c97c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AmazeeLabs/silverback_gutenberg/zipball/ccd955f455a6cd55ee8ce3063876e0d707b816a0", - "reference": "ccd955f455a6cd55ee8ce3063876e0d707b816a0", + "url": "https://api.github.com/repos/AmazeeLabs/silverback_gutenberg/zipball/413248bb07ca02405bf84f01dfd55b86f121c97c", + "reference": "413248bb07ca02405bf84f01dfd55b86f121c97c", "shasum": "" }, "require": { @@ -519,9 +519,9 @@ "homepage": "https://github.com/AmazeeLabs/silverback-mono/tree/development/packages/composer/amazeelabs/silverback_gutenberg#readme", "support": { "issues": "https://github.com/AmazeeLabs/silverback_gutenberg/issues", - "source": "https://github.com/AmazeeLabs/silverback_gutenberg/tree/2.5.12" + "source": "https://github.com/AmazeeLabs/silverback_gutenberg/tree/2.5.15" }, - "time": "2024-02-20T11:44:25+00:00" + "time": "2024-04-18T06:58:24+00:00" }, { "name": "amazeelabs/silverback_iframe", @@ -15789,5 +15789,8 @@ "php": "^8.2 <8.3" }, "platform-dev": [], + "platform-overrides": { + "php": "8.2" + }, "plugin-api-version": "2.6.0" } From 66e05c49025622979b402720afdd3a1fec581d04 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Mon, 22 Apr 2024 08:54:11 +0300 Subject: [PATCH 052/221] feat(slb-146): add missing patch extension --- apps/cms/.idea/.gitignore | 8 + apps/cms/.idea/cms.iml | 273 ++++++ apps/cms/.idea/codeStyles/Project.xml | 140 ++++ apps/cms/.idea/codeStyles/codeStyleConfig.xml | 5 + .../Drush_4_12_24__2_49_PM.xml | 784 ++++++++++++++++++ .../frameworkDescriptionVersion1.1.4.xsd | 47 ++ .../inspectionProfiles/Project_Default.xml | 6 + apps/cms/.idea/modules.xml | 8 + apps/cms/.idea/php.xml | 264 ++++++ apps/cms/.idea/vcs.xml | 6 + apps/cms/composer.json | 2 +- ...{fetch-entity-patch => fetch-entity.patch} | 0 12 files changed, 1542 insertions(+), 1 deletion(-) create mode 100644 apps/cms/.idea/.gitignore create mode 100644 apps/cms/.idea/cms.iml create mode 100644 apps/cms/.idea/codeStyles/Project.xml create mode 100644 apps/cms/.idea/codeStyles/codeStyleConfig.xml create mode 100644 apps/cms/.idea/commandlinetools/Drush_4_12_24__2_49_PM.xml create mode 100644 apps/cms/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd create mode 100644 apps/cms/.idea/inspectionProfiles/Project_Default.xml create mode 100644 apps/cms/.idea/modules.xml create mode 100644 apps/cms/.idea/php.xml create mode 100644 apps/cms/.idea/vcs.xml rename apps/cms/patches/{fetch-entity-patch => fetch-entity.patch} (100%) diff --git a/apps/cms/.idea/.gitignore b/apps/cms/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/apps/cms/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/apps/cms/.idea/cms.iml b/apps/cms/.idea/cms.iml new file mode 100644 index 000000000..41df93773 --- /dev/null +++ b/apps/cms/.idea/cms.iml @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/cms/.idea/codeStyles/Project.xml b/apps/cms/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..a1863d2ed --- /dev/null +++ b/apps/cms/.idea/codeStyles/Project.xml @@ -0,0 +1,140 @@ + + + + + \ No newline at end of file diff --git a/apps/cms/.idea/codeStyles/codeStyleConfig.xml b/apps/cms/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/apps/cms/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/apps/cms/.idea/commandlinetools/Drush_4_12_24__2_49_PM.xml b/apps/cms/.idea/commandlinetools/Drush_4_12_24__2_49_PM.xml new file mode 100644 index 000000000..0f81233af --- /dev/null +++ b/apps/cms/.idea/commandlinetools/Drush_4_12_24__2_49_PM.xml @@ -0,0 +1,784 @@ + + + + _complete + Internal command to provide shell completion suggestions

No arguments
The shell type ("bash", "fish", "zsh")An array of input tokens (e.g. COMP_WORDS or argv)The index of the "input" array that the cursor is in (e.g. COMP_CWORD)The API version of the completion scriptdeprecatedDisplay help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
shell
input
current
api-version
symfony
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + browse + Display a link to a given path or open link in a browser.
Path to open. If omitted, the site front page will be opened.
Arguments:
path

Open the URL in the default browser. Use --no-browser to avoid opening a browser.The port that the web server is redirected to (e.g. when running within a Vagrant environment).Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
browser
redirect-port
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ path[=null] +
+ + completion + Dump the shell completion script
The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given
Arguments:
shell

Tail the completion debug logDisplay help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
debug
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ shell[=null] +
+ + deploy + Run several commands after performing a code deployment.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + help + Display usage details for a command.
A command name
Arguments:
command_name

Format the result data. Available formats: csv,json,list,null,php,print-r,string,tsv,var_dump,var_export,xml,yamlDisplay help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
include-field-labels
table-style
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ command_name[=null] +
+ + list + List available commands.

No arguments
Show a simple table of command names and descriptions.Restrict command list to those commands defined in the specified file. Omit value to choose from a list of names.Notify upon command completion. If set to a number, commands that finish in fewer seconds won't notify.Shows the druplicon as glorious ASCII art.URL to your XHProf report site.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
raw
filter
notify
druplicon
xh-link
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + runserver + Runs PHP's built-in http server for development.
IP address and port number to bind to and path to open in web browser. Format is addr:port/path. Only opens a browser if a path is specified.
Arguments:
uri

A default addr:port/path to use for any values not specified as an argument.Open the URL in the default browser. Use --no-browser to avoid opening a browser.Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
default-server
browser
dns
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ uri[=null] +
+ + rs + Runs PHP's built-in http server for development.
IP address and port number to bind to and path to open in web browser. Format is addr:port/path. Only opens a browser if a path is specified.
Arguments:
uri

A default addr:port/path to use for any values not specified as an argument.Open the URL in the default browser. Use --no-browser to avoid opening a browser.Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
default-server
browser
dns
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ uri[=null] +
+ + serve + Runs PHP's built-in http server for development.
IP address and port number to bind to and path to open in web browser. Format is addr:port/path. Only opens a browser if a path is specified.
Arguments:
uri

A default addr:port/path to use for any values not specified as an argument.Open the URL in the default browser. Use --no-browser to avoid opening a browser.Resolve hostnames/IPs using DNS/rDNS (if possible) to determine binding IPs and/or human friendly hostnames for URLs and browser.Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
default-server
browser
dns
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ uri[=null] +
+ + updatedb + Apply any database updates required (as with running update.php).

No arguments
Clear caches upon completion.Negate --cache-clear option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cache-clear
no-cache-clear
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + updb + Apply any database updates required (as with running update.php).

No arguments
Clear caches upon completion.Negate --cache-clear option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cache-clear
no-cache-clear
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + version + Show Drush version.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Drush version (drush-version)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + archive:dump + Backup your code, files, and database into a single file.

No arguments
Archive codebase.Archive Drupal files.Archive database SQL dump.The full path and filename in which the archive should be stored. Any relative path will be calculated from Drupal root (usually web for drupal/recommended-project projects). If omitted, it will be saved to the configured temp directory.Overwrite destination file if exists.Describe the archive contents.Add tags to the archive manifest. Delimit several by commas.The generator name to store in the MANIFEST.yml file. The default is "Drush archive-dump".The generator version number to store in the MANIFEST file. The default is Drush version.Comma-separated list of paths (or regular expressions matching paths) to exclude from the code archive.Add custom arguments/options to the dumping of the database (e.g. mysqldump command).A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
code
files
db
destination
overwrite
description
tags
generator
generatorversion
exclude-code-paths
extra-dump
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + ard + Backup your code, files, and database into a single file.

No arguments
Archive codebase.Archive Drupal files.Archive database SQL dump.The full path and filename in which the archive should be stored. Any relative path will be calculated from Drupal root (usually web for drupal/recommended-project projects). If omitted, it will be saved to the configured temp directory.Overwrite destination file if exists.Describe the archive contents.Add tags to the archive manifest. Delimit several by commas.The generator name to store in the MANIFEST.yml file. The default is "Drush archive-dump".The generator version number to store in the MANIFEST file. The default is Drush version.Comma-separated list of paths (or regular expressions matching paths) to exclude from the code archive.Add custom arguments/options to the dumping of the database (e.g. mysqldump command).A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
code
files
db
destination
overwrite
description
tags
generator
generatorversion
exclude-code-paths
extra-dump
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + archive:restore + Restore (import) your code, files, and database.
The full path to a single archive file (*.tar.gz) or a directory with components to import. * May contain the following components generated by `archive:dump` command: * 1) code ("code" directory); * 2) database dump file ("database/database.sql" file); * 3) Drupal files ("files" directory).Destination site alias. Defaults to @self.
Arguments:
path
site

The base path to restore the code/files into.Overwrite files if exists when un-compressing an archive.Site subdirectory to put settings.local.php into.Sets up the database connection in settings.local.php file if either --db-url option or set of specific --db-* options are provided.Import code.Import code from specified directory. Has higher priority over "path" argument.Import Drupal files.Import Drupal files from specified directory. Has higher priority over "path" argument.Import Drupal files into specified directory relative to Composer root.Import database.Import database from specified dump file. Has higher priority over "path" argument.Destination database driver.Destination database port.Destination database host.Destination database name.Destination database user.Destination database user password.Destination database prefix.A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Negate --setup-database-connection option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
destination-path
overwrite
site-subdir
setup-database-connection
code
code-source-path
files
files-source-path
files-destination-relative-path
db
db-source-path
db-driver
db-port
db-host
db-name
db-user
db-password
db-prefix
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
database
db-url
target
show-passwords
no-setup-database-connection
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ path[=null] site[=null] +
+ + arr + Restore (import) your code, files, and database.
The full path to a single archive file (*.tar.gz) or a directory with components to import. * May contain the following components generated by `archive:dump` command: * 1) code ("code" directory); * 2) database dump file ("database/database.sql" file); * 3) Drupal files ("files" directory).Destination site alias. Defaults to @self.
Arguments:
path
site

The base path to restore the code/files into.Overwrite files if exists when un-compressing an archive.Site subdirectory to put settings.local.php into.Sets up the database connection in settings.local.php file if either --db-url option or set of specific --db-* options are provided.Import code.Import code from specified directory. Has higher priority over "path" argument.Import Drupal files.Import Drupal files from specified directory. Has higher priority over "path" argument.Import Drupal files into specified directory relative to Composer root.Import database.Import database from specified dump file. Has higher priority over "path" argument.Destination database driver.Destination database port.Destination database host.Destination database name.Destination database user.Destination database user password.Destination database prefix.A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Negate --setup-database-connection option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
destination-path
overwrite
site-subdir
setup-database-connection
code
code-source-path
files
files-source-path
files-destination-relative-path
db
db-source-path
db-driver
db-port
db-host
db-name
db-user
db-password
db-prefix
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
database
db-url
target
show-passwords
no-setup-database-connection
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ path[=null] site[=null] +
+ + batch:process + Process operations in the specified batch set.
The batch id that will be processed.
Arguments:
batch_id

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ batch_id +
+ + batch-process + Process operations in the specified batch set.
The batch id that will be processed.
Arguments:
batch_id

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ batch_id +
+ + cache:rebuild + Rebuild all caches.

No arguments
Set to 0 to suppress normal cache clearing; the caller should then clear if needed.Negate --cache-clear option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cache-clear
no-cache-clear
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + cr + Rebuild all caches.

No arguments
Set to 0 to suppress normal cache clearing; the caller should then clear if needed.Negate --cache-clear option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cache-clear
no-cache-clear
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + rebuild + Rebuild all caches.

No arguments
Set to 0 to suppress normal cache clearing; the caller should then clear if needed.Negate --cache-clear option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cache-clear
no-cache-clear
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + cache-rebuild + Rebuild all caches.

No arguments
Set to 0 to suppress normal cache clearing; the caller should then clear if needed.Negate --cache-clear option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cache-clear
no-cache-clear
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + config:pull + Export and transfer config from one environment to another.
A site-alias or the name of a subdirectory within /sites whose config you want to copy from.A site-alias or the name of a subdirectory within /sites whose config you want to replace.
Arguments:
source
destination

Validate that there are no git uncommitted changes before proceedingWhere to run the rsync command; defaults to the local site. Can also be source or destination.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Path (path)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
safe
runner
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source destination +
+ + cpull + Export and transfer config from one environment to another.
A site-alias or the name of a subdirectory within /sites whose config you want to copy from.A site-alias or the name of a subdirectory within /sites whose config you want to replace.
Arguments:
source
destination

Validate that there are no git uncommitted changes before proceedingWhere to run the rsync command; defaults to the local site. Can also be source or destination.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Path (path)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
safe
runner
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source destination +
+ + config-pull + Export and transfer config from one environment to another.
A site-alias or the name of a subdirectory within /sites whose config you want to copy from.A site-alias or the name of a subdirectory within /sites whose config you want to replace.
Arguments:
source
destination

Validate that there are no git uncommitted changes before proceedingWhere to run the rsync command; defaults to the local site. Can also be source or destination.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Path (path)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
safe
runner
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source destination +
+ + core:edit + Edit drush.yml, site alias, and Drupal settings.php files.
A substring for filtering the list of files. Omit this argument to choose from loaded files.
Arguments:
filter

A string of bash which launches user's preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.Launch editor in background process.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
editor
bg
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filter[=null] +
+ + conf + Edit drush.yml, site alias, and Drupal settings.php files.
A substring for filtering the list of files. Omit this argument to choose from loaded files.
Arguments:
filter

A string of bash which launches user's preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.Launch editor in background process.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
editor
bg
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filter[=null] +
+ + config + Edit drush.yml, site alias, and Drupal settings.php files.
A substring for filtering the list of files. Omit this argument to choose from loaded files.
Arguments:
filter

A string of bash which launches user's preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.Launch editor in background process.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
editor
bg
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filter[=null] +
+ + core-edit + Edit drush.yml, site alias, and Drupal settings.php files.
A substring for filtering the list of files. Omit this argument to choose from loaded files.
Arguments:
filter

A string of bash which launches user's preferred text editor. Defaults to ${VISUAL-${EDITOR-vi}}.Launch editor in background process.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
editor
bg
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filter[=null] +
+ + core:global-options + All global options.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Name (name), Description (description)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + core-global-options + All global options.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Name (name), Description (description)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + core:rsync + Rsync Drupal code or files to/from another server using ssh.
A site alias and optional path. See rsync documentation and [Site aliases](../site-aliases.md).A site alias and optional path. See rsync documentation and [Site aliases](../site-aliases.md).Additional parameters after the ssh statement.
Arguments:
source
target
extra

List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).List of paths to include, seperated by : (Unix-based systems) or ; (Windows).The unary flags to pass to rsync; --mode=rultz implies rsync -rultz. Default is -akz.A string appended to ssh command during rsync, sql-sync, etc.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
exclude-paths
include-paths
mode
ssh-options
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source target extra[=null] +
+ + rsync + Rsync Drupal code or files to/from another server using ssh.
A site alias and optional path. See rsync documentation and [Site aliases](../site-aliases.md).A site alias and optional path. See rsync documentation and [Site aliases](../site-aliases.md).Additional parameters after the ssh statement.
Arguments:
source
target
extra

List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).List of paths to include, seperated by : (Unix-based systems) or ; (Windows).The unary flags to pass to rsync; --mode=rultz implies rsync -rultz. Default is -akz.A string appended to ssh command during rsync, sql-sync, etc.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
exclude-paths
include-paths
mode
ssh-options
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source target extra[=null] +
+ + core-rsync + Rsync Drupal code or files to/from another server using ssh.
A site alias and optional path. See rsync documentation and [Site aliases](../site-aliases.md).A site alias and optional path. See rsync documentation and [Site aliases](../site-aliases.md).Additional parameters after the ssh statement.
Arguments:
source
target
extra

List of paths to exclude, seperated by : (Unix-based systems) or ; (Windows).List of paths to include, seperated by : (Unix-based systems) or ; (Windows).The unary flags to pass to rsync; --mode=rultz implies rsync -rultz. Default is -akz.A string appended to ssh command during rsync, sql-sync, etc.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
exclude-paths
include-paths
mode
ssh-options
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source target extra[=null] +
+ + core:status + An overview of the environment - Drush and Drupal.

No arguments
A comma delimited list of projects. Their paths will be added to path-aliases section.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Drupal version (drupal-version), Site URI (uri), DB driver (db-driver), DB hostname (db-hostname), DB port (db-port), DB username (db-username), DB password (db-password), DB name (db-name), Database (db-status), Drupal bootstrap (bootstrap), Default theme (theme), Admin theme (admin-theme), PHP binary (php-bin), PHP config (php-conf), PHP OS (php-os), PHP version (php-version), Drush script (drush-script), Drush version (drush-version), Drush temp (drush-temp), Drush configs (drush-conf), Drush aliases (drush-alias-files), Alias search paths (alias-searchpaths), Install profile (install-profile), Drupal root (root), Drupal Settings (drupal-settings-file), Site path (site-path), Site path (site), Themes path (themes), Modules path (modules), Files, Public (files), Files, Private (private), Files, Temp (temp), Drupal config (config-sync), Files, Public (files-path), Files, Temp (temp-path), Other paths (%paths)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
project
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + status + An overview of the environment - Drush and Drupal.

No arguments
A comma delimited list of projects. Their paths will be added to path-aliases section.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Drupal version (drupal-version), Site URI (uri), DB driver (db-driver), DB hostname (db-hostname), DB port (db-port), DB username (db-username), DB password (db-password), DB name (db-name), Database (db-status), Drupal bootstrap (bootstrap), Default theme (theme), Admin theme (admin-theme), PHP binary (php-bin), PHP config (php-conf), PHP OS (php-os), PHP version (php-version), Drush script (drush-script), Drush version (drush-version), Drush temp (drush-temp), Drush configs (drush-conf), Drush aliases (drush-alias-files), Alias search paths (alias-searchpaths), Install profile (install-profile), Drupal root (root), Drupal Settings (drupal-settings-file), Site path (site-path), Site path (site), Themes path (themes), Modules path (modules), Files, Public (files), Files, Private (private), Files, Temp (temp), Drupal config (config-sync), Files, Public (files-path), Files, Temp (temp-path), Other paths (%paths)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
project
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + st + An overview of the environment - Drush and Drupal.

No arguments
A comma delimited list of projects. Their paths will be added to path-aliases section.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Drupal version (drupal-version), Site URI (uri), DB driver (db-driver), DB hostname (db-hostname), DB port (db-port), DB username (db-username), DB password (db-password), DB name (db-name), Database (db-status), Drupal bootstrap (bootstrap), Default theme (theme), Admin theme (admin-theme), PHP binary (php-bin), PHP config (php-conf), PHP OS (php-os), PHP version (php-version), Drush script (drush-script), Drush version (drush-version), Drush temp (drush-temp), Drush configs (drush-conf), Drush aliases (drush-alias-files), Alias search paths (alias-searchpaths), Install profile (install-profile), Drupal root (root), Drupal Settings (drupal-settings-file), Site path (site-path), Site path (site), Themes path (themes), Modules path (modules), Files, Public (files), Files, Private (private), Files, Temp (temp), Drupal config (config-sync), Files, Public (files-path), Files, Temp (temp-path), Other paths (%paths)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
project
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + core-status + An overview of the environment - Drush and Drupal.

No arguments
A comma delimited list of projects. Their paths will be added to path-aliases section.Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Drupal version (drupal-version), Site URI (uri), DB driver (db-driver), DB hostname (db-hostname), DB port (db-port), DB username (db-username), DB password (db-password), DB name (db-name), Database (db-status), Drupal bootstrap (bootstrap), Default theme (theme), Admin theme (admin-theme), PHP binary (php-bin), PHP config (php-conf), PHP OS (php-os), PHP version (php-version), Drush script (drush-script), Drush version (drush-version), Drush temp (drush-temp), Drush configs (drush-conf), Drush aliases (drush-alias-files), Alias search paths (alias-searchpaths), Install profile (install-profile), Drupal root (root), Drupal Settings (drupal-settings-file), Site path (site-path), Site path (site), Themes path (themes), Modules path (modules), Files, Public (files), Files, Private (private), Files, Temp (temp), Drupal config (config-sync), Files, Public (files-path), Files, Temp (temp-path), Other paths (%paths)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
project
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + core:topic + Read detailed documentation on a given topic.
The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).
Arguments:
topic_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ topic_name +
+ + topic + Read detailed documentation on a given topic.
The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).
Arguments:
topic_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ topic_name +
+ + core-topic + Read detailed documentation on a given topic.
The name of the topic you wish to view. If omitted, list all topic descriptions (and names in parenthesis).
Arguments:
topic_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ topic_name +
+ + deploy:batch-process + Process operations in the specified batch set.
The batch id that will be processed.
Arguments:
batch_id

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ batch_id +
+ + deploy:hook + Run pending deploy update hooks.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + deploy:hook-status + Prints information about pending deploy update hooks.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Module (module), Hook (hook), Description (description)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + deploy:mark-complete + Mark all deploy hooks as having run.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:aliases + Creating site aliases for running Drush on remote sites.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-aliases + Creating site aliases for running Drush on remote sites.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:bisect + Drush docs for Git Bisect.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-bisect + Drush docs for Git Bisect.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:bootstrap + Bootstrap explanation: how Drush starts up and prepares the Drupal environment.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-bootstrap + Bootstrap explanation: how Drush starts up and prepares the Drupal environment.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:commands + Instructions on creating your own Drush commands.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-commands + Instructions on creating your own Drush commands.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:config:exporting + Drupal config export instructions, including customizing config by environment.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-config-exporting + Drupal config export instructions, including customizing config by environment.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:configuration + Drush configuration.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-configuration + Drush configuration.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:cron + Crontab instructions for running your Drupal cron tasks via `drush cron`.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-cron + Crontab instructions for running your Drupal cron tasks via `drush cron`.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:deploy + Deploy command for Drupal.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-deploy + Deploy command for Drupal.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:example-sync-via-http + Extend sql-sync to allow transfer of the sql dump file via http.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-example-sync-via-http + Extend sql-sync to allow transfer of the sql dump file via http.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:examplecommand + Example Drush command file.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-examplecommand + Example Drush command file.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:generators + Instructions on creating your own Drush Generators.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-generators + Instructions on creating your own Drush Generators.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:hooks + Drush hooks.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-hooks + Drush hooks.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:migrate + Defining and running migrations.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:output-formats-filters + Output formatters and filters: control the command output

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:output + Output formatters and filters: control the command output

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:policy + Example policy file.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-policy + Example policy file.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:readme + README.md

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-readme + README.md

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs:script + An example Drush script.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + docs-script + An example Drush script.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + drupal:directory + Return the filesystem path for modules/themes and other key folders.
A module/theme name, or special names like root, files, private, or an alias:path string such as @alias:%files.
Arguments:
target

Reject any target that specifies a remote site.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
local-only
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ target[=null] +
+ + dd + Return the filesystem path for modules/themes and other key folders.
A module/theme name, or special names like root, files, private, or an alias:path string such as @alias:%files.
Arguments:
target

Reject any target that specifies a remote site.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
local-only
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ target[=null] +
+ + drupal-directory + Return the filesystem path for modules/themes and other key folders.
A module/theme name, or special names like root, files, private, or an alias:path string such as @alias:%files.
Arguments:
target

Reject any target that specifies a remote site.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
local-only
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ target[=null] +
+ + example:table + Demonstrate output formatters. Default format is 'table'.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: I (first), II (second), III (third)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + tf + Demonstrate output formatters. Default format is 'table'.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: I (first), II (second), III (third)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + image:derive + Create an image derivative.
An image style machine name.Path to a source image. Optionally prepend stream wrapper scheme. Relative paths calculated from Drupal root.
Arguments:
style_name
source

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ style_name source +
+ + id + Create an image derivative.
An image style machine name.Path to a source image. Optionally prepend stream wrapper scheme. Relative paths calculated from Drupal root.
Arguments:
style_name
source

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ style_name source +
+ + image-derive + Create an image derivative.
An image style machine name.Path to a source image. Optionally prepend stream wrapper scheme. Relative paths calculated from Drupal root.
Arguments:
style_name
source

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ style_name source +
+ + image:flush + Flush all derived images for a given style.
A comma delimited list of image style machine names. If not provided, user may choose from a list of names.
Arguments:
style_names

Flush all derived imagesDisplay help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
all
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ style_names +
+ + if + Flush all derived images for a given style.
A comma delimited list of image style machine names. If not provided, user may choose from a list of names.
Arguments:
style_names

Flush all derived imagesDisplay help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
all
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ style_names +
+ + image-flush + Flush all derived images for a given style.
A comma delimited list of image style machine names. If not provided, user may choose from a list of names.
Arguments:
style_names

Flush all derived imagesDisplay help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
all
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ style_names +
+ + jn:get + Execute a JSONAPI request.
The JSONAPI URL to request.
Arguments:
url

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ url +
+ + lagoon:aliases + Get all remote aliases from lagoon API.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + la + Get all remote aliases from lagoon API.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + lagoon:jwt + Generate a JWT token for the lagoon API.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + jwt + Generate a JWT token for the lagoon API.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + lagoon:post-rollout-tasks + Run post-rollout tasks.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + lagoon:pre-rollout-tasks + Run pre-rollout tasks.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + php:eval + Evaluate arbitrary php code after bootstrapping Drupal (if available).
PHP code. If shell escaping gets too tedious, consider using the php:script command.
Arguments:
code

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code +
+ + eval + Evaluate arbitrary php code after bootstrapping Drupal (if available).
PHP code. If shell escaping gets too tedious, consider using the php:script command.
Arguments:
code

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code +
+ + ev + Evaluate arbitrary php code after bootstrapping Drupal (if available).
PHP code. If shell escaping gets too tedious, consider using the php:script command.
Arguments:
code

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code +
+ + php-eval + Evaluate arbitrary php code after bootstrapping Drupal (if available).
PHP code. If shell escaping gets too tedious, consider using the php:script command.
Arguments:
code

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code +
+ + php:script + Run php a script after a full Drupal bootstrap.
Arguments:
extra

Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
script-path
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ extra[=null] +
+ + scr + Run php a script after a full Drupal bootstrap.
Arguments:
extra

Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
script-path
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ extra[=null] +
+ + php-script + Run php a script after a full Drupal bootstrap.
Arguments:
extra

Additional paths to search for scripts, separated by : (Unix-based systems) or ; (Windows).Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
script-path
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ extra[=null] +
+ + pm:security + pm:security has been removed. Please use `composer audit` command. See https://www.drupal.org/project/project_composer/issues/3301876.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sec + pm:security has been removed. Please use `composer audit` command. See https://www.drupal.org/project/project_composer/issues/3301876.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + pm-security + pm:security has been removed. Please use `composer audit` command. See https://www.drupal.org/project/project_composer/issues/3301876.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + pm:security-php + pm:security-php has been removed. Please use `composer audit` command.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sec-php + pm:security-php has been removed. Please use `composer audit` command.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + pm-security-php + pm:security-php has been removed. Please use `composer audit` command.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + role:create + Create a new role.
The symbolic machine name for the role.A descriptive name for the role.
Arguments:
machine_name
human_readable_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name human_readable_name[=null] +
+ + rcrt + Create a new role.
The symbolic machine name for the role.A descriptive name for the role.
Arguments:
machine_name
human_readable_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name human_readable_name[=null] +
+ + role-create + Create a new role.
The symbolic machine name for the role.A descriptive name for the role.
Arguments:
machine_name
human_readable_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name human_readable_name[=null] +
+ + role:delete + Delete a role.
The symbolic machine name for the role.
Arguments:
machine_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name +
+ + rdel + Delete a role.
The symbolic machine name for the role.
Arguments:
machine_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name +
+ + role-delete + Delete a role.
The symbolic machine name for the role.
Arguments:
machine_name

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name +
+ + role:list + Display roles and their permissions.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: ID (rid), Role Label (label), Permissions (perms)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + rls + Display roles and their permissions.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: ID (rid), Role Label (label), Permissions (perms)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + role-list + Display roles and their permissions.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: ID (rid), Role Label (label), Permissions (perms)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + role:perm:add + Grant specified permission(s) to a role.
The role to modify.The list of permission to grant, delimited by commas.
Arguments:
machine_name
permissions

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name permissions +
+ + rap + Grant specified permission(s) to a role.
The role to modify.The list of permission to grant, delimited by commas.
Arguments:
machine_name
permissions

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name permissions +
+ + role-add-perm + Grant specified permission(s) to a role.
The role to modify.The list of permission to grant, delimited by commas.
Arguments:
machine_name
permissions

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name permissions +
+ + role:perm:remove + Remove specified permission(s) from a role.
The role to modify.The list of permission to grant, delimited by commas.
Arguments:
machine_name
permissions

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name permissions +
+ + rmp + Remove specified permission(s) from a role.
The role to modify.The list of permission to grant, delimited by commas.
Arguments:
machine_name
permissions

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name permissions +
+ + role-remove-perm + Remove specified permission(s) from a role.
The role to modify.The list of permission to grant, delimited by commas.
Arguments:
machine_name
permissions

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ machine_name permissions +
+ + site:alias + Show site alias details, or a list of available site aliases.
Site alias or site specification.
Arguments:
site

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ site[=null] +
+ + sa + Show site alias details, or a list of available site aliases.
Site alias or site specification.
Arguments:
site

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ site[=null] +
+ + site:alias-convert + site:alias-convert has been removed. Please use Drush 11 or convert by hand.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sa-convert + site:alias-convert has been removed. Please use Drush 11 or convert by hand.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sac + site:alias-convert has been removed. Please use Drush 11 or convert by hand.

No arguments
Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + site:install + Install Drupal along with modules/themes/configuration/profile.
An install profile name. Defaults to standard unless an install profile is marked as a distribution. Use minimal for a bare minimum installation. Additional info for the install profile may also be provided with additional arguments. The key is in the form [form name].[parameter name]
Arguments:
profile

A Drupal 6 style database URL. Required for initial install, not re-install. If omitted and required, Drush prompts for this item.An optional table prefix to use for initial install.Account to use when creating a new database. Must have Grant permission (mysql only). Optional.Password for the db-su account. Optional.uid1 name.uid1 email.From: for system mailings.uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drush.yml.A short language code. Sets the default site language. Language files must already be present.Site nameName of directory under sites which should be created.Configuration from sync directory should be imported during installation.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
db-url
db-prefix
db-su
db-su-pw
account-name
account-mail
site-mail
account-pass
locale
site-name
site-pass
sites-subdir
config-dir
existing-config
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ profile[=null] +
+ + si + Install Drupal along with modules/themes/configuration/profile.
An install profile name. Defaults to standard unless an install profile is marked as a distribution. Use minimal for a bare minimum installation. Additional info for the install profile may also be provided with additional arguments. The key is in the form [form name].[parameter name]
Arguments:
profile

A Drupal 6 style database URL. Required for initial install, not re-install. If omitted and required, Drush prompts for this item.An optional table prefix to use for initial install.Account to use when creating a new database. Must have Grant permission (mysql only). Optional.Password for the db-su account. Optional.uid1 name.uid1 email.From: for system mailings.uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drush.yml.A short language code. Sets the default site language. Language files must already be present.Site nameName of directory under sites which should be created.Configuration from sync directory should be imported during installation.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
db-url
db-prefix
db-su
db-su-pw
account-name
account-mail
site-mail
account-pass
locale
site-name
site-pass
sites-subdir
config-dir
existing-config
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ profile[=null] +
+ + sin + Install Drupal along with modules/themes/configuration/profile.
An install profile name. Defaults to standard unless an install profile is marked as a distribution. Use minimal for a bare minimum installation. Additional info for the install profile may also be provided with additional arguments. The key is in the form [form name].[parameter name]
Arguments:
profile

A Drupal 6 style database URL. Required for initial install, not re-install. If omitted and required, Drush prompts for this item.An optional table prefix to use for initial install.Account to use when creating a new database. Must have Grant permission (mysql only). Optional.Password for the db-su account. Optional.uid1 name.uid1 email.From: for system mailings.uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drush.yml.A short language code. Sets the default site language. Language files must already be present.Site nameName of directory under sites which should be created.Configuration from sync directory should be imported during installation.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
db-url
db-prefix
db-su
db-su-pw
account-name
account-mail
site-mail
account-pass
locale
site-name
site-pass
sites-subdir
config-dir
existing-config
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ profile[=null] +
+ + site-install + Install Drupal along with modules/themes/configuration/profile.
An install profile name. Defaults to standard unless an install profile is marked as a distribution. Use minimal for a bare minimum installation. Additional info for the install profile may also be provided with additional arguments. The key is in the form [form name].[parameter name]
Arguments:
profile

A Drupal 6 style database URL. Required for initial install, not re-install. If omitted and required, Drush prompts for this item.An optional table prefix to use for initial install.Account to use when creating a new database. Must have Grant permission (mysql only). Optional.Password for the db-su account. Optional.uid1 name.uid1 email.From: for system mailings.uid1 pass. Defaults to a randomly generated password. If desired, set a fixed password in drush.yml.A short language code. Sets the default site language. Language files must already be present.Site nameName of directory under sites which should be created.Configuration from sync directory should be imported during installation.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
db-url
db-prefix
db-su
db-su-pw
account-name
account-mail
site-mail
account-pass
locale
site-name
site-pass
sites-subdir
config-dir
existing-config
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ profile[=null] +
+ + site:set + Set a site alias that will persist for the current session.
Site specification to use, or - for previous site. Omit this argument to unset.
Arguments:
site

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ site[=null] +
+ + use + Set a site alias that will persist for the current session.
Site specification to use, or - for previous site. Omit this argument to unset.
Arguments:
site

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ site[=null] +
+ + site-set + Set a site alias that will persist for the current session.
Site specification to use, or - for previous site. Omit this argument to unset.
Arguments:
site

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ site[=null] +
+ + site:ssh + Connect to a webserver via SSH, and optionally run a shell command.
Code which should run at remote host.
Arguments:
code

Directory to change to. Defaults to Drupal root.A string of extra options that will be passed to the ssh command (e.g. -p 100)Create a tty (e.g. to run an interactive program).Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cd
ssh-options
tty
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code[=null] +
+ + ssh + Connect to a webserver via SSH, and optionally run a shell command.
Code which should run at remote host.
Arguments:
code

Directory to change to. Defaults to Drupal root.A string of extra options that will be passed to the ssh command (e.g. -p 100)Create a tty (e.g. to run an interactive program).Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cd
ssh-options
tty
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code[=null] +
+ + site-ssh + Connect to a webserver via SSH, and optionally run a shell command.
Code which should run at remote host.
Arguments:
code

Directory to change to. Defaults to Drupal root.A string of extra options that will be passed to the ssh command (e.g. -p 100)Create a tty (e.g. to run an interactive program).Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
cd
ssh-options
tty
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ code[=null] +
+ + sql:cli + Open a SQL command-line interface using Drupal's credentials.

No arguments
Add custom options to the connect string (e.g. --extra=--skip-column-names)The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
extra
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sqlc + Open a SQL command-line interface using Drupal's credentials.

No arguments
Add custom options to the connect string (e.g. --extra=--skip-column-names)The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
extra
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql-cli + Open a SQL command-line interface using Drupal's credentials.

No arguments
Add custom options to the connect string (e.g. --extra=--skip-column-names)The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
extra
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql:conf +
No arguments
Show all database connections, instead of just one.Show password on the CLI. Useful for debugging.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
all
show-passwords
database
db-url
target
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql-conf +
No arguments
Show all database connections, instead of just one.Show password on the CLI. Useful for debugging.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
all
show-passwords
database
db-url
target
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql:connect + A string for connecting to the DB.

No arguments
Add custom options to the connect string (e.g. --extra=--skip-column-names)The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
extra
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql-connect + A string for connecting to the DB.

No arguments
Add custom options to the connect string (e.g. --extra=--skip-column-names)The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
extra
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql:create + Create a database.

No arguments
Account to use when creating a new database.Password for the db-su account.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
db-su
db-su-pw
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql-create + Create a database.

No arguments
Account to use when creating a new database.Password for the db-su account.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
db-su
db-su-pw
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql:drop + Drop all tables in a given database.

No arguments
The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql-drop + Drop all tables in a given database.

No arguments
The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql:dump + Exports the Drupal DB as SQL using mysqldump or equivalent.

No arguments
Save to a file. The file should be relative to Drupal root. If --result-file is provided with the value 'auto', a date-based filename will be created under ~/drush-backups directory.Omit DROP TABLE statements. Used by Postgres and Oracle only.Dump data without statements to create any of the schema.Order by primary key and add line breaks for efficient diffs. Slows down the dump. Mysql only.Compress the dump using the gzip program which must be in your $PATH.Add custom arguments/options when connecting to database (used internally to list tables).Add custom arguments/options to the dumping of the database (e.g. mysqldump command).Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlThe DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.Available fields: Path (path)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
result-file
create-db
data-only
ordered-dump
gzip
extra
extra-dump
format
database
db-url
target
show-passwords
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql-dump + Exports the Drupal DB as SQL using mysqldump or equivalent.

No arguments
Save to a file. The file should be relative to Drupal root. If --result-file is provided with the value 'auto', a date-based filename will be created under ~/drush-backups directory.Omit DROP TABLE statements. Used by Postgres and Oracle only.Dump data without statements to create any of the schema.Order by primary key and add line breaks for efficient diffs. Slows down the dump. Mysql only.Compress the dump using the gzip program which must be in your $PATH.Add custom arguments/options when connecting to database (used internally to list tables).Add custom arguments/options to the dumping of the database (e.g. mysqldump command).Format the result data. Available formats: csv,json,list,null,php,print-r,string,table,tsv,var_dump,var_export,xml,yamlThe DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.Available fields: Path (path)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
result-file
create-db
data-only
ordered-dump
gzip
extra
extra-dump
format
database
db-url
target
show-passwords
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + sql:query + Execute a query against a database.
An SQL query. Ignored if --file is provided.
Arguments:
query

Save to a file. The file should be relative to Drupal root.Path to a file containing the SQL to be run. Gzip files are accepted.Delete the --file after running it.Add custom options to the connect string (e.g. --extra=--skip-column-names)Enable replacement of braces in your query.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
result-file
file
file-delete
extra
db-prefix
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ query[=null] +
+ + sqlq + Execute a query against a database.
An SQL query. Ignored if --file is provided.
Arguments:
query

Save to a file. The file should be relative to Drupal root.Path to a file containing the SQL to be run. Gzip files are accepted.Delete the --file after running it.Add custom options to the connect string (e.g. --extra=--skip-column-names)Enable replacement of braces in your query.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
result-file
file
file-delete
extra
db-prefix
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ query[=null] +
+ + sql-query + Execute a query against a database.
An SQL query. Ignored if --file is provided.
Arguments:
query

Save to a file. The file should be relative to Drupal root.Path to a file containing the SQL to be run. Gzip files are accepted.Delete the --file after running it.Add custom options to the connect string (e.g. --extra=--skip-column-names)Enable replacement of braces in your query.The DB connection key if using multiple connections in settings.php.A Drupal 6 style database URL. For example mysql://root:pass@localhost:port/dbnameThe name of a target within the specified database connection.Show password on the CLI. Useful for debugging.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
result-file
file
file-delete
extra
db-prefix
database
db-url
target
show-passwords
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ query[=null] +
+ + sql:sync + Copy DB data from a source site to a target site. Transfers data via rsync.
A site-alias or site specification whose database you want to copy from.A site-alias or site specification whose database you want to replace.
Arguments:
source
target

Do not dump the sql database; always use an existing dump file.Do not rsync the database dump file from source to target.Where to run the rsync command; defaults to the local site. Can also be source or target.Create a new database before importing the database dump on the target machine.Account to use when creating a new database (e.g. root).Password for the db-su account.The path for storing the sql-dump on target machine.The path for retrieving the sql-dump on source machine.Add custom arguments/options to the dumping of the database (e.g. mysqldump command).A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
no-dump
no-sync
runner
create-db
db-su
db-su-pw
target-dump
source-dump
extra-dump
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source target +
+ + sql-sync + Copy DB data from a source site to a target site. Transfers data via rsync.
A site-alias or site specification whose database you want to copy from.A site-alias or site specification whose database you want to replace.
Arguments:
source
target

Do not dump the sql database; always use an existing dump file.Do not rsync the database dump file from source to target.Where to run the rsync command; defaults to the local site. Can also be source or target.Create a new database before importing the database dump on the target machine.Account to use when creating a new database (e.g. root).Password for the db-su account.The path for storing the sql-dump on target machine.The path for retrieving the sql-dump on source machine.Add custom arguments/options to the dumping of the database (e.g. mysqldump command).A key in the $skip_tables array. @see [Site aliases](../site-aliases.md)A key in the $structure_tables array. @see [Site aliases](../site-aliases.md)A key in the $tables array.A comma-separated list of tables to exclude completely.A comma-separated list of tables to include for structure, but not data.A comma-separated list of tables to transfer.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
no-dump
no-sync
runner
create-db
db-su
db-su-pw
target-dump
source-dump
extra-dump
skip-tables-key
structure-tables-key
tables-key
skip-tables-list
structure-tables-list
tables-list
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ source target +
+ + updatedb:batch-process + Process operations in the specified batch set.
The batch id that will be processed.
Arguments:
batch_id

Format the result data. Available formats: csv,json,list,null,php,print-r,tsv,var_dump,var_export,xml,yamlLimit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ batch_id +
+ + updatedb:status + List any pending database updates.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Module (module), Update ID (update_id), Description (description), Type (type)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + updbst + List any pending database updates.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Module (module), Update ID (update_id), Description (description), Type (type)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + updatedb-status + List any pending database updates.

No arguments
Format the result data. Available formats: csv,json,list,null,php,print-r,sections,string,table,tsv,var_dump,var_export,xml,yamlAvailable fields: Module (module), Update ID (update_id), Description (description), Type (type)Select just one field, and force format to *string*.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
format
fields
field
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+
+ + user:login + Display a one time login link for user ID 1, or another user.
Optional path to redirect to after logging in.
Arguments:
path

A user name to log in as.A user ID to log in as.A user email to log in as.Open the URL in the default browser. Use --no-browser to avoid opening a browser.A custom port for redirecting to (e.g., when running within a Vagrant environment)Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
name
uid
mail
browser
redirect-port
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ path[=null] +
+ + uli + Display a one time login link for user ID 1, or another user.
Optional path to redirect to after logging in.
Arguments:
path

A user name to log in as.A user ID to log in as.A user email to log in as.Open the URL in the default browser. Use --no-browser to avoid opening a browser.A custom port for redirecting to (e.g., when running within a Vagrant environment)Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
name
uid
mail
browser
redirect-port
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ path[=null] +
+ + user-login + Display a one time login link for user ID 1, or another user.
Optional path to redirect to after logging in.
Arguments:
path

A user name to log in as.A user ID to log in as.A user email to log in as.Open the URL in the default browser. Use --no-browser to avoid opening a browser.A custom port for redirecting to (e.g., when running within a Vagrant environment)Negate --browser option.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
name
uid
mail
browser
redirect-port
no-browser
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ path[=null] +
+ + yaml:get:value + Get a value for a specific key in a YAML file.
The filename of the YAML fileThe key for the value to get, in dot notation.
Arguments:
filename
key

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key +
+ + y:get:value + Get a value for a specific key in a YAML file.
The filename of the YAML fileThe key for the value to get, in dot notation.
Arguments:
filename
key

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key +
+ + yaml:lint + Validates that a given YAML file has valid syntax.
The filename of the YAML file
Arguments:
filename

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename +
+ + y:lint + Validates that a given YAML file has valid syntax.
The filename of the YAML file
Arguments:
filename

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename +
+ + yaml:unset:key + Unset a specific key in a YAML file.
The filename of the YAML fileThe key to unset, in dot notation
Arguments:
filename
key

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key +
+ + y:unset:key + Unset a specific key in a YAML file.
The filename of the YAML fileThe key to unset, in dot notation
Arguments:
filename
key

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key +
+ + yaml:update:key + Update a specific key in a YAML file.
The filename of the YAML fileThe original key, in dot notationThe new key, in dot notation
Arguments:
filename
key
new-key

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key new-key +
+ + y:update:key + Update a specific key in a YAML file.
The filename of the YAML fileThe original key, in dot notationThe new key, in dot notation
Arguments:
filename
key
new-key

Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key new-key +
+ + yaml:update:value + Update the value for a specific key in a YAML file.
The filename of the YAML fileThe key for the value to set, in dot notationThe new value
Arguments:
filename
key
value

Set the variable type for the value. Accepted types are int, integer, bool, boolean, str, and string.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
type
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key value +
+ + y:update:value + Update the value for a specific key in a YAML file.
The filename of the YAML fileThe key for the value to set, in dot notationThe new value
Arguments:
filename
key
value

Set the variable type for the value. Accepted types are int, integer, bool, boolean, str, and string.Display help for the given command. When no command is given display help for the list commandDo not output any messageIncrease the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debugDisplay this application versionForce (or disable --no-ansi) ANSI outputNegate the "--ansi" optionDo not ask any interactive questionAuto-accept the default for all user prompts. Equivalent to --no-interaction.Cancels at any confirmation prompt.The Drupal root for this site.A base URL for building links and selecting a multi-site. Defaults to https://default.Run in simulated mode (show what would have happened).Define a configuration item value.
Options:
type
help
quiet
verbose
version
ansi
no-ansi
no-interaction
yes
no
root
uri
simulate
define

]]>
+ filename key value +
+
+ diff --git a/apps/cms/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd b/apps/cms/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd new file mode 100644 index 000000000..f2efc6de1 --- /dev/null +++ b/apps/cms/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/cms/.idea/inspectionProfiles/Project_Default.xml b/apps/cms/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..03d9549ea --- /dev/null +++ b/apps/cms/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apps/cms/.idea/modules.xml b/apps/cms/.idea/modules.xml new file mode 100644 index 000000000..dc2033c9a --- /dev/null +++ b/apps/cms/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/cms/.idea/php.xml b/apps/cms/.idea/php.xml new file mode 100644 index 000000000..67da51258 --- /dev/null +++ b/apps/cms/.idea/php.xml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/cms/.idea/vcs.xml b/apps/cms/.idea/vcs.xml new file mode 100644 index 000000000..b2bdec2d7 --- /dev/null +++ b/apps/cms/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/cms/composer.json b/apps/cms/composer.json index 95a18201c..e187e9ef9 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -98,7 +98,7 @@ "Fix site install": "https://www.drupal.org/files/issues/2023-07-28/3349663-8.patch" }, "amazeelabs/silverback_gatsby": { - "Autosave preview": "./patches/fetch-entity-patch" + "Autosave preview": "./patches/fetch-entity.patch" } }, "patchLevel": { diff --git a/apps/cms/patches/fetch-entity-patch b/apps/cms/patches/fetch-entity.patch similarity index 100% rename from apps/cms/patches/fetch-entity-patch rename to apps/cms/patches/fetch-entity.patch From b5cc3b09df10b7db26085e72c7caa16bc08d8564 Mon Sep 17 00:00:00 2001 From: Dimitris Spachos Date: Mon, 22 Apr 2024 09:00:21 +0300 Subject: [PATCH 053/221] feat(slb-146): add version to autosave library --- .../silverback_autosave/silverback_autosave.libraries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml b/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml index aa5489ad3..fe2005e5b 100644 --- a/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml +++ b/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml @@ -1,5 +1,5 @@ drupal.silverback_autosave: - version: VERSION + version: 1.0 css: theme: css/silverback_autosave.css: {} From d3325815f846151a605be041dedd1cc0e6173620 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 22 Apr 2024 11:32:35 +0200 Subject: [PATCH 054/221] feat(SLB): add containers style --- .../ui/src/components/Atoms/Container.css | 40 +++++++++++++++++++ packages/ui/src/tailwind.css | 5 ++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/components/Atoms/Container.css diff --git a/packages/ui/src/components/Atoms/Container.css b/packages/ui/src/components/Atoms/Container.css new file mode 100644 index 000000000..8985bc6dc --- /dev/null +++ b/packages/ui/src/components/Atoms/Container.css @@ -0,0 +1,40 @@ +.container-page { + @apply max-w-full px-[1.25rem] md:px-[3.75rem]; +} + +.container-content { + @apply mx-auto max-w-7xl; +} + +.container-text { + @apply max-w-[40.75rem] xl:ml-[11rem] lg:ml-[7rem]; +} + +.nested-container .container-page { + @apply px-0 max-w-none; +} + +.nested-container .container-content { + @apply max-w-none; +} + +.nested-container .container-text { + @apply max-w-none ml-0 my-2.5 md:my-5; +} + +.container-content-wrapper { + @apply mx-auto max-w-[62rem]; +} + +.nested-container .container-page:first-child .container-text, +.nested-container .container-page:first-child .container-text *:first-child{ + @apply mt-0; +} +.nested-container .container-page:last-child .container-text, +.nested-container .container-page:last-child .container-text *:last-child { + @apply mb-0; +} + +.container-nested .prose ul:not(ul ul), .container-nested .prose ol:not(ol ol) { + @apply mt-0; +} diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index bd6213e1d..f9b68485a 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -1,3 +1,6 @@ +/* Import all atom stylesheets. */ +@import './components/Atoms/Container.css'; + @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; From 69e3a7cac7e785993afec1672f59ffd2e7b2a804 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Mon, 22 Apr 2024 15:57:04 +0200 Subject: [PATCH 055/221] feat(SLB-314): style CTA block --- .../src/fragments/PageContent/BlockCta.gql | 2 + packages/schema/src/schema.graphql | 7 ++++ .../Organisms/PageContent/BlockCta.stories.ts | 37 +++++++++++++++++++ .../Organisms/PageContent/BlockCta.tsx | 36 ++++++++++++++++-- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts diff --git a/packages/schema/src/fragments/PageContent/BlockCta.gql b/packages/schema/src/fragments/PageContent/BlockCta.gql index 2dae0fda2..958d9e283 100644 --- a/packages/schema/src/fragments/PageContent/BlockCta.gql +++ b/packages/schema/src/fragments/PageContent/BlockCta.gql @@ -2,4 +2,6 @@ fragment BlockCta on BlockCta { url text openInNewTab + displayIcon + iconPosition } diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 9300972cb..62357ed6f 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -73,6 +73,11 @@ enum Locale @default @value(string: "en") { de } +enum VerticalPosition { + Right + Left +} + """ Inteface for anything that can appear in the content hub. """ @@ -242,6 +247,8 @@ type BlockCta @type(id: "custom/cta") { url: Url @resolveEditorBlockAttribute(key: "url") text: String @resolveEditorBlockAttribute(key: "text") openInNewTab: Boolean @resolveEditorBlockAttribute(key: "openInNewTab") + displayIcon: Boolean + iconPosition: VerticalPosition } input PaginationInput { diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts new file mode 100644 index 000000000..7c6dc1566 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts @@ -0,0 +1,37 @@ +import { Url, VerticalPosition } from '@custom/schema'; +import { Meta, StoryObj } from '@storybook/react'; + +import { BlockCta } from './BlockCta'; + +export default { + component: BlockCta, +} satisfies Meta; + +export const BlockCtaDefault = { + args: { + text: 'Support center', + url: 'https://example.com' as Url, + openInNewTab: false, + displayIcon: true, + iconPosition: VerticalPosition.Right, + }, +} satisfies StoryObj; + +export const BlockCtaStoryIconLeft = { + args: { + text: 'Support center', + url: 'https://example.com' as Url, + openInNewTab: false, + displayIcon: true, + iconPosition: VerticalPosition.Left, + }, +} satisfies StoryObj; + +export const BlockCtaStoryNoIcon = { + args: { + text: 'Support center', + url: 'https://example.com' as Url, + openInNewTab: false, + displayIcon: false, + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx index a652f30b5..a0e5aa82d 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx @@ -1,7 +1,37 @@ -import { BlockCtaFragment } from '@custom/schema'; +import { BlockCtaFragment, VerticalPosition } from '@custom/schema'; +import clsx from 'clsx'; import React from 'react'; export function BlockCta(props: BlockCtaFragment) { - console.log(props); - return
; + return ( + + {props.text} + {!!props.displayIcon && } + + ); } + +const ArrowRightIcon = () => ( + + + +); From 3d740dfc29147b3bb05223efe481dc666e3af8d8 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 23 Apr 2024 10:08:51 +0300 Subject: [PATCH 056/221] chore(SLB-314 SLB-332): icon and icon display settings for gutenberg cta blocks --- .../gutenberg_blocks/src/blocks/cta.tsx | 39 ++++++++++++++++++- .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 5 ++- .../src/fragments/PageContent/BlockCta.gql | 2 +- packages/schema/src/schema.graphql | 20 ++++++---- .../Organisms/PageContent/BlockCta.stories.ts | 11 +++--- .../Organisms/PageContent/BlockCta.tsx | 6 +-- tests/e2e/specs/drupal/blocks.spec.ts | 5 +++ tests/schema/specs/blocks.spec.ts | 10 +++++ 8 files changed, 78 insertions(+), 20 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/src/blocks/cta.tsx b/packages/drupal/gutenberg_blocks/src/blocks/cta.tsx index c0bbaacdc..83bfd63de 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/cta.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/cta.tsx @@ -5,7 +5,7 @@ import { RichText, } from 'wordpress__block-editor'; import { registerBlockType } from 'wordpress__blocks'; -import { PanelBody, ToggleControl } from 'wordpress__components'; +import { PanelBody, SelectControl, ToggleControl } from 'wordpress__components'; import { compose, withState } from 'wordpress__compose'; // @ts-ignore @@ -37,6 +37,14 @@ registerBlockType('custom/cta', { openInNewTab: { type: 'boolean', }, + icon: { + type: 'string', + default: 'NONE', + }, + iconPosition: { + type: 'string', + default: 'AFTER', + }, }, // @ts-ignore edit: compose(withState({}))((props) => { @@ -116,6 +124,35 @@ registerBlockType('custom/cta', { }); }} /> + { + props.setAttributes({ + icon, + }); + }} + /> + {typeof props.attributes.icon !== 'undefined' && + props.attributes.icon !== 'NONE' && ( + { + props.setAttributes({ + iconPosition, + }); + }} + /> + )}
diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index de316ba27..ad87ab613 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -88,9 +88,10 @@ default: - + + + -

diff --git a/packages/schema/src/fragments/PageContent/BlockCta.gql b/packages/schema/src/fragments/PageContent/BlockCta.gql index 958d9e283..db56f5580 100644 --- a/packages/schema/src/fragments/PageContent/BlockCta.gql +++ b/packages/schema/src/fragments/PageContent/BlockCta.gql @@ -2,6 +2,6 @@ fragment BlockCta on BlockCta { url text openInNewTab - displayIcon + icon iconPosition } diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 62357ed6f..f364f7d51 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -73,11 +73,6 @@ enum Locale @default @value(string: "en") { de } -enum VerticalPosition { - Right - Left -} - """ Inteface for anything that can appear in the content hub. """ @@ -247,8 +242,19 @@ type BlockCta @type(id: "custom/cta") { url: Url @resolveEditorBlockAttribute(key: "url") text: String @resolveEditorBlockAttribute(key: "text") openInNewTab: Boolean @resolveEditorBlockAttribute(key: "openInNewTab") - displayIcon: Boolean - iconPosition: VerticalPosition + icon: CTAIconType @resolveEditorBlockAttribute(key: "icon") + iconPosition: CTAIconPosition + @resolveEditorBlockAttribute(key: "iconPosition") +} + +enum CTAIconType { + NONE + ARROW +} + +enum CTAIconPosition { + AFTER + BEFORE } input PaginationInput { diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts index 7c6dc1566..420a4a375 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.stories.ts @@ -1,4 +1,4 @@ -import { Url, VerticalPosition } from '@custom/schema'; +import { CtaIconPosition, Url } from '@custom/schema'; import { Meta, StoryObj } from '@storybook/react'; import { BlockCta } from './BlockCta'; @@ -12,8 +12,8 @@ export const BlockCtaDefault = { text: 'Support center', url: 'https://example.com' as Url, openInNewTab: false, - displayIcon: true, - iconPosition: VerticalPosition.Right, + icon: 'ARROW', + iconPosition: CtaIconPosition.After, }, } satisfies StoryObj; @@ -22,8 +22,8 @@ export const BlockCtaStoryIconLeft = { text: 'Support center', url: 'https://example.com' as Url, openInNewTab: false, - displayIcon: true, - iconPosition: VerticalPosition.Left, + icon: 'ARROW', + iconPosition: CtaIconPosition.Before, }, } satisfies StoryObj; @@ -32,6 +32,5 @@ export const BlockCtaStoryNoIcon = { text: 'Support center', url: 'https://example.com' as Url, openInNewTab: false, - displayIcon: false, }, } satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx index a0e5aa82d..ab5df05d0 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx @@ -1,4 +1,4 @@ -import { BlockCtaFragment, VerticalPosition } from '@custom/schema'; +import { BlockCtaFragment, CtaIconPosition, CtaIconType } from '@custom/schema'; import clsx from 'clsx'; import React from 'react'; @@ -6,7 +6,7 @@ export function BlockCta(props: BlockCtaFragment) { return ( {props.text} - {!!props.displayIcon && } + {!!props.icon && props.icon === CtaIconType.Arrow && } ); } diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index ec7dc6a1b..f11e09bda 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -55,6 +55,11 @@ test('All blocks are rendered', async ({ page }) => { 1, ); + // CTA blocks + await expect(page.locator('a:text("Internal CTA")')).toHaveCount(1); + await expect(page.locator('a:text("External CTA")')).toHaveCount(1); + await expect(page.locator('a:text("CTA with link to media")')).toHaveCount(1); + // Form await expect( page.locator('.silverback-iframe iframe').last(), diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 9a246e3f6..5808a38d3 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -52,6 +52,8 @@ test('Blocks', async () => { url text openInNewTab + icon + iconPosition } } } @@ -145,18 +147,24 @@ test('Blocks', async () => { }, { "__typename": "BlockCta", + "icon": null, + "iconPosition": null, "openInNewTab": null, "text": "Internal CTA", "url": "/en/drupal", }, { "__typename": "BlockCta", + "icon": "ARROW", + "iconPosition": null, "openInNewTab": true, "text": "External CTA", "url": "https://www.google.com", }, { "__typename": "BlockCta", + "icon": "ARROW", + "iconPosition": "BEFORE", "openInNewTab": null, "text": "CTA with link to media", "url": "/media/[numeric]", @@ -205,6 +213,8 @@ test('Blocks', async () => { }, { "__typename": "BlockCta", + "icon": null, + "iconPosition": null, "openInNewTab": null, "text": null, "url": null, From fa98dc821b96b84649d03201ce4dd0c6283a5270 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Tue, 23 Apr 2024 19:32:08 +0200 Subject: [PATCH 057/221] fix(SLB-325): adjust default hero height --- packages/ui/src/components/Organisms/PageHero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index 7eea65402..c7c18b202 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -15,7 +15,7 @@ export function PageHero(props: NonNullable) { function DefaultHero(props: NonNullable) { return ( -
+
{props.image ? ( {props.image.alt} Date: Wed, 17 Apr 2024 16:58:29 +0400 Subject: [PATCH 058/221] chore(SLB-289): disable color and typography options on the list block --- packages/drupal/gutenberg_blocks/src/customisations.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/drupal/gutenberg_blocks/src/customisations.ts b/packages/drupal/gutenberg_blocks/src/customisations.ts index 70d97ccac..fb0355c8e 100644 --- a/packages/drupal/gutenberg_blocks/src/customisations.ts +++ b/packages/drupal/gutenberg_blocks/src/customisations.ts @@ -16,6 +16,10 @@ drupalSettings.gutenberg._listeners.init.push( const paragraphBlock = wp.blocks.getBlockType('core/paragraph'); paragraphBlock.supports.typography.fontSize = false; paragraphBlock.supports.typography.dropCap = false; + // @ts-ignore + const listBlock = wp.blocks.getBlockType('core/list'); + listBlock.supports.color = false; + listBlock.supports.typography = false; }, // Allow common blocks to be placed only in the Content block. From 4db75851014c965a9e9115c59a57980cac080d0c Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 17 Apr 2024 16:58:57 +0400 Subject: [PATCH 059/221] feat(SLB-289): extend the core/list block with a new style option --- .../gutenberg_blocks/src/filters/list.tsx | 105 ++++++++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + 2 files changed, 106 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/filters/list.tsx diff --git a/packages/drupal/gutenberg_blocks/src/filters/list.tsx b/packages/drupal/gutenberg_blocks/src/filters/list.tsx new file mode 100644 index 000000000..707e7aa61 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/filters/list.tsx @@ -0,0 +1,105 @@ +import clsx from 'clsx'; +import { InspectorControls } from 'wordpress__block-editor'; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { createHigherOrderComponent } from 'wordpress__compose'; +import { addFilter } from 'wordpress__hooks'; + +// @ts-ignore +const __ = Drupal.t; + +addFilter( + 'blocks.registerBlockType', + 'custom/list', + (settings: { name: string; attributes: any }) => { + if (settings.name === 'core/list') { + settings.attributes = Object.assign(settings.attributes, { + customListStyle: { + type: 'string', + default: '', + }, + }); + } + return settings; + }, +); + +addFilter( + 'editor.BlockEdit', + 'custom/list', + createHigherOrderComponent( + // eslint-disable-next-line react/display-name + (BlockEdit) => (props) => { + const { name, attributes, setAttributes, isSelected } = props; + const { customListStyle, ordered } = attributes; + if (!customListStyle === undefined) { + setAttributes({ customListStyle: '' }); + } + return ( + <> + + {isSelected && name === 'core/list' && !ordered ? ( + + + { + setAttributes({ + customListStyle, + }); + }} + /> + + + ) : null} + + ); + }, + 'withCustomListStyleControls', + ), +); + +addFilter( + 'editor.BlockListBlock', + 'custom/list', + createHigherOrderComponent( + // eslint-disable-next-line react/display-name + (BlockListBlock) => (props) => { + const { name, attributes } = props; + if (name === 'core/list') { + const { customListStyle, ordered } = attributes; + if (!ordered && customListStyle) { + props.className = getCustomListClass( + props.className, + customListStyle, + ); + } + } + return ; + }, + 'withCustomListStyleBlockClass', + ), +); + +addFilter( + 'blocks.getSaveContent.extraProps', + 'custom/list', + (props: any, blockType: { name: string }, attributes: any) => { + if (blockType.name === 'core/list') { + const { customListStyle, ordered } = attributes; + if (!ordered && customListStyle) { + props.className = getCustomListClass(props.className, customListStyle); + } + } + return props; + }, +); + +function getCustomListClass(existingClassName: string, style: string) { + return clsx(existingClassName, 'list-style--' + style); +} diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index a0aff90af..74af52921 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -5,4 +5,5 @@ import './blocks/heading'; import './blocks/form'; import './blocks/image-teaser'; import './blocks/image-teasers'; +import './filters/list'; import './blocks/cta'; From 2f3fafc340fc7f11b5ebe6ea539730988a69059b Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 17 Apr 2024 16:59:14 +0400 Subject: [PATCH 060/221] feat(SLB-289): introduce custom/image-with-text block --- packages/drupal/gutenberg_blocks/package.json | 1 + .../src/blocks/image-with-text.tsx | 79 ++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 26 ++ .../ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml | 6 + packages/schema/src/fragments/Page.gql | 1 + .../PageContent/BlockImageWithText.gql | 9 + packages/schema/src/schema.graphql | 6 + .../src/components/Organisms/PageDisplay.tsx | 15 ++ pnpm-lock.yaml | 254 +++++++++++++++--- tests/schema/specs/blocks.spec.ts | 41 +++ 11 files changed, 402 insertions(+), 37 deletions(-) create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx create mode 100644 packages/schema/src/fragments/PageContent/BlockImageWithText.gql diff --git a/packages/drupal/gutenberg_blocks/package.json b/packages/drupal/gutenberg_blocks/package.json index 093ca1970..b31ec99c1 100644 --- a/packages/drupal/gutenberg_blocks/package.json +++ b/packages/drupal/gutenberg_blocks/package.json @@ -27,6 +27,7 @@ "@types/wordpress__core-data": "2.4.5", "@types/wordpress__data": "6.0.2", "@types/wordpress__editor": "13.0.0", + "@types/wordpress__hooks": "2.4.0", "@vitejs/plugin-react": "4.2.1", "typescript": "^5.3.3", "vite": "^5.0.10" diff --git a/packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx b/packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx new file mode 100644 index 000000000..d4717d4aa --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/image-with-text.tsx @@ -0,0 +1,79 @@ +import { InnerBlocks, InspectorControls } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { dispatch } from 'wordpress__data'; + +import { DrupalMediaEntity } from '../utils/drupal-media'; + +// @ts-ignore +const { t: __ } = Drupal; + +registerBlockType('custom/image-with-text', { + title: __('Image with Text'), + icon: 'cover-image', + category: 'layout', + attributes: { + mediaEntityIds: { + type: 'array', + }, + imagePosition: { + type: 'string', + default: 'left', + }, + }, + edit: (props) => { + const { setAttributes } = props; + return ( + <> + + + { + setAttributes({ + imagePosition, + }); + }} + /> + + +
+
{__('Image with Text')}
+ { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> + +
+ + ); + }, + save: () => , +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 74af52921..6eae568bf 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -5,5 +5,6 @@ import './blocks/heading'; import './blocks/form'; import './blocks/image-teaser'; import './blocks/image-teasers'; +import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index de316ba27..43e5fe62d 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -60,6 +60,32 @@ default: + + +

All kinds of allowed blocks

+ + + +
  • bla
+ + + +

Heading

+ + + +
12
34
Caption
+ + + +

Quote

Citation
+ + + +

+ + +

Starting from this paragraph, all the following blocks should be aggregated, as they are just HTML

diff --git a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml index 6c19a491d..53ade0b64 100644 --- a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml +++ b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml @@ -67,6 +67,12 @@ default: + + + +

+ + format: gutenberg summary: '' diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 38c057340..0a78d9dea 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -33,6 +33,7 @@ fragment Page on Page { ...BlockForm ...BlockImageTeasers ...BlockCta + ...BlockImageWithText } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockImageWithText.gql b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql new file mode 100644 index 000000000..b026e8676 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql @@ -0,0 +1,9 @@ +fragment BlockImageWithText on BlockImageWithText { + image { + source(width: 1536, sizes: [[768, 768], [1536, 1536]]) + alt + } + textContent { + markup + } +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 9300972cb..3d86b6850 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -199,6 +199,7 @@ union PageContent @resolveEditorBlockType = | BlockForm | BlockImageTeasers | BlockCta + | BlockImageWithText type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -244,6 +245,11 @@ type BlockCta @type(id: "custom/cta") { openInNewTab: Boolean @resolveEditorBlockAttribute(key: "openInNewTab") } +type BlockImageWithText @type(id: "custom/image-with-text") { + image: MediaImage @resolveEditorBlockMedia + textContent: BlockMarkup @resolveEditorBlockChildren @seek(pos: 0) +} + input PaginationInput { limit: Int! offset: Int! diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index bcefcb2ec..975d447c0 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -43,6 +43,21 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockCta': return ; + case 'BlockImageWithText': + return ( + // TODO: Implement BlockImageWithText +
+ BlockImageWithText goes here +
+ ); default: throw new UnreachableCaseError(block); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aca358f2..d516514ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -354,6 +354,9 @@ importers: '@types/wordpress__editor': specifier: 13.0.0 version: 13.0.0(react-dom@18.2.0)(react@18.2.0) + '@types/wordpress__hooks': + specifier: 2.4.0 + version: 2.4.0 '@vitejs/plugin-react': specifier: 4.2.1 version: 4.2.1(vite@5.0.10) @@ -2993,6 +2996,7 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true + dev: true optional: true /@esbuild/aix-ppc64@0.20.0: @@ -3036,6 +3040,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm64@0.20.0: @@ -3079,6 +3084,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.20.0: @@ -3122,6 +3128,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.20.0: @@ -3165,6 +3172,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.20.0: @@ -3208,6 +3216,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.20.0: @@ -3251,6 +3260,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.20.0: @@ -3294,6 +3304,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.20.0: @@ -3337,6 +3348,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.20.0: @@ -3380,6 +3392,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.20.0: @@ -3423,6 +3436,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.20.0: @@ -3466,6 +3480,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.20.0: @@ -3509,6 +3524,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.20.0: @@ -3552,6 +3568,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.20.0: @@ -3595,6 +3612,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.20.0: @@ -3638,6 +3656,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.20.0: @@ -3681,6 +3700,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.20.0: @@ -3724,6 +3744,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.20.0: @@ -3767,6 +3788,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.20.0: @@ -3810,6 +3832,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.20.0: @@ -3853,6 +3876,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.20.0: @@ -3896,6 +3920,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.20.0: @@ -3939,6 +3964,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.20.0: @@ -7147,7 +7173,7 @@ packages: react-refresh: 0.14.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /@pnpm/config.env-replace@1.1.0: resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} @@ -9956,6 +9982,10 @@ packages: - react-dom dev: true + /@types/wordpress__hooks@2.4.0: + resolution: {integrity: sha512-SixjDsBvPynmMQxBFX3i4nuWtLifqtml7PhSosoCk5RJy9S7xs/G6vvtcnAuslV3BJTyvxEbAsz4WpKaF0bqMw==} + dev: true + /@types/wordpress__keycodes@2.18.0: resolution: {integrity: sha512-09ku81E6tjB//bI5PatKM/rhTJ0aEmNLvhIn380orea+SX6/09t7luO27zxI0uHSs/1Pxue1ifrMKY+gE5lfzw==} deprecated: This is a stub types definition. @wordpress/keycodes provides its own type definitions, so you do not need this installed. @@ -10082,6 +10112,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} @@ -10110,7 +10141,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /@typescript-eslint/eslint-plugin@6.17.0(@typescript-eslint/parser@6.17.0)(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==} @@ -10226,8 +10256,6 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color - dev: false - optional: true /@typescript-eslint/parser@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==} @@ -10269,6 +10297,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} @@ -10283,6 +10312,7 @@ packages: dependencies: '@typescript-eslint/types': 6.17.0 '@typescript-eslint/visitor-keys': 6.17.0 + dev: true /@typescript-eslint/scope-manager@6.21.0: resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} @@ -10329,6 +10359,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/type-utils@5.62.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} @@ -10349,7 +10380,6 @@ packages: transitivePeerDependencies: - supports-color dev: false - optional: true /@typescript-eslint/type-utils@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==} @@ -10398,6 +10428,7 @@ packages: /@typescript-eslint/types@6.17.0: resolution: {integrity: sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==} engines: {node: ^16.0.0 || >=18.0.0} + dev: true /@typescript-eslint/types@6.21.0: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} @@ -10484,7 +10515,6 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color - dev: false /@typescript-eslint/typescript-estree@6.17.0(typescript@5.3.3): resolution: {integrity: sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==} @@ -10506,6 +10536,7 @@ packages: typescript: 5.3.3 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3): resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} @@ -10566,6 +10597,7 @@ packages: transitivePeerDependencies: - supports-color - typescript + dev: true /@typescript-eslint/utils@5.62.0(eslint@7.32.0)(typescript@5.4.4): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} @@ -10586,7 +10618,6 @@ packages: - supports-color - typescript dev: false - optional: true /@typescript-eslint/utils@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==} @@ -10658,6 +10689,7 @@ packages: dependencies: '@typescript-eslint/types': 6.17.0 eslint-visitor-keys: 3.4.3 + dev: true /@typescript-eslint/visitor-keys@6.21.0: resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} @@ -12524,7 +12556,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /babel-plugin-add-module-exports@1.0.4: resolution: {integrity: sha512-g+8yxHUZ60RcyaUpfNzy56OtWW+x9cyEe9j+CranqLiqbju2yf/Cy6ZtYK40EZxtrdHllzlVZgLmcOUCTlJ7Jg==} @@ -12630,7 +12662,7 @@ packages: '@babel/core': 7.24.4 '@babel/runtime': 7.24.4 '@babel/types': 7.24.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 dev: false @@ -14417,7 +14449,7 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.6.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /css-minimizer-webpack-plugin@2.0.0(webpack@5.91.0): resolution: {integrity: sha512-cG/uc94727tx5pBNtb1Sd7gvUPzwmcQi1lkpfqTpdkuNq75hJCw7bIVsCNijLm4dhDcr1atvuysl2rZqOG8Txw==} @@ -14439,7 +14471,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 5.0.1 source-map: 0.6.1 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -16570,6 +16602,7 @@ packages: '@esbuild/win32-arm64': 0.19.12 '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + dev: true /esbuild@0.20.0: resolution: {integrity: sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==} @@ -16762,6 +16795,44 @@ packages: eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) typescript: 5.3.3 + dev: true + + /eslint-config-react-app@6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.4.4): + resolution: {integrity: sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^4.0.0 + '@typescript-eslint/parser': ^4.0.0 + babel-eslint: ^10.0.0 + eslint: ^7.5.0 + eslint-plugin-flowtype: ^5.2.0 + eslint-plugin-import: ^2.22.0 + eslint-plugin-jest: ^24.0.0 + eslint-plugin-jsx-a11y: ^6.3.1 + eslint-plugin-react: ^7.20.3 + eslint-plugin-react-hooks: ^4.0.8 + eslint-plugin-testing-library: ^3.9.0 + typescript: '*' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true + eslint-plugin-testing-library: + optional: true + typescript: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + babel-eslint: 10.1.0(eslint@7.32.0) + confusing-browser-globals: 1.0.11 + eslint: 7.32.0 + eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) + eslint-plugin-react: 7.34.1(eslint@7.32.0) + eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) + typescript: 5.4.4 + dev: false /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -16772,6 +16843,34 @@ packages: transitivePeerDependencies: - supports-color + /eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0): + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + debug: 3.2.7 + eslint: 7.32.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + /eslint-module-utils@2.8.1(@typescript-eslint/parser@6.17.0)(eslint-import-resolver-node@0.3.9)(eslint@7.0.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} @@ -16828,6 +16927,7 @@ packages: eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color + dev: true /eslint-plugin-flowtype@5.10.0(eslint@7.32.0): resolution: {integrity: sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==} @@ -16871,7 +16971,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -16880,7 +16980,7 @@ packages: doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.17.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@7.32.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -16963,6 +17063,7 @@ packages: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color + dev: true /eslint-plugin-jsx-a11y@6.8.0(eslint@7.32.0): resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} @@ -17175,7 +17276,7 @@ packages: micromatch: 4.0.5 normalize-path: 3.0.0 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /eslint@7.0.0: resolution: {integrity: sha512-qY1cwdOxMONHJfGqw52UOpZDeqXy8xmD0u8CT6jIstil72jkhURC704W8CFyTPDPllz4z4lu0Ql1+07PG/XdIg==} @@ -17979,7 +18080,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /file-system-cache@2.3.0: resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==} @@ -18349,6 +18450,39 @@ packages: tapable: 1.1.3 typescript: 5.3.3 webpack: 5.91.0(esbuild@0.19.12) + dev: true + + /fork-ts-checker-webpack-plugin@6.5.3(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0): + resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} + engines: {node: '>=10', yarn: '>=1.0.0'} + peerDependencies: + eslint: '>= 6' + typescript: '>= 2.7' + vue-template-compiler: '*' + webpack: '>= 4' + peerDependenciesMeta: + eslint: + optional: true + vue-template-compiler: + optional: true + dependencies: + '@babel/code-frame': 7.24.2 + '@types/json-schema': 7.0.15 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 6.0.0 + deepmerge: 4.3.1 + eslint: 7.32.0 + fs-extra: 9.1.0 + glob: 7.2.3 + memfs: 3.5.3 + minimatch: 3.1.2 + schema-utils: 2.7.0 + semver: 7.6.0 + tapable: 1.1.3 + typescript: 5.4.4 + webpack: 5.91.0 + dev: false /form-data-encoder@2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} @@ -18623,7 +18757,7 @@ packages: dependencies: '@types/node-fetch': 2.6.11 fs-extra: 9.1.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) lodash: 4.17.21 node-fetch: 2.7.0 p-queue: 6.6.2 @@ -18785,7 +18919,7 @@ packages: chokidar: 3.6.0 fs-exists-cached: 1.0.0 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-page-utils: 3.13.1 gatsby-plugin-utils: 4.13.1(gatsby@5.13.3)(graphql@16.8.1) @@ -18854,7 +18988,7 @@ packages: debug: 4.3.4 filenamify: 4.3.0 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-plugin-utils: 4.13.1(gatsby@5.13.3)(graphql@16.8.1) lodash: 4.17.21 @@ -18913,7 +19047,7 @@ packages: '@babel/preset-typescript': 7.24.1(@babel/core@7.24.4) '@babel/runtime': 7.24.4 babel-plugin-remove-graphql-queries: 5.13.1(@babel/core@7.24.4)(gatsby@5.13.3) - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) transitivePeerDependencies: - supports-color dev: false @@ -18951,7 +19085,7 @@ packages: '@babel/runtime': 7.24.4 fastq: 1.17.1 fs-extra: 11.2.0 - gatsby: 5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3) + gatsby: 5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4) gatsby-core-utils: 4.13.1 gatsby-sharp: 1.13.0 graphql: 16.8.1 @@ -19453,7 +19587,7 @@ packages: - webpack-hot-middleware - webpack-plugin-serve - /gatsby@5.13.3(babel-eslint@10.1.0)(esbuild@0.19.12)(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3): + /gatsby@5.13.3(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.4): resolution: {integrity: sha512-SSnGpjswK20BQORcvTbtK8eI+W4QUG+u8rdVswB4suva6BfvTakW2wiktj7E2MdO4NjRvlgJjF5dUUncU5nldA==} engines: {node: '>=18.0.0'} hasBin: true @@ -19487,8 +19621,8 @@ packages: '@pmmmwh/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(webpack@5.91.0) '@sigmacomputing/babel-plugin-lodash': 3.3.5 '@types/http-proxy': 1.17.14 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.3.3) - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.3.3) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) '@vercel/webpack-asset-relocator-loader': 1.7.3 acorn-loose: 8.4.0 acorn-walk: 8.3.2 @@ -19526,9 +19660,9 @@ packages: enhanced-resolve: 5.16.0 error-stack-parser: 2.1.4 eslint: 7.32.0 - eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.3.3) + eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(babel-eslint@10.1.0)(eslint-plugin-flowtype@5.10.0)(eslint-plugin-import@2.29.1)(eslint-plugin-jsx-a11y@6.8.0)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.34.1)(eslint@7.32.0)(typescript@5.4.4) eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.17.0)(eslint@7.32.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint@7.32.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@7.32.0) eslint-plugin-react: 7.34.1(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.0(eslint@7.32.0) @@ -19600,7 +19734,7 @@ packages: query-string: 6.14.1 raw-loader: 4.0.2(webpack@5.91.0) react: 18.2.0 - react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.3.3)(webpack@5.91.0) + react-dev-utils: 12.0.1(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0) react-dom: 18.2.0(react@18.2.0) react-refresh: 0.14.0 react-server-dom-webpack: 0.0.0-experimental-c8b778b7f-20220825(react@18.2.0)(webpack@5.91.0) @@ -19618,13 +19752,13 @@ packages: strip-ansi: 6.0.1 style-loader: 2.0.0(webpack@5.91.0) style-to-object: 0.4.4 - terser-webpack-plugin: 5.3.10(esbuild@0.19.12)(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(webpack@5.91.0) tmp: 0.2.3 true-case-path: 2.2.1 type-of: 2.0.1 url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.91.0) uuid: 8.3.2 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 webpack-dev-middleware: 4.3.0(webpack@5.91.0) webpack-merge: 5.10.0 webpack-stats-plugin: 1.1.3 @@ -24269,7 +24403,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 webpack-sources: 1.4.3 /mini-svg-data-uri@1.4.4: @@ -24299,6 +24433,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true /minimatch@9.0.4: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} @@ -25121,7 +25256,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -26376,7 +26511,7 @@ packages: klona: 2.0.6 postcss: 8.4.38 semver: 7.6.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /postcss-merge-longhand@5.1.7(postcss@8.4.38): resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} @@ -27371,7 +27506,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /rbush@3.0.1: resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} @@ -27537,6 +27672,49 @@ packages: - eslint - supports-color - vue-template-compiler + dev: true + + /react-dev-utils@12.0.1(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0): + resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=2.7' + webpack: '>=4' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@babel/code-frame': 7.24.2 + address: 1.2.2 + browserslist: 4.23.0 + chalk: 4.1.2 + cross-spawn: 7.0.3 + detect-port-alt: 1.1.6 + escape-string-regexp: 4.0.0 + filesize: 8.0.7 + find-up: 5.0.0 + fork-ts-checker-webpack-plugin: 6.5.3(eslint@7.32.0)(typescript@5.4.4)(webpack@5.91.0) + global-modules: 2.0.0 + globby: 11.1.0 + gzip-size: 6.0.0 + immer: 9.0.21 + is-root: 2.1.0 + loader-utils: 3.2.1 + open: 8.4.2 + pkg-up: 3.1.0 + prompts: 2.4.2 + react-error-overlay: 6.0.11 + recursive-readdir: 2.2.3 + shell-quote: 1.8.1 + strip-ansi: 6.0.1 + text-table: 0.2.0 + typescript: 5.4.4 + webpack: 5.91.0 + transitivePeerDependencies: + - eslint + - supports-color + - vue-template-compiler + dev: false /react-dnd-html5-backend@14.1.0: resolution: {integrity: sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==} @@ -27936,7 +28114,7 @@ packages: loose-envify: 1.4.0 neo-async: 2.6.2 react: 18.2.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /react-split-pane@0.1.92(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==} @@ -30290,7 +30468,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /style-to-object@0.3.0: resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} @@ -30682,6 +30860,7 @@ packages: serialize-javascript: 6.0.2 terser: 5.30.3 webpack: 5.91.0(esbuild@0.19.12) + dev: true /terser-webpack-plugin@5.3.10(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -31026,6 +31205,7 @@ packages: typescript: '>=4.2.0' dependencies: typescript: 5.3.3 + dev: true /ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -31174,7 +31354,6 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.4.4 - dev: false /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -31846,7 +32025,7 @@ packages: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} @@ -32817,7 +32996,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 3.3.0 - webpack: 5.91.0(esbuild@0.19.12) + webpack: 5.91.0 /webpack-merge@5.10.0: resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} @@ -32964,6 +33143,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /website-scraper@5.3.1: resolution: {integrity: sha512-gogqPXD2gVsxoyd2yRiympw3rA5GuEpD1CaDEJ/J8zzanx7hkbTtneoO1SGs436PpLbWVcUge+6APGLhzsuZPA==} diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 9a246e3f6..4b86529d4 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -53,6 +53,15 @@ test('Blocks', async () => { text openInNewTab } + ... on BlockImageWithText { + image { + __typename + } + textContent { + __typename + markup + } + } } } { @@ -106,6 +115,28 @@ test('Blocks', async () => { "__typename": "BlockForm", "url": "http://127.0.0.1:8000/en/form/contact", }, + { + "__typename": "BlockImageWithText", + "image": { + "__typename": "MediaImage", + }, + "textContent": { + "__typename": "BlockMarkup", + "markup": " +

All kinds of allowed blocks

+ +
  • bla
+ +

Heading

+ +
12
34
Caption
+ +

Quote

Citation
+ +

+ ", + }, + }, { "__typename": "BlockMarkup", "markup": " @@ -209,6 +240,16 @@ test('Blocks', async () => { "text": null, "url": null, }, + { + "__typename": "BlockImageWithText", + "image": null, + "textContent": { + "__typename": "BlockMarkup", + "markup": " +

+ ", + }, + }, ], "hero": { "__typename": "Hero", From 16236d3665213ae29821bbad52b2bcd65391ce73 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 24 Apr 2024 14:14:29 +0400 Subject: [PATCH 061/221] chore(SLB-289): add imagePosition to the schema --- .../content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml | 3 ++- packages/schema/src/schema.graphql | 7 +++++++ tests/schema/specs/blocks.spec.ts | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index 43e5fe62d..61b3a4ac9 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -60,7 +60,7 @@ default: - +

All kinds of allowed blocks

@@ -117,6 +117,7 @@ default: +

diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 3d86b6850..e3b54ace8 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -247,9 +247,16 @@ type BlockCta @type(id: "custom/cta") { type BlockImageWithText @type(id: "custom/image-with-text") { image: MediaImage @resolveEditorBlockMedia + imagePosition: ImagePosition! + @resolveEditorBlockAttribute(key: "imagePosition") textContent: BlockMarkup @resolveEditorBlockChildren @seek(pos: 0) } +enum ImagePosition @default @value(string: "left") { + left + right +} + input PaginationInput { limit: Int! offset: Int! diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 4b86529d4..b5cf47cf5 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -57,6 +57,7 @@ test('Blocks', async () => { image { __typename } + imagePosition textContent { __typename markup @@ -120,6 +121,7 @@ test('Blocks', async () => { "image": { "__typename": "MediaImage", }, + "imagePosition": "right", "textContent": { "__typename": "BlockMarkup", "markup": " @@ -243,6 +245,7 @@ test('Blocks', async () => { { "__typename": "BlockImageWithText", "image": null, + "imagePosition": "left", "textContent": { "__typename": "BlockMarkup", "markup": " From 958724e36b47be435c7c5289939d830335582400 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 24 Apr 2024 14:58:26 +0400 Subject: [PATCH 062/221] chore(SLB-335): update Drupal to 10.2.5 To fix https://www.drupal.org/project/drupal/issues/3394048 --- apps/cms/composer.lock | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index 07ca8aaa5..e913103c2 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -2371,16 +2371,16 @@ }, { "name": "drupal/core", - "version": "10.2.3", + "version": "10.2.5", "source": { "type": "git", "url": "https://github.com/drupal/core.git", - "reference": "cc8c7952f7013795b735f5c15290e76937163bb7" + "reference": "dddd242b74f40df892a7f16a48245c3b76d9b003" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core/zipball/cc8c7952f7013795b735f5c15290e76937163bb7", - "reference": "cc8c7952f7013795b735f5c15290e76937163bb7", + "url": "https://api.github.com/repos/drupal/core/zipball/dddd242b74f40df892a7f16a48245c3b76d9b003", + "reference": "dddd242b74f40df892a7f16a48245c3b76d9b003", "shasum": "" }, "require": { @@ -2528,13 +2528,13 @@ ], "description": "Drupal is an open source content management platform powering millions of websites and applications.", "support": { - "source": "https://github.com/drupal/core/tree/10.2.3" + "source": "https://github.com/drupal/core/tree/10.2.5" }, - "time": "2024-02-07T22:44:48+00:00" + "time": "2024-04-03T07:19:20+00:00" }, { "name": "drupal/core-composer-scaffold", - "version": "10.2.3", + "version": "10.2.5", "source": { "type": "git", "url": "https://github.com/drupal/core-composer-scaffold.git", @@ -2578,22 +2578,22 @@ "drupal" ], "support": { - "source": "https://github.com/drupal/core-composer-scaffold/tree/10.2.3" + "source": "https://github.com/drupal/core-composer-scaffold/tree/10.2.5" }, "time": "2024-01-26T14:59:30+00:00" }, { "name": "drupal/core-recommended", - "version": "10.2.3", + "version": "10.2.5", "source": { "type": "git", "url": "https://github.com/drupal/core-recommended.git", - "reference": "ee5d148455ca5792108a1fd007ae162ea2ffe859" + "reference": "bd7fe9e734a82762814d9c31255cd362d9c044f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-recommended/zipball/ee5d148455ca5792108a1fd007ae162ea2ffe859", - "reference": "ee5d148455ca5792108a1fd007ae162ea2ffe859", + "url": "https://api.github.com/repos/drupal/core-recommended/zipball/bd7fe9e734a82762814d9c31255cd362d9c044f1", + "reference": "bd7fe9e734a82762814d9c31255cd362d9c044f1", "shasum": "" }, "require": { @@ -2602,7 +2602,7 @@ "doctrine/annotations": "~1.14.3", "doctrine/deprecations": "~1.1.2", "doctrine/lexer": "~2.1.0", - "drupal/core": "10.2.3", + "drupal/core": "10.2.5", "egulias/email-validator": "~4.0.2", "guzzlehttp/guzzle": "~7.8.1", "guzzlehttp/promises": "~2.0.2", @@ -2663,9 +2663,9 @@ ], "description": "Core and its dependencies with known-compatible minor versions. Require this project INSTEAD OF drupal/core.", "support": { - "source": "https://github.com/drupal/core-recommended/tree/10.2.3" + "source": "https://github.com/drupal/core-recommended/tree/10.2.5" }, - "time": "2024-02-07T22:44:48+00:00" + "time": "2024-04-03T07:19:20+00:00" }, { "name": "drupal/ctools", @@ -11254,16 +11254,16 @@ }, { "name": "drupal/core-dev", - "version": "10.2.3", + "version": "10.2.5", "source": { "type": "git", "url": "https://github.com/drupal/core-dev.git", - "reference": "8c1bf854f2cf47d4f06918099ea866ce2471b2c6" + "reference": "71d714deff8c277b8ef2f331f3bddbba03274dc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-dev/zipball/8c1bf854f2cf47d4f06918099ea866ce2471b2c6", - "reference": "8c1bf854f2cf47d4f06918099ea866ce2471b2c6", + "url": "https://api.github.com/repos/drupal/core-dev/zipball/71d714deff8c277b8ef2f331f3bddbba03274dc1", + "reference": "71d714deff8c277b8ef2f331f3bddbba03274dc1", "shasum": "" }, "require": { @@ -11271,7 +11271,7 @@ "behat/mink-browserkit-driver": "^2.1", "behat/mink-selenium2-driver": "^1.4", "colinodell/psr-testlogger": "^1.2", - "composer/composer": "^2.6.4", + "composer/composer": "^2.7", "drupal/coder": "^8.3.10", "instaclick/php-webdriver": "^1.4.1", "justinrainbow/json-schema": "^5.2", @@ -11304,9 +11304,9 @@ ], "description": "require-dev dependencies from drupal/drupal; use in addition to drupal/core-recommended to run tests from drupal/core.", "support": { - "source": "https://github.com/drupal/core-dev/tree/10.2.3" + "source": "https://github.com/drupal/core-dev/tree/10.2.5" }, - "time": "2023-12-12T22:01:45+00:00" + "time": "2024-02-14T18:07:20+00:00" }, { "name": "google/protobuf", From 7ebfcaf072e3ea8924f3a1cf972906fcb9692546 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 24 Apr 2024 18:01:25 +0200 Subject: [PATCH 063/221] fix(SLB-345): decrease title font size and remove max-width --- .../ui/src/components/Organisms/PageHero.tsx | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index c7c18b202..ca227836c 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -115,36 +115,34 @@ function FormHero(props: NonNullable) { function NoImageHero(props: NonNullable) { return ( -
+
-
-

- {props.headline} -

- {props.lead ? ( -

{props.lead}

- ) : null} - {props.ctaText && props.ctaUrl ? ( - + {props.headline} + + {props.lead ? ( +

{props.lead}

+ ) : null} + {props.ctaText && props.ctaUrl ? ( + + - - - - {props.ctaText} - - ) : null} -
+ + + {props.ctaText} + + ) : null}
); From f044a2d6dc83655f95334cd8fe31b7b693556a42 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 24 Apr 2024 18:06:25 +0200 Subject: [PATCH 064/221] fix(SLB-346): reduce vertical spacing between Hero and content --- .../src/components/Organisms/PageDisplay.tsx | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 975d447c0..1b3a2d212 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -15,54 +15,52 @@ export function PageDisplay(page: PageFragment) {
{page.hero ? : null} -
+
-
- {page?.content?.filter(isTruthy).map((block, index) => { - switch (block.__typename) { - case 'BlockMedia': - return ; - case 'BlockMarkup': - return ; - case 'BlockForm': - return ; - case 'BlockImageTeasers': - return ( - // TODO: Implement BlockImageTeasers -
- BlockImageTeasers goes here -
- ); - case 'BlockCta': - return ; - case 'BlockImageWithText': - return ( - // TODO: Implement BlockImageWithText -
- BlockImageWithText goes here -
- ); - default: - throw new UnreachableCaseError(block); - } - })} -
+ {page?.content?.filter(isTruthy).map((block, index) => { + switch (block.__typename) { + case 'BlockMedia': + return ; + case 'BlockMarkup': + return ; + case 'BlockForm': + return ; + case 'BlockImageTeasers': + return ( + // TODO: Implement BlockImageTeasers +
+ BlockImageTeasers goes here +
+ ); + case 'BlockCta': + return ; + case 'BlockImageWithText': + return ( + // TODO: Implement BlockImageWithText +
+ BlockImageWithText goes here +
+ ); + default: + throw new UnreachableCaseError(block); + } + })}
From f3e702125d8d0f8d505dd3cb070f319e25589f65 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Thu, 25 Apr 2024 07:26:22 +0200 Subject: [PATCH 065/221] fix(SLB-337-334): add dark overlay and vertically center the content --- .../ui/src/components/Organisms/PageHero.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index ca227836c..55a1088ca 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -62,18 +62,21 @@ function DefaultHero(props: NonNullable) { function FormHero(props: NonNullable) { return (
-
+
{props.image ? ( - {props.image.alt} + <> + {props.image.alt} +
+ ) : null} -
+

{props.headline}

@@ -117,7 +120,7 @@ function NoImageHero(props: NonNullable) { return (
-

+

{props.headline}

{props.lead ? ( From 71b1e8a9da3c8a2faea3f1e626ed6007c41cf6a6 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 17:13:15 +0300 Subject: [PATCH 066/221] feat(SLB-297 SLB-296): replace the core quote block with a custom one --- apps/cms/config/sync/gutenberg.settings.yml | 6 +- packages/drupal/gutenberg_blocks/css/edit.css | 5 + .../GutenbergValidator/QuoteValidator.php | 40 +++++++ .../gutenberg_blocks/src/blocks/quote.tsx | 100 ++++++++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/quote.tsx diff --git a/apps/cms/config/sync/gutenberg.settings.yml b/apps/cms/config/sync/gutenberg.settings.yml index e58422162..ed9ef9d7d 100644 --- a/apps/cms/config/sync/gutenberg.settings.yml +++ b/apps/cms/config/sync/gutenberg.settings.yml @@ -4,12 +4,12 @@ page_template_lock: all page_allowed_blocks: core/paragraph: core/paragraph core/list: core/list - core/quote: core/quote core/table: core/table core/all: 0 core/image: 0 core/heading: 0 core/gallery: 0 + core/quote: 0 core/audio: 0 core/button: 0 core/buttons: 0 @@ -75,6 +75,7 @@ page_allowed_drupal_blocks: drupalblock/all_core: 0 page_title_block: 0 drupalblock/all_forms: 0 + masquerade: 0 user_login_block: 0 drupalblock/all_lists_views_: 0 'views_block:content_recent-block_1': 0 @@ -91,3 +92,6 @@ page_allowed_drupal_blocks: system_branding_block: 0 node_syndicate_block: 0 drupalblock/all_user: 0 + drupalblock/all_webform: 0 + webform_block: 0 + webform_submission_limit_block: 0 diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index f4a49c277..1c603451e 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -12,6 +12,11 @@ content: ''; } +.gutenberg__editor blockquote .quote-author, +.gutenberg__editor blockquote .quote-role { + text-align: right; +} + .gutenberg__editor .container-wrapper { display: block; position: relative; diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php new file mode 100644 index 000000000..1f9f9d9c9 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php @@ -0,0 +1,40 @@ + [ + 'field_label' => $this->t('Quote text'), + 'rules' => ['required'], + ], + 'author' => [ + 'field_label' => $this->t('Quote author'), + 'rules' => ['required'], + ], + ]; + } + +} diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx new file mode 100644 index 000000000..1dacdf98b --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -0,0 +1,100 @@ +import { RichText } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; +import { compose, withState } from 'wordpress__compose'; +import { dispatch } from 'wordpress__data'; + +import { cleanUpText } from '../utils/clean-up-text'; +import { DrupalMediaEntity } from '../utils/drupal-media'; + +declare const Drupal: { t: (s: string) => string }; + +const { t: __ } = Drupal; + +// @ts-ignore +const { setPlainTextAttribute } = silverbackGutenbergUtils; + +// @ts-ignore +registerBlockType(`custom/quote`, { + title: __('Quote'), + icon: 'format-quote', + category: 'text', + attributes: { + text: { + type: 'string', + }, + author: { + tpye: 'string', + }, + role: { + type: 'string', + }, + mediaEntityIds: { + type: 'array', + }, + }, + // @ts-ignore + edit: compose(withState())((props) => { + return ( +
+ { + props.setAttributes({ + text: cleanUpText(text), + }); + }} + /> + { + setPlainTextAttribute(props, 'author', author); + }} + /> + { + setPlainTextAttribute(props, 'role', role); + }} + /> + { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> +
+ ); + }), + save() { + return null; + }, +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 6eae568bf..2763bdad8 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -8,3 +8,4 @@ import './blocks/image-teasers'; import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; +import './blocks/quote'; From b5de2661bf1c88ed9ffad64eec007bd46f8c0feb Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 17:28:27 +0300 Subject: [PATCH 067/221] feat(SLB-297 SLB-296): quote graphql block type --- packages/drupal/gutenberg_blocks/src/blocks/quote.tsx | 2 +- packages/schema/src/schema.graphql | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index 1dacdf98b..f42c3f82d 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -46,7 +46,7 @@ registerBlockType(`custom/quote`, { keepPlaceholderOnFocus={false} onChange={(text) => { props.setAttributes({ - text: cleanUpText(text), + text: cleanUpText(text, ['strong']), }); }} /> diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index e3b54ace8..15f964aba 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -200,6 +200,7 @@ union PageContent @resolveEditorBlockType = | BlockImageTeasers | BlockCta | BlockImageWithText + | BlockQuote type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -257,6 +258,13 @@ enum ImagePosition @default @value(string: "left") { right } +type BlockQuote @type(id: "custom/quote") { + text: Markup @resolveEditorBlockAttribute(key: "text") + author: String @resolveEditorBlockAttribute(key: "author") + role: String @resolveEditorBlockAttribute(key: "role") + image: MediaImage @resolveEditorBlockMedia +} + input PaginationInput { limit: Int! offset: Int! From dbd22bd9df2a69ee8e88a0b7b8de3067d12c089a Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 17:42:21 +0300 Subject: [PATCH 068/221] feat(SLB-296): organism placeholder for quote blocks --- packages/schema/src/fragments/Page.gql | 1 + packages/schema/src/fragments/PageContent/BlockQuote.gql | 9 +++++++++ .../src/components/Organisms/PageContent/BlockQuote.tsx | 7 +++++++ packages/ui/src/components/Organisms/PageDisplay.tsx | 3 +++ 4 files changed, 20 insertions(+) create mode 100644 packages/schema/src/fragments/PageContent/BlockQuote.gql create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 0a78d9dea..cbf2227eb 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -34,6 +34,7 @@ fragment Page on Page { ...BlockImageTeasers ...BlockCta ...BlockImageWithText + ...BlockQuote } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockQuote.gql b/packages/schema/src/fragments/PageContent/BlockQuote.gql new file mode 100644 index 000000000..b50478e0a --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockQuote.gql @@ -0,0 +1,9 @@ +fragment BlockQuote on BlockQuote { + text + author + role + image { + source(width: 1536, sizes: [[768, 768], [1536, 1536]]) + alt + } +} diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx new file mode 100644 index 000000000..4676f6302 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -0,0 +1,7 @@ +import { BlockQuoteFragment } from '@custom/schema'; +import React from 'react'; + +export function BlockQuote(props: BlockQuoteFragment) { + console.log('Block quote props:', props); + return <>; +} diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 1b3a2d212..86813de03 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -8,6 +8,7 @@ import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; import { BlockMarkup } from './PageContent/BlockMarkup'; import { BlockMedia } from './PageContent/BlockMedia'; +import { BlockQuote } from './PageContent/BlockQuote'; import { PageHero } from './PageHero'; export function PageDisplay(page: PageFragment) { @@ -57,6 +58,8 @@ export function PageDisplay(page: PageFragment) { BlockImageWithText goes here
); + case 'BlockQuote': + return
; default: throw new UnreachableCaseError(block); } From d8b802ce8d14b65a0c924a2a673528bdcfadc56f Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 20:26:22 +0300 Subject: [PATCH 069/221] chore(SLB-297 SLB-296): update the quote text from text to quote --- .../Validation/GutenbergValidator/QuoteValidator.php | 2 +- packages/drupal/gutenberg_blocks/src/blocks/quote.tsx | 10 +++++----- .../schema/src/fragments/PageContent/BlockQuote.gql | 2 +- packages/schema/src/schema.graphql | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php index 1f9f9d9c9..53df53041 100644 --- a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/QuoteValidator.php @@ -26,7 +26,7 @@ public function applies(array $block): bool { */ public function validatedFields($block = []): array { return [ - 'text' => [ + 'quote' => [ 'field_label' => $this->t('Quote text'), 'rules' => ['required'], ], diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index f42c3f82d..1aea31b6a 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -19,7 +19,7 @@ registerBlockType(`custom/quote`, { icon: 'format-quote', category: 'text', attributes: { - text: { + quote: { type: 'string', }, author: { @@ -37,16 +37,16 @@ registerBlockType(`custom/quote`, { return (
{ + onChange={(quote) => { props.setAttributes({ - text: cleanUpText(text, ['strong']), + quote: cleanUpText(quote, ['strong']), }); }} /> diff --git a/packages/schema/src/fragments/PageContent/BlockQuote.gql b/packages/schema/src/fragments/PageContent/BlockQuote.gql index b50478e0a..785efb620 100644 --- a/packages/schema/src/fragments/PageContent/BlockQuote.gql +++ b/packages/schema/src/fragments/PageContent/BlockQuote.gql @@ -1,5 +1,5 @@ fragment BlockQuote on BlockQuote { - text + quote author role image { diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 15f964aba..47ca8af98 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -259,7 +259,7 @@ enum ImagePosition @default @value(string: "left") { } type BlockQuote @type(id: "custom/quote") { - text: Markup @resolveEditorBlockAttribute(key: "text") + quote: Markup @resolveEditorBlockAttribute(key: "quote") author: String @resolveEditorBlockAttribute(key: "author") role: String @resolveEditorBlockAttribute(key: "role") image: MediaImage @resolveEditorBlockMedia From dc25b433a42828898be20d46733921fcd71b3ebc Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 16 Apr 2024 20:44:05 +0300 Subject: [PATCH 070/221] test(SLB-297): update default content and schema tests to include quote blocks --- .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 4 ++++ .../ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml | 1 + tests/schema/specs/blocks.spec.ts | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index 61b3a4ac9..6a4e6eded 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -118,6 +118,8 @@ default: + +

@@ -199,6 +201,8 @@ translations: + + format: gutenberg summary: '' diff --git a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml index 53ade0b64..5ad90259d 100644 --- a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml +++ b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml @@ -73,6 +73,7 @@ default:

+ format: gutenberg summary: '' diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index b5cf47cf5..1f26435dd 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -63,6 +63,14 @@ test('Blocks', async () => { markup } } + ... on BlockQuote { + quote + author + role + image { + __typename + } + } } } { @@ -194,6 +202,15 @@ test('Blocks', async () => { "text": "CTA with link to media", "url": "/media/[numeric]", }, + { + "__typename": "BlockQuote", + "author": "John Doe", + "image": { + "__typename": "MediaImage", + }, + "quote": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.", + "role": "Project manager", + }, { "__typename": "BlockMarkup", "markup": " @@ -253,6 +270,13 @@ test('Blocks', async () => { ", }, }, + { + "__typename": "BlockQuote", + "author": "Jane Doe", + "image": null, + "quote": "In vitae diam quis odio tincidunt faucibus eget ut libero", + "role": null, + }, ], "hero": { "__typename": "Hero", From 05e3ccac04c543373b53a3d2556ba6badd788597 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Tue, 16 Apr 2024 14:51:14 +0000 Subject: [PATCH 071/221] style: adjust quote styling --- .../Organisms/PageContent/BlockMarkup.tsx | 19 +++++++++++++++++++ packages/ui/src/tailwind.css | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index 54f29a9fb..443b8fd19 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -49,6 +49,25 @@ export function BlockMarkup(props: BlockMarkupFragment) { ); }, + blockquote: ({ children }: PropsWithChildren<{}>) => { + return ( +
+ + + + {children} +
+ ); + }, }} markup={props.markup} /> diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index f9b68485a..39d6591e2 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -4,3 +4,9 @@ @tailwind base; @tailwind components; @tailwind utilities; + +/* Prose overrides */ +.lg\:prose-xl + :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { + padding-left: 0 !important; +} From 0a95fe55b5514fab24b52bc0aa533da6c842465d Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 17 Apr 2024 09:02:17 +0000 Subject: [PATCH 072/221] style: add block quote styling --- .../Organisms/PageContent/BlockMarkup.tsx | 1 - .../PageContent/BlockQuote.stories.ts | 23 ++++++++++ .../Organisms/PageContent/BlockQuote.tsx | 46 +++++++++++++++++-- packages/ui/src/tailwind.css | 14 ++++++ 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index 443b8fd19..a83f81b79 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -18,7 +18,6 @@ export function BlockMarkup(props: BlockMarkupFragment) { className={clsx([ 'mx-auto max-w-3xl prose lg:prose-xl mt-10', 'prose-a:text-indigo-600', - 'prose-blockquote:border-indigo-200', 'prose-em:text-indigo-600', 'prose-strong:text-indigo-600', 'marker:text-indigo-600 marker:font-bold', diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts new file mode 100644 index 000000000..e5efd0c53 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -0,0 +1,23 @@ +import { Markup } from '@custom/schema'; +import Portrait from '@stories/portrait.jpg?as=metadata'; +import { Meta, StoryObj } from '@storybook/react'; + +import { image } from '../../../helpers/image'; +import { BlockQuote } from './BlockQuote'; + +export default { + component: BlockQuote, +} satisfies Meta; + +export const Quote = { + args: { + role: 'test role', + author: 'Author name', + image: { + source: image({ src: Portrait.src, width: 50, height: 50 }), + alt: 'Portrait', + }, + quote: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 4676f6302..adab18a20 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -1,7 +1,47 @@ -import { BlockQuoteFragment } from '@custom/schema'; +import { BlockQuoteFragment, Html, Image } from '@custom/schema'; import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { - console.log('Block quote props:', props); - return <>; + return ( +
+
+ + Quote Symbol + + + {props.quote && } +
+ {props.image && ( + {props.image.alt + )} +
+ {props.author &&

{props.author}

} +
+ {props.role && ( +

+ / + + {props.role} + +

+ )} +
+
+
+ ); } diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 39d6591e2..f6eb52ad0 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -10,3 +10,17 @@ :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { padding-left: 0 !important; } + +.prose + :where(blockquote p:first-of-type):not( + :where([class~='not-prose'], [class~='not-prose'] *) + )::before { + content: '' !important; +} + +.prose + :where(blockquote p:first-of-type):not( + :where([class~='not-prose'], [class~='not-prose'] *) + )::after { + content: '' !important; +} From 866e1e6cc8ca1004fe297a09555524a0ce157744 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 18 Apr 2024 05:02:12 +0000 Subject: [PATCH 073/221] chore: adjust image for quote uthor --- .../Organisms/PageContent/BlockQuote.stories.ts | 4 ++-- packages/ui/static/stories/avatar.jpg | Bin 0 -> 6782 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 packages/ui/static/stories/avatar.jpg diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts index e5efd0c53..ffe6090f1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -1,5 +1,5 @@ import { Markup } from '@custom/schema'; -import Portrait from '@stories/portrait.jpg?as=metadata'; +import Avatar from '@stories/avatar.jpg?as=metadata'; import { Meta, StoryObj } from '@storybook/react'; import { image } from '../../../helpers/image'; @@ -14,7 +14,7 @@ export const Quote = { role: 'test role', author: 'Author name', image: { - source: image({ src: Portrait.src, width: 50, height: 50 }), + source: image(Avatar), alt: 'Portrait', }, quote: diff --git a/packages/ui/static/stories/avatar.jpg b/packages/ui/static/stories/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e796b545eff1cbe835334f4ddba927c3802c719b GIT binary patch literal 6782 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 W!8{yho95C(Tb0H<%=AkcX#5Ybp@(Sz literal 0 HcmV?d00001 From a67450f150ed4995a694fe4eabf06dd1839b65a1 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 18 Apr 2024 05:24:01 +0000 Subject: [PATCH 074/221] chore: update block test image check --- tests/e2e/specs/drupal/blocks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index ec7dc6a1b..1f54560a8 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -27,7 +27,7 @@ test('All blocks are rendered', async ({ page }) => { page.locator( 'img:not([data-test-id=hero-image])[alt="A beautiful landscape."]', ), - ).toHaveCount(1); + ).toHaveCount(2); await expect(page.locator('figcaption:text("Media image")')).toHaveCount(1); // Video From 8e3c03f4a76e4610dc5229c2079c105e554074dd Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 19 Apr 2024 13:02:24 +0000 Subject: [PATCH 075/221] style: spacing adjustments --- .../components/Organisms/PageContent/BlockQuote.tsx | 10 +++++----- packages/ui/src/tailwind.css | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index adab18a20..6c1abaad9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -29,13 +29,13 @@ export function BlockQuote(props: BlockQuoteFragment) { alt={props.image.alt || 'Author image'} /> )} -
- {props.author &&

{props.author}

} +
+ {props.author &&

{props.author}

}
{props.role && ( -

- / - +

+ / + {props.role}

diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index f6eb52ad0..2d464b850 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -11,6 +11,11 @@ padding-left: 0 !important; } +.prose :where(blockquote p):not( :where([class~='not-prose']) ) { + margin-top: 12px !important; + margin-bottom: 12px !important; +} + .prose :where(blockquote p:first-of-type):not( :where([class~='not-prose'], [class~='not-prose'] *) From e34e4b80f27efcf93ef78c80f3b9040ce6461e17 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 22 Apr 2024 14:48:09 +0300 Subject: [PATCH 076/221] test(SLB-297): update quote tests --- .../2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml | 27 +++++++ .../content/file/the_silverback.jpeg | Bin 0 -> 146269 bytes .../5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml | 74 ++++++++++++++++++ .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 13 +-- .../ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml | 4 - tests/e2e/specs/drupal/blocks.spec.ts | 10 +-- tests/schema/specs/blocks.spec.ts | 4 - 7 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml create mode 100644 packages/drupal/test_content/content/file/the_silverback.jpeg create mode 100644 packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml diff --git a/packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml b/packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml new file mode 100644 index 000000000..8947e3bbc --- /dev/null +++ b/packages/drupal/test_content/content/file/2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7.yml @@ -0,0 +1,27 @@ +_meta: + version: '1.0' + entity_type: file + uuid: 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7 + default_langcode: en +default: + uid: + - + target_id: 1 + filename: + - + value: the_silverback.jpeg + uri: + - + value: 'public://2024-04/the_silverback.jpeg' + filemime: + - + value: image/jpeg + filesize: + - + value: 146269 + status: + - + value: true + created: + - + value: 1713785099 diff --git a/packages/drupal/test_content/content/file/the_silverback.jpeg b/packages/drupal/test_content/content/file/the_silverback.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..25cc96bcef3f34d50dac92b3fb29a0d4a7e97881 GIT binary patch literal 146269 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

$cHaNN!u^wefP7j>fm116{1@N<8PkIg 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

!EV5Nfp^&=_H`_?ky zaAdof=FLg&Y|_=%nr_1!UPW z_Y?{~)?Dn4rc~#qpZUGIhtAe7rq6{hzR&Ueg@woZ;PE7e*-yPF59zfH5Srou4bufC zNbl^5K(xKGjMbUelF0nEEkbyst1%G0oBBHd6)GKy26jYaY;SBnsppZ=(o4?3_Or() zl3-!MK}iZW_9G`h8*4ceka;V?9vI}N`Q{ZR0G|ibw2jhZHKDZb#p5|`2LG=HZ3mKG zU{Jg~cg(!ZE@#*frh)8gBMD5SKb!phP*}sl2L>+KtMi(zzx25~U9=>La0s^9Y1ZN5 znfOb1k#iLRh@|6*`+S;P!ueh0qMs0f{i+GAlD@fZ-#{}PP6WtBW?W1JyL_SA%rmMF#wlWP= zZPa-a0O0QQ+x*HYW76}1?)yO03|#4ENw>*BGl%f`2>4=ZXPX;2RLe0MRe$nV!eG;1^MMfXf z!!K@=461Ho)jTB*xd|9Teb&cQmr+Vo4>np7AL^{A%naWOA%O~bW%K{`D{br@{pLKdxWJZN8>p5B-Zci8|n16d#6Jv#g-Qk*@}|E1*+ z=0TS=@8{TBWX)z1j5UhhJ8eOq> zSLFCN6#0K6ZTHu96p{)0nF%jj)++ONsjoC*-!*mb{2`bNSdt!kgJ3?UwG_ToxQezI z7RB>oU(`=+beZr?RmEboZ2RC*ov_7irL{|{@y^N)@p{9vrPIQW(1%xc=vQS^i&!<2CWl$e@4t1^K^60c&K-7gWNBH1wz*V$#UO z#|gX1?E|q<1_WZK$PbO)A7A-n?Y9tHq)BJ=@z0!8+f&BtO&x~P^dyE}_3;Ldqu*4C zfbYgeQZA{r4EK9Gu>{likFPpei}@j1^qmY5#UW@reZG*x=68Xw)Qd0evfwNvQiAZu zQC_F{By9*|Yb>r+D{$`R{hOG=vcy=3LEK@*zTxjtpk>GlDXsfXOk^nlEqm&!?8rBn z*-x4hfjm4a$VVu4v4BHmOj=~+)I%$H)$ef7orMHcD)km%jLmz8d+x>KYup~w*HN1D zz`Ks3+ln^H^!uL$Us4ojHVV;QR1N>&#M}|D@l#%uDkjBDqIpmuQJ7 zLlY=Hv#kC^-D_r?O89+dX&I9eM(T5CK0i7b<23Z6lA>R(4G)6WFhc*o6qZbN-3`zL98ty9vz{?V)2}IJ zZ_~mP%u!JQ(%}e;t}NqA%6#OzG%=V06UXWGwhX2zfNHaBvI|^&`&Bfgh72V-|OtiGUtddoRxK0!c5>5>a?*|V8JmW+k<74oFXO0vJBeF38SMhPcTYU zoMg-$`HGV&NAj&-J(Eo+Q>(Cff}-&`B+_!2v-s^023K=3&&g@@m^`gTEFE;S_2e|~y@e!gu&qucU2 zN<|!oIaD7K4yD-$7eTsT2{Kef0RA|x#1Q!GD>;_xGA#(8bGsVS;GdFg3h9lL>Ex1_ zF-qJxE8h%~k53!~-#)0WCCP4OdEW=s(-%9*2g^YcX^EVfp4C#(?nKXxjnz^X1?}Yw zn|PsTU)A1HuH%5br2s^tHoCH%3$Fweu{rQ$MxrP)T=o(dqaCOU!k4uZGm>M&YbjRn z=;eKtJ{`+bWHu7R&hnWPJj>6t<5A29_0f9w*z_ks;U3I6T(#n_y}>_haRk&~{f_N* z#S|?_i4XNFdp)i~{YDk;hvr<5zX4R(~%9FBpU+=Ngx0$<_xl9z4Sz`dqF# zoi?PJHe0Kggv3FdTRWn&h9mE{u?tU^^&$ax`Ce$dZcEpBy4CN*x~PHe{|w#FYVLn% zA1wV|M*O~J*(RB`OtfNg!eufYE%SD3G|L*HhfS(Us-dFo>co$D=(k zo)V`)Yx&(xyz>*z;yYvwFz~i@`{t!z2ojK`=Xuh;5tnD*#UafIjd%glrKRtDYLN^rA)D<>wEU$U>LG9UN&kZPJzfv##)8UW8z_K z7Y{o`QJ9cOLQ@Z0v})wVoN?ZUCoE|fVbY_XaKbqOA!+pA)u!DoJ&OCNQx* z!GxT3Tp>k+2>as0F-?@WI`N7NFF0rTR*n;d{M(o5tw@^1tVOMe5<=5JOf=cXE($6T z<51om8eKlZANvhJtZbO>NfFA`n2KuxP21}>Be7%@*w4o{QgH9&+DzRNG!yhys<3!$ z?sXxiBm$*9$d6x^*g23z#DK-np@3dXAFPS3`VYBo8A>4$p-atWig1a=6(#g8D_GKpHM}#3#dEPH@fAgZD--d0T44l6V5mBn}T+F1zsh6Q^?iJf3?>|!h21&X^ zTx3tyL{+2@D!*b+Ldo*~s!^pl*L6Ylc&d@-GPXvT`bchbeDuMY+FNPORxODIgq&6U z3AlKTA>sL$uBKGb+1v#Mk?wMg$rf&O^VsLz+|nAvKvn3r z!ZU0S2AL2+qXKrrrtN=tcB-QT*`-S=`}^ZjkNMEWK2>()1Hv2>5sJ|FFm0=co@aB+4M+FpJ z3zRjSZC@gA*^~1?Fq2@+hHuT9#vKarcGdH36C)K|&p{Xu7Hy*(#-3K;*Ozc>`1pYa z@26X_&R10Mxyw9jHhpXmQQ*^I(anl@A~osi2jRlM5oVv?v5UPmo=Fi~3qB(I_Fcyv zVB|EJ)>vUF5V94=GCn$M7_#}Xo?#d~<1zb9nDqHzR7Cmh*Iqc;XiRQvEij0Ah{i0| zq};vlwS0N43&YgQ2R#~U92fv`{mhK~3t}3}&%9QiV+paRbduLRKiUrYKM}YbS4tB1 z-%a}JMnpk<)|j^xlq*|yR7+BZ-mGqpNB-P#ewxfL+Jmu0qn4ct*)0Z}c*jX1bhs{UpdTBZ%lc@Cke=BD-8S!x< z^|Y&*R8X^5+crI9rZ)~4K)RM7Ly#4SaIE4p+qqY-E?0;?cQk~x+wY8}eE%Wm52`55 z%cyX{d$?Fiq_mRnX85_Otqe3nHeR5;&&|NTzB@-$XGEhR;)zad>$J^>ovk)TK7lv9 zeEmk~z`#l#48snYSEcc)!tlUgRO;K2>?k#*spurm{=EMq|NS7?{fz;G{k&|L;61#8 zVH%ab{tq4wkU%u!dAE3BCBgjUT3Ey6c*$Fa|6166b%y?MB7k_6QhqCz*+fMJh3T)4 zYDsb9->|ceN~Y6Km6cn(Fff74I^pNytD6RQv!2{7XmLFOzuQ}dcXcHdE{XRNQ)IO+hHuz#@MTMTVzss$!x{Gk3X?uqOh5FpM-VW$Q38W1%-lN@ zqo)Q119nT-iX4se=q&}yvh>Lpel|_XJLzsYPB4VZEw>mGJZZ$VA(q77Zc%0rA#dDi zK|mT zMF>%w$@^Lvsw0Z%64^(u9)2b;OF{%U!jJrj5uLwk<7Qc zaiV)Cwzz%f#H%O>G9{t?6uJP-6lQ5^YGf$XJU@OLefF5ZFG!ayFEw%_nxRe+zm+#97hzDcqUD_YijWkgiK zIg%20J;Nw6Odo(@g4P$y+@wc>-cYTAE{2t!`PhsbSl{85LGWS9>5FnI6zpr-FuOHP z*wkmyX*E~x$kcPApx@91Yi|{eO5QE7M~1SHIAWWzEz7#RW^z5568;H0UE&Z=Y21B zD$x1j*OD`6A5FzwQKQR_YC$PoO=GD|BgSR+o<{rB0T`P^g8eiFpapfG3Csg+f+Jq8 zd@n*Z)}c|h4k7lmkEW$9s(%{fE|6h$ROahSK>T>hlZNe8#}=!zD}iWs7IXEJ?5_)+ zzR!VS8b3tmIsIRL<{5#xdoe=%%c)RBIfO7xTkz(RWR&@-M3@Vv?QmJbbint)U>vSE z25UYr^dhaw3Lm_XuR?5JxJ$X*d5a;%H^AwPdLG~B>icYCH4H%ocU^bV)$j}u){#=D z=3NH_UNf=9W0#>oH*rQTxwr(=FpC=y=duxDU%-yxoD+mtBO~%!Mc67W6ME0mHkqy1 zocU=k+j5vhQc8fQJr3T+VSYi{{{XLTI01saZr}_Ji#=xZ)EL0Gm_2&2Yr0k`zY>#1 z$UH9&7OJ#&f+>=kTc-(QeLDasg9UjD_$0_Mgf54}KZT~z(?SP`>zz%-f}ldNS24wC z7@k%CuYX$M#!!K%TNU*4zAse+FT|+$Fpo z68VQ)q}tH;M*@DS1Gw}(Cdx(ER^4F6!{ppYD0OFVM#$mo4O)Zyr*EH}Q5**H<6HP+ zhOvYTlVF@Le;a${8XqEDq5Zp;$a(!_h?AoCDM^1Ik;sTQaPCObluVSQ_sP7M5X6X8 z!s)h{;~haY$mv3p*D(Riq#s;u5gg+~yEsh~e85#bXR7lhY!6UySo^b;u_eO#FwNtc zJ0#OT`erc#JuOQrP=#UNWseMm?t6ceN5g_#sRNOSZ6j|;A1TW?(vA}J_X(!{uweN( z3jU1btaHjl7R7&c#>D8^AoD@wQa1n7ft@W5&3CsOh^8-I!tpsV3fq`=1F?V%keCru z_$T(jSsjif3po5;s@M7xsC)eT9%g7+Yr;8Rvv?c_HYKY6*DQMD&d%btZr6U}Y8F0K zIAj3XG~34FE|MDNV+1|xZ4fV-nhmItP#-+dk70IumRKfO%nzcE)BAnouxiFTBEn8i zkm2N8*d)DVumOHHQwT$2`y1WcnXF@pvyF;SU6zaQom07Un3A*gYH@1#(HsheskE)i zB;dTu6(D3D2TzmQTjgF=rn>9eG1%+d_%)|mnj!n#vPnj%+&XiPt*j-%Zn2d(4*`Ad zZ=S_kDj3jZ&}GK>Hv>>AJrPBc3nbXVb|W$FEA2bYrFrQG?UPMSHN6h}SHO?@b0t?< zji<0AQ*d|cEcA4)=?CNu0y}xw~rCi2>JnuJtrfNa_F%Dt%0kZlRA6%Rs+A!wV zy_<3b(NQWrggi5MED@v=M#y2Pgc&l8+=_fY{uJ2RKRF^sE(BD9Ht~iw2y7->*RzP` z>~Hz0^BN zUdAz6d5<4m&#^WPah@SsUID?%k|bu?MzeXxaQ&v&w~-BB?~Df{E+hSn=5D+r&h8)P z^j$V(TdVU!L?o-7_I8lgP~XaU+1vMKI~x-}|RkSf`gS_3SP` zDqdTmAe5w+$w@)+!lLpoU;d8SI3^y$O3X4Rq6tp(2Ijh zwNr4pGK=#jF2l$F_{rrb#fW*Zy%h6NZVl6v)*Aimls*X%;PO|-vXK9xDEK5_K-zO1 zINn#(=0(uCObY&dj*AkNc+N|Go?hFLXOOIpfaRZzv%=jt`=zjP0RtqEtXm^nte&RQ z!90m39ow;6sLT`vYX2cYn0<}?yPk94Y^CMHbl`* z+(gwB6$l~jF%{bsVro;dNoSf%8{ZSrwYQB>ZH}dj<2si%1IHF$kWHjBIKS$X?qRaD z!sJiA=;r)Frs|Lv{oWT#^!tf^ zV4*E)_7y2R;Wt1x2fZ%R$iR0$=W$$F#~I9E&oe1Fn2J`7mX!50>eFOMXtuYvBT{Ks zd?TPaWFko<6vT67GpZnQt*0Oh+rF7b6 zi-=hr#pFN1+H&N!1}D8G_GzEG9Z@N23=(@U<#S!dsT0W97G2#gQ*tniqgkWwEjpgz zGr7Vm?gUvx{n<&p6H&Q47NLSU;l(lRdz2&YnsczK%T8cDZ+s=l+6R|mLA zJ!g?M^N|@=<=VxGCvk#luQT-&yRgGmNcgLSy%R62q`7CV(PMw~?Cjm{Nbd(g#lWQS zql1eQn}?vV)Tr#Qv}>}t)yI!|%Q^*mu}ZaC)HbES=r=b#x!m&+XK|y~m-mzDF^PYi zwoKtK4F3VRS07(Z0v(p*okcA~f|%!?6L=)vgRA5G!|b}YV1d{w7v3u|FpG%w;eak= zaoK$HC22eKmlZ14_}+Duc-t{7BT6Qi37CYoP-W3JwowaC6!!neUeLsuSGm*o3xEJi zM$*RSFR{LXG6X{(AJlHEVAbfhf9`sF*l^l=ijGnqPQ^LMC?M*!dd{D4bBIu^<G%j#{LoU+Zwnd$u(w$URBZM5+cS@EyI0kOj`Hd)SO19wnU-<;4J2*FENdo=yl0gU z`rPkdwCv`|TmBav0l+Rr5bB|?o10BGmb74lsijjGn-Ri4PdFe?ha0haAK)7Tw6#Uj z8V-M?Y#YXcZ@`vHQ}wzwtS4`o;w5K{aMl-fT_XEcR%1M37F2AH(;TtT#>vKFcKbY? z2qYCWJOV4j!j0CuVnqyRta58lUvqj>JziCY_e3^$6oXq~Gg0SlznGW5$Ubk`n5UV7 zAAHn!`}-F_l+VH*Y*G;mKr;p}roZ=Snwh^Je%5JRIp~s9c4l_;?AvaYS*wJ%yzbc( z)MD~jcA%3HB-WzA(l=bnGqVwy%~gu5GdcWp+%jPotSy2AUnn(k{`l7wDK`*_&+%iJ z5n8wGE$fwnFg&O{S05B4EsdbXJefAN@hiBbG=D8}cp3__`E=@`Ws@NvyKCLxo@aYX zNyM}_D)-xt=b(}V8)Q$DYi^o$^S0n^ezK{uGgEej`(|IC)@a;M==Ik+z&jk79$&M1 zyF9iUg8pEEH@^P?bZIYFIHR){W6hur+DVxBGFRp4P5Wf51=Cl>=lJEuzSF)zUz|0f z#F$94D?CybOx69rn?Y?(^5W2$0$H)@K^Nt@ho9{~8$& zzx@vob1l$<`YYla(*{ZQroQ zVeYHeqRhpH1o1THU}8t@31_y~CoY%I3UF>#r#-)aBg93qD0Q&KpKI7UKq*M|`zo|y z7dl3ky{FgIS8wT>3^x*2j)O;SU7{u(@(vpR1Kn(5XnUL)GN5B)J&d~XMqrQ+qGE%A z?KAM~DDy4aRosdc=FUM%1g(1+orKWe4y8a-^}iW?kvTj5+L$a-Mv;_#=PDkrPg>goL@L!+9t_HfLfC4$uToJ)&M7bi4Ok=lMIve=)r$x`N0_L`Kv1u#k ziyfc%)W)JqqyDNa!4YtJ#wcU0CXmKWY3K zT(;L$Gv!pJ1_uRa1zv4PiSNqawF%MJ7QZL*;bNGYZ0LXTv(87LwkY-8ig^FFsgKgW zbD_@i&aL07lMc`RC_B7ZjOznoL+eQLD$skkKU@qI%E2M0;r^FK3M@Hhs^?1)%fMk^ z8Y^oF4o3@3eEy)ctbl(|pK+VdE?9EOm~$(Gv@g9}@uIW3LQhNuE-xy5%(o}s(N!1G zwAYTxDR{;4qwO=l$Hs_FF{2OreYU+RIXKQ;PCF!ibeP zgi2WmJnf16hjM__uNirZZ@A(aEw(&Fr>gru}T>wl~}5(%;bi05x# zk_6w}(__SP`iJqPxKNvAGgH*3Y_7kgBFJ8mcf$oug9`ZA-+|H#IkABv2Uo@Q=X`sK z`%1RVhn!-G>C)={rs%B5Mv8A$I_4$hH?yPDNa%l5XP_Ou2PGf|o*}==g-tQ^~ec5T471&C2CPe%&n6NQf&-dA)Q=UNHQ_ z?3(*a=}JgONE)yLlGB6_$4dHCP$q0};Fx|Mvz?vqS*g9vk;tweCv)tN+)ZT!g@cY)c5c=O zF#;!s*Rj@KCnz}My}UB(#&IYY{jMy(W^C8cWlOikqVr>3BL_)wUesG5I0h}d5+r$> zJX_Wb{P;Ckf$;00(F#B|NoOpMk}$TTTR&vau$PXq;!S~M`9S``+1lykzp{I!IL*V=mYPUJDzuMr z#=i=VGs=*FBeE`^4!)X;z3b}_r?l3IlO*1Zazpjstgb4#OClUh6WfjlW&I2OLRS)+ zJ=M6D|KgsS+;ezcxUrr!p@?-EaRtK8u|Y+MiodDxDA%CD32XHx$y0X2V%F4hpkota3!C5uB(|fa;J7YL&L$ zU`uE>h)CX5h8p010T-^ri=KDscY(Vl0_j8<@@l6(A?(|RkH<7MXUcrEsgS_z{{TE# zNBWfo05BSVSDY9SGCrA~1En4Vqsl*^P-UPTDcIVFp~|o^c~lD%J}lA)P1g3yq?UaA zQ`8#c9F#Kohg~EkyK2q$-|617|M+Be>^|0z;7i9?$gf%sr6IycWMQ>ZDfo5!_TKj% zex&2l8W<$9k9AvY|M1}J^HdVjdK(gqIU!=`s+38j+gl9>^64#y4)Id8=}JOA(gY8=?}Fw32sL&&vGpR`(d# zaFa70X_m}}`2%zDDBc--LGsU>96o>W6i4vD(*PD_dfQEGG38e*W5F1+N6#vj(!oPd zU#5f$jl)mTe~N>}#*4G0Q`Ux(YV#~;=?T3bx!c&_7@6&~JtGhI8Yvq8ek6yYoSgJV zZ~_I5&$YL{l9muH)|}?8;Frotc+wilBGu~&&}F6s`sQ(~I$q)w7ycenBEj$&Hv^v9 zlEUTBoqGu!Zk_2mF6|nW3qO;B<C};?m>wlABp^i<7FHrn#wW)Lc zvX&`Hm(0GI8B3eS-Z6b5=qh$xC|rnSizx~wA(FNS9;SLqSO-ilVwshSAHUd(^mYX= z_u`9f5kJhyJ8!4oMH>$PdgeEB^o*wrf9exV=cs{Z@7!x)Obb7&o%gUZfD-5p9%M?LF>K3ssokg?>as$(L_E!!Teb(&a#e-Q`$m6}gWeP_0HuO&C z+cXxEvK}1t`SK`mM!$(isNPu7xKjhw3v!t?J(_KUsOrYSis^%$`Ng5TthPYbx0=ar z+@ldhP>c|yatQ1dI^ti_dlRN*bJvxI4ikOAK_?Wp8~vO)I}Nc2&w%%?CeBYjth|>% z^ArVLNCf>vLP;YVDTS0BX#3nM(P)n^$E3#}&-D&zCw>G{WG+XEGK~H0$*o+k_T6bP zm5Jl|1|%b1wzO-mFO|k+FR@f5*YkSbcJ4$;-%EjF%-6F~@OrR5%7B8w@H6CC7WbNwA~0StO=k$A*%oDHWcKA-cI&C?{xG(o%h# zzIEpx3whEb`8rb6v3R2eAn6RiOyN_z*h4}lETx0e zcK-^uvy;|uuaV~WvoCCCpulfSTMspXy%S1b-44dM4sBn1ETjqeZKfNz_FxJ~n*O~v z%Sf7{YnRaUP&FTjzbbG2eHSqx)WK|2=>0RE^76%unI9_@oC zgnNxBx|JQBZv+JCMjonJr=}tH*2o&F%E-PMj4<>Ay2mk_39S=IT~E}&<4C%j$XF{V zFJA4Fou)5A@)-m5O&*DqOj;GqP0*rT%mfoPb%aQPmjUzRu68m5SLzix39?OSNyu!7 zcO)<0*?Swir`&8@jZTR-K(0_}#jFl#$iweRy}#FRQc|(@W)~pXV^o@(tx1u5A}jPf z$X6YbA}sopQb$0cZSfVGW>ke&-y{ruJm{bgtL%EC0n;%xgqA2k!D?TDotZI0sv?v9 z+qwzZ8*V0R>%-&-t+XoiV8S#4Z(>0FDyzLa(HaBSF10;`cw}Xx{Mu-QDg-YhM-@}? zZ^*|EADXO{C#Lv%acdlF?Qk7&D7lkabzXndS93BS?c%@;y#GXFS;oM%2b`@)xXx*R zt#b5x|4S0iOk#AZ@*_+3=@Zh6}EV` zb95pL_5!U(Q3K`9-pkdN-04)|P1rwFUT5==wW*6@iB+jCWex~@GGwX_y~NV&C0k9i z<(0sDlGTqFH#GO9Zn6`1k4fozUu5b)>w}2PbUV2z+052P(nMs?oZ{rwzo;!zy~%?i z4lYc2TP^XPlu(E_?Ka(zO^myWRVLk+ zEm&0@Wxhl-?Z@l1N(5tK^&j7puKjHeCIQ8V?5NLu)W6;Ij$rhr2{Wohsw5kO?km67 z*Zy|3&b^5(5}Hke!OTXy68BVYET=x$#M6t4RJh9zw{nRH0%d{6k5MB;L5khK+f*Tg zRv%}mq7av3oUb4g?ieb!#vOZ?l|NJ?vUmxCR>RX@_4rUyqMz^F*a0J+z)sa8SB78N z0L8t7iL_S8Z1v}+!v`bpR$eh}dVYAOK^9E2*#R^Z4Rd=FS4lxW-QG zcOddmNnK*eQe!8-=O6Yg3y{-ZXWL?Tl8(TWwA>M@jcvh8@&I^8&Z&Y&zMk>T7lZL# zb7vO+W_8WS8y09<-p&623qkb0l}XrFcbhK?esyPd{nbzxY!iY+nDwp$*-pHdOQ=tG zWC?-sH>v!qsL|?3X!xx#45mSYAIiD1LxtXfww{YUUGQ88oC2qbJ?5D@%V2I`RD+XT z?!Nd8z9=Vw=khgg(`}3DK@km>gOE9PQe_FOJi-o{Kd?etW-c>?6wY!XV z1dzTcnDVE(H%@J;#KQ?JXRtlL zm2C306DwqOiSHS0w2h!KxHz6*k3a8O)wK|EAOYcyK8L+@rv0$F02ax9c&goAy-*~s z^gQ{4<|;BTQY(5QP010{bvHKAkvw2QQ^5zlFt!;+=>~s*fFQQfuelCXouz+= z1!f>rx;~qFfaHfj4}MJJ%gVFY7G#i1H+Ht@0M0#e-}%u^t>Lz0AEbGTA5lyx00OFH z9~KXB>qgLBE3{9BCAb1l1Ls^Zi|oZl?TEmR44IFh{{U)=U=bvVCFCOpBANTY_;k4{7guJC!S-@taPeJi?KL9Q^4||X>@?8CnNzU%e6Vu^`8JU zv|s~)BZ6zI4nNeTuy+hH$&3;7=7ueRJM+iZkJJLp@g2^2`B1etMgYldk1-Ub1s2%* z8ZJ}?8$yyvF)V-LrKdr_&zJN5RVzhF3VY6bgV>KsPgV*M-f{DT2D;WwCi@Aoi{KJt z=RZvPQm^8UXRwamv^$p|cle`;=jBRYzT)6eC0jeEzn7QFu;k?@{XuMc(h(b@<@~s+ z-FgE~9uhZ)J7OyeRk#R} z{i?h6i6Ts#z#IzQ{ac2EB<-9G5F-Q3kb6^D8e1vCiwoBPkPcuO@1B3PBEAq*v2Dbk zk>mkY8r4y7436NLh4g=s?af(gok58%K$#*Z9D(IcN`&XV#;9C@@qo zCn9k^wY_D6VZ4AbH>IKlm{xRJIMYO z@5L{Ec^Qf4&ZlQfx*f&6>IerLlpbUKs^Ju!GpNmBmQn`<;Q98adR?Y2?D$>)^T$6b z9-Ux;GH36aX|92QCVBacQ*zZ3*&DiuJddZ}=TU1C)4?1cKQUX?vgQO&{82Pbv`HY5 z8%H(Eju$FqNMIfZ=TyGdXi%hrR1A@v5!?`DdE$H3)!W5LJcfxrM!64bZJ*vI_7F^* zL^K#MMB_c{)aY??$xFcT#kP5U{-Dau;+7aBM49g~^5gTO4zkxFalH6M0HFT>)MGfD z)mFOP5)Aog`Qsh9njV;+^3Rl*&uovEdio>KqQgfl3~gZ}!W(P<0N|0FP6b3{C;-9`V{}H!hL}<~{!apKOkKkCg*(802zC6Z16enhUAaIl~j^C;MaT zTe6GSPt5sw_ogot8-nT9ufKt->24)C1lp| zf<%cBPiXSRE=GUb`c~MJoRQ{uAD%x-h*3zSs59LFlA~{snIq36bB<_gz4#gZJionR z`o|TBv&91<#(w<#>Q^JNoaDhDN-zVtf#f8Tdq?*^wX4EU5gdX1Bm0^F+n@n?-I*P; z#~Hxq<~)rD-uCUu^);OrL$)IV7kKHPn zJPF+7bLxIZhpg7#46vcM9zlbjk%1glh5Polhtv^i!vL%ZW(;`B$Co5xtJUI@HjGsj z57jpAECR411eNg4N{EU70D4jS$htrZrBo8-vH*^I8S;)zbr&qP$ROCeZ-y>NZ^(%T zbI79U`bDTV?S-{1EJ}-|hd7fPM;+p`ePS!nZRqn0{eIx^+he!3J5B(O!|rLl2Ti$s zSUGL-WD^n>Bepwv53PCvdn~G0+9|kTpNDkJjiZk}!@o6kqqc}PG)B-21G)Rp<^*|~ z&nH*^0BNjxXXNwEf3uq&+@o*z3IW4=xRA_9#I%J6=4zYS?_!!<8=-CQ_(GR>$&yw; zA6fa=t>3gRF_t9(9ugq87}|WeoYfyzwWyPB)w@WH09K!ZPGlSy9sd9zd7>F)!dTMz zm6rX@wSXqtwn!2khj(KTKhh=){W-3^?Kb9cn*wYE!UlE%3~nMKVAoTu=p{%OP0d5W z@Y@?Luc^36f!YF-`c)bBQC*4Ka@=MCEEt%P@&lO7Z5r|>oqHVGdtA$fV#H*D3nwSC z#wYZymq*u<@`BCou3I6D@o`QF#z7yQIn+Isun-N5&eMf4v(z-tJ)1u|;Ix;P(>1(K z=()cQv`B=J^(UInX-#WHZ^`HyjZK@RDLa>OU=kn*1cM`X*&dXiP48F0 zeyAm>Rxi7WFn3RdOZj&CQyo95ov{eFBgO$N6dA;m&NG_fbULB?Glkp^^$v&ODe^p4 z{g1I^`|-7rV7oz(|f?U8-LYQD+V?Hj-bBgD>?YlPIDwhx?q!=XjQON?j zvBfPjE;M&t8>kzk?j$1|DI>s2iHu1wHjLMlelTfA?GEFIGTHtlP^LWq+C6g>f6{u6 z>z4M(k^#@GL~-Z&npg2}rndW!wn7zBe2W#3N0~4&^Tk?RXRl&AGs_%*y>35BD7CqO z1PKKAm;fLWWsW-<7$2>4n)nZk|NX&UhvyXa1QTxE^&;su_bLo^oV? z&*@Ae&21xTBL^cqjyrJ?M|EifB67Y)dx6Jq&V{Duw}<+i!0#W{p3~fd8{*g)!2bX* z&y0Cc4S>?Q1TZlMf0!rpA3BK$9SeB1kyEVUF?jphrW{^?NR>f)BfC1Yp1>3=Fe~bnR;%ld(F&F;75pwi4X^t*v50~kzY%DDmUrv?8@!K0!fbJAVHZC7_T$& z6=^c!aou08HM_+TUg*ImX@NO`k1D+BTCnhAgPHX09jm9h!UzTo40oEOzoH8OI48Uh zKQrFBS66Z4K|@zHtF?$ADCa%+s!pZX6;Qc`P++fO-c;XH>5AKTFNBT(+dr*TH@dVB zy+VMPMLr=P46*f|Ybat;X6h#}zR|_Qaa3({l^6>H%<^jAL+w#^U~NivWzNa-z@+Z` zU2f3#E{OVI+k+^UQ!_J?&`f@HZ_uqh>+OEk*wfq#Cuk?au^VHV6}<4J-F?YXM_tnt zi)4_eaKem|2M7AnH=fc4yKtzuL?nzT`FWp}DQ~IVOXAEHRt$ad0CCtT>zwwp zt1)-9z%!o;!fx6-5Dp6--GLHnVU3zEVZNW#G`T4(LB`c3nE4{$6Y{{s*7W|;v=a76 z3zh+cFrGI+5Mm{BA4=jnj-~ChAya9{5DZG=0~q7KdQdrclXdfk3(1cDRn)PnGL#~E z#+%xEcNQRkaA&v7Rc4mX$=b(nB#;2l5jE$>PT-i|w8RbjDM=V!yDpPUa2PY&0j`h%c zDR32Iw`keUGTE%M4~N~eunEbL@4}+psqKb3$}*jbDG-9+g(u?WK3QN3QTXb|PQam6#!wB)I8 zc&^S<;ZKw?Z5Fkg= zsP#Q@%)mPVjybEIp2*6F;W?Qf(y+;jQSNe1F3k5pVNOqfDwdK|k+?zRdeGYTU=bVx z89AuyG|vJeIpVOBq8etJ%WmKi<_$Bbx41_mx8F34txSSHU-_tBzwpK}_w}lTsHpb{ zt~YVVYCfXSZxsD1c5Rm?a_7t@nj$#^iG0XpUz$@+Pjr8PRHYi2o_`F?d_a^b++E%%c}PLBEA1d$*N50zFr zPh*Npxz9Sfk#VvPIi_?sPETnyWv}VG0tR(vB=}T(KRGnrzMQZC1eoL={qbE&RL-7B zb}9OMmn_R6R8D<9`_+K!w%L#{Z+3ns7|7s$bbgW2wi}Hf!?jeO&+9eM==x2@SGMLY z@+Ksnai2OR$GMZI+`WBri=-6_gaahW1O4f(M^qbv77K3#Ema<#9V5h9KmhR~nZ4}g zw<^vU5dZ*4nDdI_a(APT@09L?npJR`+TezEvVz>jsJdG`<>8{3i z0HZ8VZ$VY*$vZZLSC>OQYR1+BlGz@l^GRIN6<)}=_>0b;ZKRZe1>YG&AV*wg;Qu5X`Q3z=T_Zj!roAr3(U`Ody3m> zY+1DsUJUVvJme03wb0>9MJ#O#-PBuFwQV4vAeIs{<;5%QR-C=7;X%pq4*ZFapzTJ} zJ-7E)V{2_J6mS6Z?T?jM^=2TWsF3+SnC=gqa?2Z&PubZVS?B#bp{TpI+ek)w z=5deBwfzfnIo`LWFL6%jR2xU1(z_0csMx$fQxn+FF;sSRggPtaG?L`_V@J`wq65m@4tZTT{clR_Oz`iF!|9(P-hqC|hP4DP&=zwUW{5t;c@J$;S44}Ae9 zQQL3utc}y@6^3;u6%@O*Ur{!1+1D=jY>?jQY${mfHz7=7PB^OXYI>n}wQfol-H%Xv zkLgGDq!aEgD{Zp=BeZblHnH>LiXH7SeMOsvGQg{j41i(Bp@3_6&OAAMp1x>Rdo2B* zx>_fY4o46;n&{a#9AKQ2{%GJ&0;>9EkwOj45N8LL%qjF3tGzjdBlw6186(S@@v_PC zwWQ21VE+JQ5&`7nXvuF+_oe!loA;6i)c^>b56|z4-j_~PZ!R|n;1j#$jE|)~@BSts zbCWW5_kd68O>c%Aw;mfN)2fe@d*>>#ZvVa#)e!02#(fnsV3q&P*P0 zkL^_Y@TA*ehW`LC$UcI2@~)4Nn=L)?G6%wYccoPAA|UcfINAsKsZBM}xVA@}jt|b4 z)a!yUR*~-uN#-Ndy#D|y<;@?9NY3_H-|7Q`%*xTUjyzt!Iu@qnTTld*J75TzBl>r$ z`(6+SDy=Fx!Qgq~YU5h)By$H9&&kO@kki?{Ort0xX)z*ao;%YzJBmXnN{{Ccl99op2nBFkCqaElOM$+$~gkJ%-#s@B+1W2L(xha`E?nlT<( zK2^S?;@R4y5y2!zV<+f;l`UlFu`%%T&oSDwl$!V!*(ElQr!t2v{%zCb0m(BzrC95A zB>*GhnIBX73NF4vZy;@$0)2mry)AJ~!L|V20N>pJIV1U3d$~0yrfb}HQicP72`#vf z$R2r|(mLiFf!Q61^*ntl$EfN8H$(1o!d-pl{}Un zeCC_g>2~r);37hjGnknAQhJ?383_a&6`vvj`T;avsd7k@0N_u5DLuYsyvWHnNab5& zwm@}d1Qr7;!O!0UgoaTEsV$tCAjsg+2Gbi2 zr@!KYWk={^6~X&Ydvjca3>f4d;yZB$0ItgU-7E0=`SHY8nRQ*MefQ-<8SHbAPtez| z_*9oS^fu}HoW`#2ib&6S$Zp(V@r-s9){^LK?K`+#fm~ou3_%7nGJDg$&{{%eBWd%O zD>3yFd)GcS2Z=v1gUx%VVE&;Tkg(*O2|^2egddkCg?mdJ;FH1s0C~sHy$I?UVq`={ z2p=FZ_nHmEK2z!ayC2G(i6#>D;gA6X$ef7zN6w>Yj`{ga$>Y+cYR{zlNIyxRxyO{Q00hgl~{LN0xIQXC3GQ{{WC5Q8hcM3cK~)Ae}DO?Ei==!O>u&#l1Kxv$&8X< zWYFCJ3krL4>CP+Je+zw$*rMghwRP2sDxT>TDH*UZJ+>i(u8=D|3W(PIq_f(o~3xq15Vz;Q<&bZc(@n z!bu#!z^d(EwN})d%?wFzachYSk9frY01V^~^#1@$=myQp_U7MGt-RJ3u*!<~wl$x?YcUR5(DY3LGq`-QVIQ_nDKEU2~-q zX=xVP-nipv+rXa;5gw<2e5R_k`u*u|-b0|9gn=v}pa9rY7#m>Etyy)SbivZ{N-p0L zyDJj_;vg}Tkix6F??pE(Y5xFc-*G{3L`BFs0IviB4oA+o=aD*3SLeY{k$I~}Ybko# z+BOLBJf8G&yb+7?0_O>sewqwM{h#-8ck|b5#q<~NxYnEqlJ|7@6 z;xwCkQa$F;F3@rY)=xqpgO9Cw*HhQCxf{$5Err??U@S>KFJ5WiWwnCIcWm7YwKoF; znaGnQRddZ$OZ_-ZK;!F6={n83W_{k~{{S&^2|eU2`qe0u z-JNGa=&Z^;+giL#5WC=mz*dm}@?*bxCA|ivWSe0I?)*;@2%k>%Rnfh%w&8;&*yG){ z)9D-m^Q!F^wD%g|LhdJk2$AKHllo?gOJX~V9cO9U<+nyrPGouzWAvwQbv-rJi?+!J z;3N@oF_R;-`qwY4S52(TxT_G%M40Wt+-7IYSu@+pjrfyx*#mHAcS4H2!nAIQ5ip~H$Qcz_@?5EzalM~C>Yhm_IT`t5 zKaioS{mmOvydF1ZIRp;~GxM4Pdij4Z_O8Ob2ex7{oR6WAOzG}&3GQ+8%=t;j>qr6H z`qKyx1muAL=a?fo^BAC(i!*tGl?Hp5;K9KLeMcU?Rb6n&;~0-XIsJc~XkQ?YOi3NR zLG2V<*Sns7zb6#zl&IZ{KOwnH1Cfcu_TcBt3MPPH5!yz4&Iiw*r2|hh^3R_hFVu*p zWJWP3oX5)c^Lj6 z82S;;d5yEj^sOc00G1%jsK97O5eqZ7ydK+(M<4`P^l`XA48Tkfq=;zn7VHeb`I`DK z@QrtA%PD}vBL*B{pmv-zd6pcC@K0kctg;4ZZJ|zbyyaJ;*`c$kB_Vq^p201iC>Cz|bec~!FGX!;({;zpNl#Zz*4 zg$U)jJ~tp8zFZOBk=E-C_fmsug+_OYED746GD;TSCunIA=xfD#ac#TqURySR*}M;Y zU7|>}YGB+FrJHhvk)ISdyZ#jWJeKV4adDdQ4&L|x;4F(ORULxFwaAixc?Jr=$1W^* z)%h(+*&eOYZMLZ%%Q3lb+}+$aZ9E2s;QB%BMbUd1r(N#P?--rHpAOPb`e1)5=rno} zY}|ErMrL3}8*mIjne^@2xsIXHgEiT>V&Pd;>~SM+84;1iWf`ZY%4)!Mo{Ft2YFW1p z*?yZuV9gQ!d_CnR0j&0w%z=#Fk1q3#ig zUQ`3m9-Zo+1=z`AbQL>r)6WskPkk&P; zfkQ2q+I%F+vatUEPs1a#A4-3x>TU&Q4RM+Q*xi(JWO5B>ENWs{t?YNH`y8$Zy>+w$ zV5ym$N#K5UZ_+dtm%=YAfFLv<)-hGxGfD3N=WFHFJi7o-l{j?v({9%)Rz}~RGb5VI zBwuE0C{YV?n0H8+?Rc0i?m5=8!ZI5bp^L|S;piC zj^~)ipTE|mnt9AZ{+w4G z(`_<2KtmxTYcbp5KXF}lqgEgwKMy^t%gIUT#kymCHuCWp@BI0pbv5x4I6c41{Y?_h z^+g6eXSZNH=tEh$80HV>_wuOq3scy}mqJ{k#G-?|oP58n9`=UZkO$NHn%L@?J759H zI0NTE8uVE^w-fwa3G^OS(S{-5tgR_)!KXa^LM+u%l=s!vl%_lwo&#o$8`7 z^X_w1y;oHr1GL9~zs|6lOK8qx8s-E^+8c@PeoDU)61^Htom7o=E(K6zSIIiDW07@$~YlJN+!XIacx} zcoaQ;pjuD?4$Tmx#ytDh%M_&OY4Vj9c5bSXi8C9CihokdcAe0}^cCk{_K^(73PBzT zIQ6S#>ZG)y?>jeS;(aHRSb7u3E$uL#Cl+vk~;qae2C=6s{I zAeS7C#r@j=2?XQ=+9!%8w^Bgd2zJ|&JIx+#Xi8K^PpY|c@J2gSF5(UYf7AWy(*FQZ zRc(i8wxB|TC5iL@0D9wkFSWAnte}tpP)FtgtI#bS&1`_eOO8mviLG3m9Z1q&Fda|Z zTWfVtkgLIgJn`BFYmoM@tZs3&2?$6eoB`%(OWw>DR=U>~1hL!?bIkfxt7)|Fr(0`q zi;TnG-Y4Sk8+~FU5nWJ(V(l8j2|t7;m#CKaWo|;?6Bs?eI=<1mxjULbcmN0|&bZ!} z(~jk~Biy46r@J)ZndVJ*+Pz59+e-=+uij$L+_HjXDEeZlwPTd{x@d*%a-oG#Ab4bP z2PFBALsvS^pBAm$FCoZaz14nRM-|0;V63H7WWR-{fjmujc5a45S(Q`>vHqWn^{z`! zF8jX55}lOYBc@(!mzcR0gzcOx#s{gVx}YBDZta5{fy9XVQeM$@Cim8@hudv`^KEQRFMi$(!?-jNK{$QYu#ysQ7s=9ua zZQWrWC&D)^u#L_6Ri{L{_KI7Az`0W1Xk@E?QBQTfIbU~iNR(~T04Hg~0zX-;<+xSq zjHP1zTmqoW1y&n^@jbMYRz&eyKIfbg=q3K zv=Vzv;8T4|QwaPt0fCq!;wo#m9jK)HqSo{-+8cKiqgxD8N%afmqek~ku@((jm2y8=O;TgXz% z{u=k#DHGImfhlyZ_B)p*64((0AU-28lUZWJ91JlemY*zrBAe>BAaj5*jTx$?)# zh;@;;a#V4`&m8uy=woq3q}Os9fw%<)ckt#(1IrxeiW$C$HkIuY-^j;cGf``xNdPRw zkOqBcGX{>@W@S?nqs#%j_1(yr6%ltWcSS}5TAg;u_h9YBgW@geCy$*ssOgQgTnM>1 z40Gi%>je5$`yqLBp8 z4g^GZAkT4H(&`7PVr0hl@=t~YK<)IbvLv<%QZy`*+y4NB9}&PN zN0+J16RX+URxlYs2h{WW)AnqGZJ-I-aX9(;&v>eBP3^+SpW!kmxbyO^Zlk5B-@QN7 zcR+M{pk;F@EfL5#KdnEZw4jGMa0pYxPbcUqI-N2`-Q@_9d^wZ-Beg^_?2ywU6c9wm zIsWyGFt|&QViYB8z8Yt8ek!5;6Qk*S|Dd)))pmn1*Q2 zZ!iax&^kLuyteyGG+@lCnE>MgM`K>)^LjT_Yo-u=;|YMi1s(lN;Q2*0p;PfFFv*Zi z_A&MCLDJui(nDr9DE<&&Eg@kVI1o?yKNnPnmjLgn^O|rX?`r|S) zpGxXhq}f*P(B11Sp6!$Z0{ga8j!rTs&WWee1@I}C=6{6)9FHj=M4D?$b8rM756Hou z3}dz=j8ZpT1|kpTk_Ju&O>)a6Xu?u<4@uH(t1xTZOWZ7@-I)dfAP{{&T5|sYPu+!N zEC~UmsUXG*vwW_CxyWQ zTO%7;M+6^{iq)cp8~_0=yJqMN6oV>H0wAA5LemcjgC#&FK#vhIgU^&>iVmw^Z{7?n zpAO(LHva(n(H|`FTY8DxVBXF*qV#cTuMs;518MKZdF)Lv`V!95+z=0(3=!Cgt4F^q z-QbarPdYW2FwPhd7$k6f{+}w~_2((KF6Nx9Lc%`VQgQ(eK4i$Ikmh?w9kGwp(X_g4 z!URf#5`4#*q1)1sP#(bd=hxP=cP*hcM$-A&?!+!mW@d9HnzBG7f2T3r^FE&{PWg4n zI|$sTiSoqKT_;kcj|r8TU`!a`M{MG2SZx*DyVc*@EEY_N9#h=;*PQ+Kk zSxTre6S%+zJD4NVzN#`!UxTvObBQPKj%cAf`E&OpENiob0PaUe0Osz3@Z@>4fM?QZu-lK3qf-(#ca6ue{J?aIig$MV( zpGo`q&}O4kM4&2-ZmrE_XwWiBG0uFv>j3oo`B5}_R%784+pr(0iqW;09Py0)RILiZ zQ3aBFefF-uqWcclHc2S4F2rC@3ZoK72g~%X{{WH_lx1kQUE+h`Kg^BR!~Zt zg&Bur-9DSUQf+WC3>cCU3<&}U81M9|yGMH%nLI%4+{A(_%;i$FC-9GTod9q`GP8_; zpsNo^?QEIImYYuC+9!}pGZT`=WDaMO z#Z&0LlJ|!F#<;6!PwyMF_?Us^+v#0b{grsbv0^4=Q+V6iQ-x6+oYXbFK~~n^b^?|t zsOvmrkPHlw&2-_(C_kv+oPOt<`%8ITTU|PXXitaAqB-0F0LD!%q1A4R!}z`)hG6{{RB4 zx+5xFx&f66%)=^9a3}O5y`{F+LAUP}P`$uzICgR10Y(U3d(Q^CVKm~Tbji{i(l*(5 z3Wsb-QC{|ULaV-wf z;Kyj^)~b3JR#`dbB*ter9lLiOtA5a0MqtgpIO$9(IxP=BLy(5W8jr5x|%uK3Nm?D}?vgHZue}LqhHG z0DD1DS3Xl+H%hm174J=%;b3oI0WvM}0Pb1^YuH>(`|GCWSj)kF0EOeiASe;})cVD% zgnsOef_IWOu`$NtI32V8)?U*ufnL!@LjjkPc#;}poRQ~MHm$e|4FJdh%#u0B8+%Ep zQcq$?=5-q409I|sfB=u7Zt=xZ^-VOHh?#&4fIp|y*GuhJPB0i1o)`?r6Q7Ig1M5{f zD>&NB002QGjDBBA*OrSZdQ_tW+k@o>yZ-=ZbQZ(gc2;1+1P%Z>&uKWCrO^8?xRw_a zVC9G)NgdeZ5r7VA@7jm=N(!d$1+?5)t*|-5h%ChCBym{e$eZH3vQ*M^c0aV+yL8*Q zr`{>tpaQrcNMDc~8oalqwk1~BDxyIH#C>Fq#Lu01qG~ReS5{dR@ZX zC^Nd@cPwGbMnV&aUL%oSURO2h;+H7d6OA#8C1e&PEclAVkC$mb*0z_38+&fu00@|g z^W)YkI?Yo%GclGiCj)^400D>`3L2Wwh92ScsDbzS{mp2Z2b^<`KcM>5opC%Yz~W{n zJ=Zy0L`Gy}9!DhA7Lw^#Rs^d9f(GoL^zde4D$lAt)gjniwzn0NYe|3 zY)k+!FgO(KsujJeQ$83201iy?x##9akx6B?sqPKST{{V>Ro!rUt zfq+k#@6A8b?!jRYr9frGu#gDcMpR_L=7uy_^taZm>`<( zIzL|}mE8r$0GEBBN|j)u=vWs{=kTe>-eLm~cpLj{LxNgI!Xn!%@JUc+2x$j0CxE$9 z*HF~HEv@3LA`w{c=OPsHLF35zP@Mtj8ZTjK605mL*!xU4KDD+O&)z}T7U?|9sTIa3xH@v_lzY@u1 zpA!$z4)xjJ(gGP!#^ob(F5nDc#EH*o6boL;g)08>LyjPA`Qm%|8rL3m+Fkq`$%fG` z+hA-X$P*{!nt{=u6Nw8W1o!p%{Hhvz%m(7Yhl)V#2PB^{j|lwy($?#N%J10+Tn*zP2=N%f@)1|vX5F_c2qgUb z55Cl;-i&?Q8)iiL@k%Xy%A|1JQ=&2`xwvg7C4YzMIjfJt*G9W}T>}6%L6AdiQpY&nS5s(Dg93t-B1&{E0oqUN^P^<07mN1JBHZ^{R`S zKRj#I$0T}oK2_A| zZGmXA!D2R%xq-CA{BSveCOG-=TlQbo$|%|no~j1L=fpP< z2|wmMbIew2bV!!2Fk|mbNdijexAv?i+?j-xv(9=iO)aSLpLZ7|1D)K!^buYDkEWtr zx@0K1w{tdE0wz1sDbtSCbR*ukAekEn@e%9Vo$7k`RaQG$3Gp4E$J5l}uOvCiGiM%W zGu1Tmve2z<<@S0gs(Jp3PV?%I4EZCvYVJ?IRIIYdc9`W$sV9Svx`QNc0|j z)wfO2x}j%LxW{pjOmKPgfm%&Fv}p*m#l4=<9mSZT+QVsI;v*8jPSjhSH7+wSW;Y80 z8-@qWefFzyrJbx)4YCMRFgr#v53LEhLvD6hh>{7~;y?o&$JV*qc4d`Gum__mNp>xB zJ5_;Za(_-JiLBcICcyv?Gv*_Th0RMT1ysCvNj;C8{*}8o_cw`1JkPIxlnRL%_yN-{ zlGlU-vDd^@2P)!zV0u-Bf=%X7B{m5yfEXMo2PS#+rMgv+x|`(Za@#B?jl3B3qiMCX zaluSmJ{OK>pU7fsns}6@VM>R?2^j5i#{jo>KAe4OT|&=;V#+Zv7!D6(3`f?gy8g8g zNn)ki11LO=84=u^DUam`n#ya8hbLvG zvid0~LELh!J{$>{;;Fhtlm7sC%ehH_48S6MQaC^9Jk#AMqKA%Xbd^@K7qKfTP2-NismA z=`VOs@U5^(Anu9a1IA7xJ?Y85#mVw|80vbCS$q;lBXmchs}7xW;fXw$EGIr=%f2e3 zrV_w=z*sX36_C(;z>$c}Sey2@f{G&6>uPrrs!VRd$ENQ?P`(Ar+?75nWrfS`2n;x% z7(CByqZ~$kDw9>J6T#gkBQc0L?@wxeJ<`x8!#K#nKgy^&A5On+&3y?l`J#MwBaB zx9B)sQxUDiL+@nuwz|?!T zR$a$x#{l|{W4YCz&0pwV*Be2XYO#H?wzo=WcZr!I0%1g0Sf>?(;n1(r3z$cE> zmqpRr8{fvuGUnkV2LAvk?!rz)nlg0oO4h%*zZlh>KA!kshCD1el6m3X3&H;*z8 z7$lSap4h2r^#Q8!Hac?1C=SoaKbz?0m4YC04OoXayli>>d#dw4 zUm-x$x~nLi!1zHDs%N}R%;IaEzlOivhjsv51eG5WkKQQ|Q+EbCuc)W`b(@I!jg;u&n)3yVns#)mcf^`$hc>Y2@#VZkFRa9;D!s^@4p+Qu>7L=n$(70-=37Amli#5;PKxgdh~E}5Jx zxd;qwQ?wFuC3ZlMQ<~>fZp_Kr2V^!CF}U|;A^`6lt9na57afmoAbAL<8&@EBLM~)Z z{P~hh<~u}9SJ=5Dz9${9OepglWYoo23^#^8UUTYeU$TL|0hwK}a5J9| z8TF5?F>d}Y?N^~6X#njPQIp`7=u{x zJ|GZC=f#i*ZhcNMQ(Hs;7>sZ}A_4wWThlu^DhAm2Pz<-8Ng40jruLi?82|)FjbNNi zgUm;9Tuv>wVyX*%)Dov_jBW@?21XbkPUEqx(%S&6n1kVyp8o)+&zjA3xVEH9y6{x) z!SwiuiXEYuB49*}4n0R4V1AX+2q88^UR~3d+DR-&#(aQ2U#&aSY=Rw#0a_yvVq}Q* zrLEtAlmQk189pB6nhJ?M&TD;3UWfy4nPCV4i0l>zi24kgtWepaV;8S@kf?yWiHVXs zH=NB*;gu|+3HZP=N0DB|PEVNmRX(QaiBL$yaCVY#r^HD)?J?dd%_g9gEAaS)p5Tmq zky&MfzQZVesrGnk6L5sOp9g*O!ZvU`n=nZ2T)(mI)`%oHBTyIJft|{%Nh<`g2M|A6 z^&sjFVWT0}pIyr%bb3jycdvAx5?&9vx9?l1a6>FK=0|KC;=K-sJfj~@UA%j*k#NaM znPb)NOD(}@TstIt!?xN8AecZ{Vt7-ZE^Eg+?^rjsmQ@b2H>#>`Ol$>FLik{f*(bjh z>w3#8;U(C@HL-B6(u{kUV>tkGh|e{~={h{w7a4v2#JmG=jVBpCCT+y+A~^3}pHDQE zvwU(%9G6DvZ+Vz3cv0>RU|?}1dF)*@O&(+?fQB;9y`3y{GSXCQ6dXRrf7<2e0Z6#S{{WO*=f8Ph&Z|Xq7qp>ti0T`4%Z#@PA^=n1+5nInGc%MmbCp-k z(Qi+aumtDJ8T_i9Yq|U`)opSD?sppin8Cp~%z>C5MyE8KVA}9bSv0g? zdq3=h>@#sKZnj4=0ir^l#PCdst50VAld^+$)9)hQcOo;43RTU>n#-`PBJ6N zK;(m*#sygFu7DKaGXV=6oD3h9YvnR^nO&>z>~L~qSj2&Vq6can?1X|OF)`!^5=enR zKj%kN!{v59P{3%(X5y)BN!$ye71v`9mOqqhz!df5Ds`e59>r*%{xE`8H|WM`0evF zMfEOf?MCXC?raFeDfbk!DpYkfbzUN>+7Cn0(II7N|p;VJ3 z46s9zH(@|y$E`{FqYoJyPRLLU?G5YS2b%1|@W*46(P^!_zShJe3?qmnnA-+>#zBd$ zcFvCCF$}Ho48{i?$&C6@x;B>X$nlZL0Eh%|03>|pGEF`jL11Bq-*}`LJ;??+ImZ>z zk$JDV6L##@i>f$nn~H+ODybljA^cEZb}Tna7=K^EKWC*Xzc9p|-D>3V4p0N$eGZY&@I!XvTbOc~>~ zBc$qn>r&0UoDjs$)mDU5SiymkeEw5gO}QqM+BZ{PNOi7=nui|sZzaZ24$MjdysKL| z1bKs*6>aR#O|_?yXzpbnykaugBq%%q-xNz4+lg<)%Blke_ou_Sv5Xj)5JBxewZ4(A zAH@f7Ef&NnB4;fe^D3gba^%S@e0&hHQ14dhX@6zOTYE&Cg#(Z=BgMgtX8!=ayPp(bC85{|jotfs(%m-Zt=16S_g;4yRWZn$m@)D- zf2iqB7>hO)Ad&Cxx-lNAu6B4p{)^6oUlBVsT6LKV~eO zhjriMYzs_RX^2wN6m9bq=S%5g*cOG*cFB0Sc3cd^EM_+k2qkkQ)%vQGqjp!TM?Rf1 zPPcB0cbE`_+DoX`$#O7J<7t`X!5>`1M60uN7}Cw0|L^MkpQvmO1FupBbf54*4Lv*+NR?!;B6`6I{^y| z@Q!|D*GwfU_>~)^bbi8J2_zCr{7Me&F z@Udm??VHz+yFd;?CTYbX<2Cc=Sm}*vikakqVkRIH-@SYGgf*)~1s7eEf#PF>$S`0;?vW(- zuP64+1F!LW%S`*{AOb--z>Zkp=M}$2akRp2(aBkGIa$Ml;vkYi3Vk-5;Ag!+F!I6w z0BSwbtiUQ15s;;>69Adcc;+AuX>{_5n2-35X1x*y7V`ke7=(ShfG24H<8%oi88M#t zpk2g_5Dab;pHVPlzXP`vT`rxOf`KIc0OKT{2d!sNv$T zng0N6&@k6oU}d)nkpd=vgpB+-B=-h8RmI(Ui$=!AB$OiIzF8FVN?QP+V=Ziv#GC>Q z)9RQQAb$ukB=8`UBb-cjk(%C?&@u_y+^IMR&pnJ_b|)C1M(m>Je9V6gXS7Vje5BV` zpz9K%)vIfzQWdV{{XAaPp1{!{t|mUZfcNn zmP%f_LBxd(X)s79d=t;Yc#6*?_(iwiE`=BH!(Ql@Mx3|31>@c#y667@SdPldOm5GZ zpDOFW4LVibJAu7jm?)!w&#)wX7tW-_Vj7ZTq;=}CKIWsoTnX6P-j1>T3louEir zm^ICdf@79V40PdmwXwZqm5~x7m@IN2cdIRTTK(xqx)HVwh%BA0$T4zu&(5Wz)4C%u zfIx%*=kxQ5+{pBx zdsES`5$^%G`zOTPLP(4N41w!SjcE)N5Qo15aQW1_rS&Z#w^}|INg&T=0&)+2^_*ms zhZ?<f=&eM)PO8eGqCxuY^O}=&aKhm}B|*m6$@1=V z0;QqQ1-A?dawi7|$YjUYy;loGPmGP`40av(7%}Tc6&W`Df-~hKKkSbI++?@JCym+K z2r=C8%>$vM8CWf9eiCIzZ=BVIjZ`Z;KpUZqh*|xAAyBcaw#jg<&8jgYkY+qQX1gJg zM7hNwUaNd5*|n8GExLa%KJ{R|NkS}uPkhhk{it>{5pQd;W(z)I4-$Xgt2%#ILfcf2 zEx$_zZA8WBVF!Mt$47F)B_XGCow5&Y&(5yN1{+0Dl*8 zJpE}Kp%U31`GYBwDrUz(n0-eJ4~soMfJi8a3)C+-#x(YYO7IYm3KU-I19|r z6HwFjg~G_(ND?uMyVTnd+q`%ieEoi-%CmS(coh9P)Tsmm0|cL+9qO~%E}&Ilvn>1| z4+t2aopg5eR*HBAcsMdWbNs1YZM48ExQ=t%)K*c!D;$+0&i$q=yAIOAFnbyE{{VXE zy_V_iHq#0~HwqK~0C=s;A5Ll1>xjFCZJ+X-}k8hj>v`**JU zSJIV&D~=eMVZj*$8Wx*OZG*#dz;|y!pE_!H+g4kRB8RJJN^Br*a$u(rr}(FzpsJVX zV1Dyq05op?5KaIc`SbLyo2y-GgK~h4J7DKNK>5_VeC%XqEJq<$06u0<>osX8_A~HN zYx+XmVFSei8#s!dkJ;#eKq^{TPsDOy_w82dK-;&!KAwh?>bLft+l&Js3FnA`%=QA4 z>KshgMv)%2_O>VftpPHl81Ivgc>2|MPU?0GKFe&bFeDNQI94Ra0MF8c*FB!Od=*LX zkVUevRFSyLNF2b3JocvaqUEb?DhX?z77)^bL}pJWK(2=*h%mE{Q(C$%|f66C;@|ufSVR0rx4rc(!^~Dp~8|?vEiAYnq zfD+RK7$nb-?LxVwkPDX}5d$uASMQX>675hRi%Wi1Z;t92gRDJ;|kYeKFNZ8%UAG#3v^s z4IlAD>b+pw2ne0h?n#Ik+Mc*E^r}9MuXea?6eUP$fDXgRC%)n{6l2dBqPXhEG~GD+ z=4D_R?ZNerha|*$RrgLP%B;X|A_)hAFh}Q9+6_9auyVjp{U$N=Z#Aa$q%EM^NJe7d z?K3UjCxTCE=7&=#&8aO{e*XZ}&9uq&Vf(hk$l5&QdQiGW$wr9WS~KV%4><2e*6KhI zBXJCPA~A!XF~vut+)-&UAdTig9Cqe@zs|8y=a$!mb3W$(0Bcu;0D!Owj|c`tiN|7K znqxxII~rrTg%jIo@8>5UI zA6n^zy*!&=a^$FUS<=^fJMMN;N~Yljo08;q9f0ztwYr9N0J;X;q(m$#87m_P!-Gp{ z&$~;7*{(5A(s_m8$C>h^w0%J?A%>F~R*lLh;vi?7S5GR7aU*s)a^$_k4Cz=#pg zd{TV_P>pL~8;yiha1cgaOB;(ksWJgyBT6_N*DnS7ZI5(If4kkhJ+h!SKp=maK_IZ; zOwu}DkF}NeHNtlUNZLUi@qnOz%>}g<+^LTc-n&$jvv zu)-qwnS#XNdDhVBTU2k46@}|vK?cKc%34=u+M(J>5dcYvkr?B>RP`IT2p25elx?9! zwTq1>JDm8)0#4NfJ-gAmZmn;<#mSp|GI%$f8+RTy#lbNfqB|NFOVcH#b!)40Ozag9 z`^9^A0Rw8?BgDJ8Fd%2Wb;}aHk!tu+`V@5UcAA#1lezGr4emz7u<`02>`P| z6WWhwtt;(KI9o2v!Py($UMoAF_oswBouCsrsaWEnXmLqIOR04@H#a@g;ZOl~IXK@F z7(I;-N7F-DD3>9H#c<8W?D7sE#yfFO-_j<-e(#3f<84QF0k(q+fhQZHeQ6=5VWhpZ z1y)mX)Bso%Y|Qw|#{if)^R67Qa!cyJa|uMZ=~vjQ?jX4(4aFic#~u^qHLV`MZQpXX z;Y|C^03fN5V~7U;QkGxcqB4M%;F6`JhUQ0sL7b9cM2>l@yIO>Bufr6DUQd?S$o0LcFUD60;qd~H&2WT@_BiGv&;o@%$Q>BFMA zeSl7JB47|$;b)T3!LD_c`n(&{rZ~y=4mE%!pcn;U+g5oafE$?taAKzMBfvu$5v zpCcQ9Q8H(o3{ri1+gh<>Vks$cimJc71al$AB!fAt8}^hnpd=P!beV(V00I91ltldM zv&rM%*Yp*P=&pO)cS4PXZcV_JC4dCBPCi4*gQk0nwQgDG2Iyc+d6?{Zr$6l>Zr{CC z+<>vT1S#>8y5Nx{2FrS8g15?t3OqBo&REZ2+!68s{{T9yvRrM2hSl`l_P|SlSjc6W zah}LxWOknYs+-z1VXy>pJ4z9e=@>NEQn*T~1TN6hNSO>rJ>(x+S5d4gs~ef5?a2mt z^Tde}>sZDy$yM+Di&V!nvQk~t?k(=X8<|!!zCIqrDLhks4^jewJCuT?$1q5rp~Wxz zExStB6mCnXUuecva-<##!HiUNBp-E>z@cDD?J>wwCQc(b`Bi0ujFbNWxBd#7v`<%V zLWv|H1K~Tl#}k3+TGVS5K$3-zzB@gTkO7j2SzKRo##~3b4DYJ9d^K2;N7? zfKKR<6t7HBZQ4m?3{K(}Pjew;5`WV*E4xj#BZ794(4sO_lQ=tsLG;dR3{B2Wzo4TK z{WDkp0D25ac*@`~8-!*IuX8gsy?(eKcHhNtY>1T}C}Ko+018`Gr#lihoQ=)EJ(QWq z?fG}BP@7c@2wbTd0fq?&=6S5ADsR|33SYehC?QBZMgZ&p&j{{VU*d|N@_3=G#X?H;(> zx=Nt7le*e8l_ANOmfAoKK6o9yXr9&d>wpGTw!gcD za#Lo}5#f@uayjNJ)`o9|I=Wjrp_wADO8Ey644FR{mLSJ$(X_W7`+%UdN?H;CAZ5Va z9C02%`PFWxU%6jol0X|sSp?!Zj2{00tvS-|GAXh_UC09oJ4iV@N|nJeBs5KM@bJtp zbTM~9bK)TG$p%0Xk>Vu47{I0VmaVGpmsc176*l7&nVF8_YP!|Zo0bVAp9w27+r&eG z9F_xgVrW-%@)(e31CU1Ed=Y``jL@R5LBwgVu~48E%&6UiJeU%`zOh{Y089v4?8`d7 z)gF-5H zNV3xC(XFc7b{7C}08kVaz-PEv6$V5?lQhRqyS~~^-+QsQfyaRD1j`X5gTMomMX{<^ zDpV9wHim#mAW0@(G=w7IM|0~WV!RY_Yy*fyHt2$dk6r^T7)KDBEfA=;r4qLw?n z8v>HbWJ#Ee$?O1MKEj0BpsqrOA)p=6;pQ*~(m|7sILVr0{?aY+LprEktUOAmfJ(*` zm;(?yw>4hh^$mg;b{mTBC*()gswyk72^IZv;RP+gm)&e+k(ec;kEK`pK<?%i z;gu$rw{vK)I@^U-*>k!Zdof^VZ;6<@2$(0QR00hh*LRvf|P6ck-0*)Fsw%yV|+*Hc=kBVmyOnM4<`j?5emDu3i zckX$LbVG2KCt{fQNCxIAFbhZh+VSFSl6)bl-96EE@zac2dw$_#J59NCa~VD`eqoJY zTxM&aK}%s>LWnZtd{PuCKQ2DCA7Ql1Xz$p&fD7*!OhA^}q+`{NaRMu%T}{Qe-n71N z&)6Pcf3eSXo|wvsWD9ncU74Qqt+%@8j2{t8YYQX7PUGc9_CKdJyMe=4a0HN-QSf*N zI6TL4aYFW+MZZyb-21yBhmV;BK~RI1at?g6JW%vHqjiFy_d07nJ)n$#t=Xe}#xMln za0fHe+mQg?M2?8m0+eA4|oaQKhnLnp7^Bn_lXU3 zpLK0O8_zqq3ZP}2`42q6s_jMjI0hiFQe^X)AY{f&SIev|97VF+GGxSm0N{Q{wkTFk z>X7pQRGR#q1=gv9oA+ z&oBmfoZx4H`kHhR;oB$A5t-meAbI-LXTBvs+W>gHvT+0$B#}+&j1^(xncNt7LoNrQ z!Idg!AIr}X zo2`C`HK#@f+T4bCIbDW!h9`m~`R1wh76G*Omf2B-Rood0lPp-AmL@U{Z;5Mt*|b{h zDeYFdE&w^&z$jo;hV|XtcNw1C)_WM-;0ZkPG@DI&aC-Cq00ypWvI%Qa zh)K5Nb;E(Q7xQIc(g@_pj`aQHcN<7RFi6?8q?jce;h6sbPCmG$ewd4^v_ZHPt(}dx z83aU0Vi(~c=jm43?N7Q;ZW2h`RdxwUI8mPX$p^O;objbqw?xgl&{Vq^~Z^`dzBQvHdF-phWetM{P$wrL7fjsp+#5uE)o<|3WwcE~_5 zti9ujli==g&f4R&AGuU?ea$iZp;2vh)gio+IRl4q0TVBm_6OVx{oqT-n3nLk$NXzv4`ItN%mZ~}KQ z-61(BVhAUK35Y$7Sag1rw(ZNP0gr_eLzDid4J3Y%OB;<_ZvDI&-J0q>EZs+4(-{mr zJJ?SXji(qpNRIPD=~^_jO}2NtbS~|XR6!ezMmY!4 zmfCVKD~tR<>h8DPIAvskZzOmIK~fGutgcnxFaQ3Yq6NeE4DjHIII4vX@qaK zm!2El+C@^t6NYvkFtMC-9E$I0@}p;d@(ZkMB$?VfY<=WMkK~EDz4qgAcF*C5y<&@SFZjo^i)K*a@gB&KOKN7#IXbr zNoXaGBt>}@(x)a9s?P` zDj4Eqh^>Y{)3o+?Cgx^Rvv37F(1pncLY17(Z~!qR(mJafNrD+v?cTo$J%~P{ej>eA)Z0NJ_k6U8?Phud{wLj)iI zHuz4~JA`D@zR3F(ds1aXd!;)~;IcP=k_l6~B=Z9k6EzZ&O4u!#_TUp_Ew%pu zyu4R%wm;Gd&B6hJoaDgGeHGHy)r8q$E!ssGS>EKH?8HeV9y+PG94 z_ax6=U5upL8tA=XODg2X zNicq5Xg4(xyr>&_XPDg`rcZiFZUtcByo-&1Dd#W-GoPhPUvykX0wtPdPA9k%isSJ{ z`1H0%QtU?fP@SOh=O#a;Sgq7(oP!ItaRg`UT&GFbs244y^hNNyKh#gszroOa3XYp}YX zyl8?*J{XMSiN+~Q+5|WySBGf?q|c!kCayAQG0v3~H#CMQ-U?-jjm#50Kj{BOTkYh!P?X26?NS9?LRZTqV`wWUT)Hq>~$Si3jUK_QTm<+3qdN0N_I< z!mLr4Oe0T;bAysSYtY3QF3iTqy`5!$2vVztAf6-ECQ0(8y}$N_Y~EGc0s%Wf8J^@u zO*^8z>1bUR$+>`5V;)53lU2Ug-MN0gcFRk?-CKJ= z3Gj`^41hd{kJ7FBHSuu+sUR}taA5j;>xb!n!-L}90+lj3AfJ)#{KM>%8 z1_g2T6O#HEa%{2a-B1}SLnu3DN|6{o9CCY_K7&+?af1vrd6ExojQ&+op)0t7wFViU z4%j|@U;#7b7>@ii`(18AFpuym@U?H){OH zfCIEK27D$A?H+!ailXV3u^_kwikX>(o!>aCEmo|tU;$S!vfeBnM&W>c4J;TdJ&+iX zp^I+GB+k`6ly~fS&1sfbqSFOt*bo*%?Gnc_2|4C>M}MtvMRj+AWOKK0#y^!Zxujyf}r|%BuBzU{AoS4KO)N^fy@~9PvpAnH0;tibt0O1t9%Ot|7Bw&NaN1>m~ z$j1@+tebuw+fOy+2$0CsN=t#TXb%Sh>)>v z(Ez4P79+3(%y!7CTV+|maswQ~s}r$%oJ9G>JEqjT-`r>2s*f(u(dJF7`VLoi4es15!Zx9XnG(ixKTjy4tAOxOmnuhq z<$%bU@W>EJ1By?rYS~`lJ^&a6DkOP96SSD%Mt@qO>m6;D-Pcx9$gp;4nf@b!C8PeP z0xLg7&9r9Iayvau@gUkjZf)@f@xfsp79gsy20;VsRN8N6R@vNH+Xrp5oGBoecss5< z+ zLtnRJrye&{T;I}_GQn7oJRq2kyt7(Yp@XDU_Qj#gQjSO7)<+QEN@Gx^t4F-IJ&crvnDCJhJOJ*iF2?%Xx9 z(;EipVn;Fn=e7J;-coG1b@jP@0lM%%gzY`V9oRu=&>qIXE)Td~e~st?+9`;;evw!lzf zZU=*b634%Xj`3EVKJb0V+>{VO@hL}=cqt-Cl1#{&QtwByg9M$5NVKq3Hs|6n%w)v0 zk3r3A)8n-y&9Nlz#(!#6W+%9Y$%rb-0r1R7A0lgh&ud!Qz+<%yzjw3@5%7(~Ok|G` z03S-C>V0j)+CbYa7?27>mgF7TT*uD>qoeCByi54Q09C@OC7Aa3sN%pl-Zf418Dc5RD^?oGnVE(@0(oBsfH zY5`sHWwJQ>)dsUws<`{T&_IsD?x7+XZwOG{AV^jUS7P8EK$=(DO?+Dxfe^;oJ6FI; z5E=Onc?&i;V@N5(8~f86ZhGo@%M}UiFUSKg5g& zEdoOTB0$FFgO2rLm)B*BhT4}_f8HzYXCa_&Fn0*X0LC*lNp*^_WS?~Vh}BTo;77br$b8R;LF{BldGwv2x={Nqi(o3TWkTx8-}^AmU{7G* z+PO=%?j59$d2T!?2;7Gt?GiwqcAn8)A52no%V@HK#r?~v45+F>!ti8DkbV<9S1xEm z3w@t;;O5JXM{Be0hWZauT}``^P0K(471LutGSUM~7!XWmrR?psBwn|5irr^1a0GEH zuqsC&Kp{vF2YQD^xVG+wMN2*%mo1@Yw|P6fJe6Q`Dq#6hx@Mhit0O=yamsp-WFJGe zYs%^;6)8q9POJJmIWxaton=#fm3{l0dZxi60B$TB5#DK$;3{}QaYVoYw*nY|Fac=n zD>{mXdqxiIjAW2N;~bU%c||n3V{Z;Eozg-U+(!f$QXq)pxg!>iIDy;3CJ)3{VBw6ia>zSGZP?5Gsb7kvCz|if`8CVxNn6wHskLkd*nkEl=3%_@WRp$b)_BSQ z$imqw+>Bts{)URt;MpK1-~fUG#1KmcCm5c6XnK2-JQAiFI}ysyJQ2lV)McpY0H&rI z(w)O;12*_FB<&6BnC(h+oif}-2Y%h7#0XQgMrE-Pn&=kFk+cAyDu4j++bfZd3ik1H zCTU%EqSd=Dr48Q;xj^3SBrrY#0XQZ|PzRZ+>2=xv0MyzlERKbfVwERojhMhIAdHO6 z$vx|OOMF2)Nu2YX@fh=}&tI+WgXJGIl2@rR2gZ%|(hbx!D{g_5Y7OPgH-Twd-tgK8lpR@F< z9*v~^TN9Sd2s*z~{XOO{FBDL}JQ31I$E8Cjoyh)opbJum<^N+^q06sl?k> zS%c^1BDChA9#nbq*T=WHq)qIa1qCcFb@% z?Lg_8#<^v@SwrC50WGz%rGW&DbMzyvBAx0jk}xC^0Ushn_xi+kszlVPI$GVips*1JJ8&`Oo@Y7 v5Gyd11gv~fxS25m14)zT+J~vpcw5D}r*WLYVUjs8d5+nwUPhy9$tVBW-7%_o literal 0 HcmV?d00001 diff --git a/packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml b/packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml new file mode 100644 index 000000000..5dbede2d2 --- /dev/null +++ b/packages/drupal/test_content/content/media/5dfc1856-e9e4-4f02-9cd6-9d888870ce1a.yml @@ -0,0 +1,74 @@ +_meta: + version: '1.0' + entity_type: media + uuid: 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a + bundle: image + default_langcode: en + depends: + 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7: file +default: + revision_user: + - + target_id: 1 + status: + - + value: true + uid: + - + target_id: 1 + name: + - + value: the_silverback.jpeg + created: + - + value: 1713785099 + path: + - + alias: '' + langcode: en + pathauto: 0 + content_translation_source: + - + value: und + content_translation_outdated: + - + value: false + field_media_image: + - + entity: 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7 + alt: 'The silverback' + title: '' + width: 1280 + height: 720 +translations: + de: + status: + - + value: true + uid: + - + target_id: 1 + name: + - + value: the_silverback.jpeg + created: + - + value: 1713785205 + path: + - + alias: '' + langcode: de + pathauto: 0 + content_translation_source: + - + value: en + content_translation_outdated: + - + value: false + field_media_image: + - + entity: 2f1be18f-dc18-4b56-8ff0-ce24d8ff7df7 + alt: 'The silverback DE' + title: '' + width: 1280 + height: 720 diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index 6a4e6eded..ebd66747b 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -8,6 +8,7 @@ _meta: 3a0fe860-a6d6-428a-9474-365bd57509aa: media 478c4289-961d-4ce8-85d6-578ae05f3019: media 72187a1f-3e48-4b45-a9b7-189c6fd7ee26: media + 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a: media default: revision_uid: - @@ -102,10 +103,6 @@ default:

Heading 3

- -

Quote

Citation
- - @@ -118,7 +115,7 @@ default: - +

@@ -192,17 +189,13 @@ translations:

Heading 3 DE

- -

Quote DE

Citation DE
- - - + format: gutenberg summary: '' diff --git a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml index 5ad90259d..a367f61b2 100644 --- a/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml +++ b/packages/drupal/test_content/content/node/ceb9b2a7-4c4c-4084-ada9-d5f6505d466b.yml @@ -58,10 +58,6 @@ default:
- -

- -

diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 1f54560a8..727920dcd 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -27,7 +27,7 @@ test('All blocks are rendered', async ({ page }) => { page.locator( 'img:not([data-test-id=hero-image])[alt="A beautiful landscape."]', ), - ).toHaveCount(2); + ).toHaveCount(1); await expect(page.locator('figcaption:text("Media image")')).toHaveCount(1); // Video @@ -50,10 +50,10 @@ test('All blocks are rendered', async ({ page }) => { await expect(page.locator('h3:text("Heading 3")')).toHaveCount(1); // Quote - await expect(page.locator('blockquote > p:text("Quote")')).toHaveCount(1); - await expect(page.locator('blockquote > cite:text("Citation")')).toHaveCount( - 1, - ); + await expect(page.locator('blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")')).toHaveCount(1); + await expect(page.locator('blockquote p.not-prose:text("John Doe")')).toHaveCount(1); + await expect(page.locator('blockquote p.not-prose span:text("Project manager")')).toHaveCount(1); + await expect(page.locator('blockquote img[alt="The silverback"]')).toHaveCount(1); // Form await expect( diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 1f26435dd..4730f8d2a 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -157,8 +157,6 @@ test('Blocks', async () => {
  • list 1
  • list 2
    1. list 2.2

Heading 3

- -

Quote

Citation
", }, { @@ -248,8 +246,6 @@ test('Blocks', async () => {
-

-

", }, From b0bac3dc60e4b7dba64e0d11efbc029d3932f7c2 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 22 Apr 2024 15:02:11 +0300 Subject: [PATCH 077/221] fix(SLB-297): prettier --- tests/e2e/specs/drupal/blocks.spec.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 727920dcd..1e9e881dc 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -50,10 +50,20 @@ test('All blocks are rendered', async ({ page }) => { await expect(page.locator('h3:text("Heading 3")')).toHaveCount(1); // Quote - await expect(page.locator('blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")')).toHaveCount(1); - await expect(page.locator('blockquote p.not-prose:text("John Doe")')).toHaveCount(1); - await expect(page.locator('blockquote p.not-prose span:text("Project manager")')).toHaveCount(1); - await expect(page.locator('blockquote img[alt="The silverback"]')).toHaveCount(1); + await expect( + page.locator( + 'blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")', + ), + ).toHaveCount(1); + await expect( + page.locator('blockquote p.not-prose:text("John Doe")'), + ).toHaveCount(1); + await expect( + page.locator('blockquote p.not-prose span:text("Project manager")'), + ).toHaveCount(1); + await expect( + page.locator('blockquote img[alt="The silverback"]'), + ).toHaveCount(1); // Form await expect( From 74e756d9266183c3667e5b31b1ae10779ff4ac5b Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Tue, 23 Apr 2024 21:31:51 +0300 Subject: [PATCH 078/221] chore(SLB-339): quote editor block style --- ....entity_view_display.media.image.quote.yml | 33 +++++ .../core.entity_view_mode.media.quote.yml | 11 ++ apps/cms/config/sync/image.style.quote.yml | 15 ++ packages/drupal/gutenberg_blocks/css/edit.css | 11 +- .../gutenberg_blocks/src/blocks/quote.tsx | 132 ++++++++++-------- 5 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 apps/cms/config/sync/core.entity_view_display.media.image.quote.yml create mode 100644 apps/cms/config/sync/core.entity_view_mode.media.quote.yml create mode 100644 apps/cms/config/sync/image.style.quote.yml diff --git a/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml b/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml new file mode 100644 index 000000000..b88657103 --- /dev/null +++ b/apps/cms/config/sync/core.entity_view_display.media.image.quote.yml @@ -0,0 +1,33 @@ +uuid: 195a40ca-6da4-4e2e-bc2f-c9c1fc0385bb +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.quote + - field.field.media.image.field_media_image + - image.style.quote + - media.type.image + module: + - image +id: media.image.quote +targetEntityType: media +bundle: image +mode: quote +content: + field_media_image: + type: image + label: visually_hidden + settings: + image_link: '' + image_style: quote + image_loading: + attribute: lazy + third_party_settings: { } + weight: 0 + region: content +hidden: + created: true + langcode: true + name: true + thumbnail: true + uid: true diff --git a/apps/cms/config/sync/core.entity_view_mode.media.quote.yml b/apps/cms/config/sync/core.entity_view_mode.media.quote.yml new file mode 100644 index 000000000..51148e9b4 --- /dev/null +++ b/apps/cms/config/sync/core.entity_view_mode.media.quote.yml @@ -0,0 +1,11 @@ +uuid: f54c9109-a0ff-4a09-9772-748af96b8eb0 +langcode: en +status: true +dependencies: + module: + - media +id: media.quote +label: Quote +description: '' +targetEntityType: media +cache: true diff --git a/apps/cms/config/sync/image.style.quote.yml b/apps/cms/config/sync/image.style.quote.yml new file mode 100644 index 000000000..b7a85ee89 --- /dev/null +++ b/apps/cms/config/sync/image.style.quote.yml @@ -0,0 +1,15 @@ +uuid: 983e88dc-78f8-463d-b088-026d7d96bcc0 +langcode: en +status: true +dependencies: { } +name: quote +label: 'Quote (24x24)' +effects: + 3ac18111-aeda-4cd7-aafc-5cd66d0cbf7e: + uuid: 3ac18111-aeda-4cd7-aafc-5cd66d0cbf7e + id: image_scale_and_crop + weight: 1 + data: + width: 24 + height: 24 + anchor: center-center diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 1c603451e..39ab66473 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -7,16 +7,17 @@ margin:0; } +.gutenberg__editor blockquote .quote-image img { + object-fit: cover; + max-width: 100%; + border-radius: 9999px; +} + .gutenberg__editor blockquote::before, .gutenberg__editor blockquote::after { content: ''; } -.gutenberg__editor blockquote .quote-author, -.gutenberg__editor blockquote .quote-role { - text-align: right; -} - .gutenberg__editor .container-wrapper { display: block; position: relative; diff --git a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx index 1aea31b6a..d5873d2d7 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/quote.tsx @@ -35,63 +35,83 @@ registerBlockType(`custom/quote`, { // @ts-ignore edit: compose(withState())((props) => { return ( -
- { - props.setAttributes({ - quote: cleanUpText(quote, ['strong']), - }); - }} - /> - { - setPlainTextAttribute(props, 'author', author); - }} - /> - { - setPlainTextAttribute(props, 'role', role); - }} - /> - { +
+
+ + + + -
+ disableLineBreaks={false} + placeholder={__('Quote')} + keepPlaceholderOnFocus={false} + onChange={(quote) => { + props.setAttributes({ + quote: cleanUpText(quote, ['strong']), + }); + }} + /> +
+
+ { + // @ts-ignore + error = typeof error === 'string' ? error : error[2]; + dispatch('core/notices').createWarningNotice(error); + }} + /> +
+ { + setPlainTextAttribute(props, 'author', author); + }} + /> + / + { + setPlainTextAttribute(props, 'role', role); + }} + /> +
+
+
); }), save() { From f0564b76ebb24c0d312f0176094338c930f834fb Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 08:39:18 +0200 Subject: [PATCH 079/221] style: adjust margin --- .../ui/src/components/Organisms/PageContent/BlockQuote.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 6c1abaad9..d4f84abe1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -24,12 +24,12 @@ export function BlockQuote(props: BlockQuoteFragment) {
{props.image && ( {props.image.alt )} -
+
{props.author &&

{props.author}

}
{props.role && ( From eeb3cb615990e25eb7821f97e4c782acbf33835a Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 09:13:30 +0200 Subject: [PATCH 080/221] style(SLB-298): add no author avatar block quote story --- .../Organisms/PageContent/BlockQuote.stories.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts index ffe6090f1..6b40c8afd 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.stories.ts @@ -21,3 +21,12 @@ export const Quote = { '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, }, } satisfies StoryObj; + +export const NoAvatarQuote = { + args: { + role: 'test role', + author: 'Author name', + quote: + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

' as Markup, + }, +} satisfies StoryObj; From 2e4b557538aeab948e9d43706f5435800dc67592 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 24 Apr 2024 10:41:49 +0200 Subject: [PATCH 081/221] style(SLB-298): add blockquote padding --- packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index d4f84abe1..7ad1d3ab0 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -4,7 +4,7 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return (
-
+
Date: Thu, 25 Apr 2024 09:32:20 +0200 Subject: [PATCH 082/221] style(SLB-298): typography tweaks --- .../ui/src/components/Organisms/PageContent/BlockQuote.tsx | 6 +++--- packages/ui/src/tailwind.css | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index 7ad1d3ab0..c99955e06 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -3,7 +3,7 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return ( -
+
- {props.quote && } +

{props.quote && }

{props.image && ( {props.image.alt )} -
+
{props.author &&

{props.author}

}
{props.role && ( diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 2d464b850..944e19871 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -6,12 +6,17 @@ @tailwind utilities; /* Prose overrides */ +.prose + :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { + font-weight: 400; +} + .lg\:prose-xl :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { padding-left: 0 !important; } -.prose :where(blockquote p):not( :where([class~='not-prose']) ) { +.prose :where(blockquote p):not(:where([class~='not-prose'])) { margin-top: 12px !important; margin-bottom: 12px !important; } From 80da9cb6e69435394fbd3045a5ccccbcc6607978 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 25 Apr 2024 10:27:10 +0200 Subject: [PATCH 083/221] chore: update tests --- tests/e2e/specs/drupal/blocks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 1e9e881dc..c3c775333 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -52,7 +52,7 @@ test('All blocks are rendered', async ({ page }) => { // Quote await expect( page.locator( - 'blockquote:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")', + 'blockquote p:text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis nisi nec neque porta, a ornare ligula efficitur.")', ), ).toHaveCount(1); await expect( From 1ff2f16e114c0731e51472bde898d1265fc2f340 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Thu, 25 Apr 2024 14:20:51 +0200 Subject: [PATCH 084/221] refactor: use shared version of silverback_autosave --- apps/cms/composer.json | 1 + apps/cms/composer.lock | 28 +- .../drupal/silverback_autosave/LICENSE.txt | 339 ------------- packages/drupal/silverback_autosave/README.md | 7 - .../drupal/silverback_autosave/composer.json | 6 - .../install/silverback_autosave.messages.yml | 1 - .../install/silverback_autosave.settings.yml | 9 - .../schema/silverback_autosave.schema.yml | 53 -- .../css/silverback_autosave.css | 15 - .../js/silverback_autosave.js | 455 ------------------ .../silverback_autosave.info.yml | 8 - .../silverback_autosave.install | 111 ----- .../silverback_autosave.libraries.yml | 16 - .../silverback_autosave.links.menu.yml | 6 - .../silverback_autosave.module | 140 ------ .../silverback_autosave.routing.yml | 7 - .../silverback_autosave.services.yml | 51 -- .../src/Ajax/OpenAutosaveDisabledDialog.php | 21 - .../Extension/ModuleHandlerEmptyAlter.php | 22 - .../Theme/ThemeManagerEmptyAlter.php | 22 - .../src/Form/AutosaveButtonClickedTrait.php | 33 -- .../src/Form/AutosaveEntityFormHandler.php | 327 ------------- .../AutosaveEntityFormHandlerInterface.php | 25 - .../src/Form/AutosaveFormAlterTrait.php | 235 --------- .../src/Form/AutosaveFormBuilder.php | 251 ---------- .../src/Form/AutosaveFormErrorHandler.php | 43 -- .../src/Form/AutosaveFormInterface.php | 109 ----- .../src/Form/AutosaveFormSettingsForm.php | 209 -------- .../src/Form/AutosaveFormValidator.php | 80 --- .../AutosaveEntityFormDatabaseStorage.php | 348 -------------- .../AutosaveEntityFormStorageInterface.php | 203 -------- 31 files changed, 28 insertions(+), 3153 deletions(-) delete mode 100644 packages/drupal/silverback_autosave/LICENSE.txt delete mode 100644 packages/drupal/silverback_autosave/README.md delete mode 100644 packages/drupal/silverback_autosave/composer.json delete mode 100644 packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml delete mode 100644 packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml delete mode 100644 packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml delete mode 100644 packages/drupal/silverback_autosave/css/silverback_autosave.css delete mode 100644 packages/drupal/silverback_autosave/js/silverback_autosave.js delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.info.yml delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.install delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.libraries.yml delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.module delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.routing.yml delete mode 100644 packages/drupal/silverback_autosave/silverback_autosave.services.yml delete mode 100644 packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php delete mode 100644 packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php delete mode 100644 packages/drupal/silverback_autosave/src/EmptyAlter/Theme/ThemeManagerEmptyAlter.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveButtonClickedTrait.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormAlterTrait.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormSettingsForm.php delete mode 100644 packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php delete mode 100644 packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php delete mode 100644 packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php diff --git a/apps/cms/composer.json b/apps/cms/composer.json index e187e9ef9..bb6feeb43 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -35,6 +35,7 @@ "amazeelabs/proxy-graphql": "^1.1.7", "amazeelabs/proxy-gutenberg": "^1.3", "amazeelabs/silverback-cli": "^2.9.4", + "amazeelabs/silverback_autosave": "^1.1", "amazeelabs/silverback_campaign_urls": "^1.0.5", "amazeelabs/silverback_cloudinary": "^1.3.0", "amazeelabs/silverback_external_preview": "^2.0.3", diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index ef2a0aa18..3477e203f 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7fdb7073ff9cc88dcbc78224e439e65f", + "content-hash": "f2375207790855cc777235f699c7c2b9", "packages": [ { "name": "amazeeio/drupal_integrations", @@ -343,6 +343,32 @@ }, "time": "2024-01-03T06:30:01+00:00" }, + { + "name": "amazeelabs/silverback_autosave", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/AmazeeLabs/silverback_autosave.git", + "reference": "8208ddba6d5da916bf5649b7e0e53efdf491bb7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/AmazeeLabs/silverback_autosave/zipball/8208ddba6d5da916bf5649b7e0e53efdf491bb7a", + "reference": "8208ddba6d5da916bf5649b7e0e53efdf491bb7a", + "shasum": "" + }, + "type": "drupal-module", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "description": "Adds autosave feature on forms.", + "support": { + "issues": "https://github.com/AmazeeLabs/silverback_autosave/issues", + "source": "https://github.com/AmazeeLabs/silverback_autosave/tree/1.2.0" + }, + "time": "2024-04-25T13:14:00+00:00" + }, { "name": "amazeelabs/silverback_campaign_urls", "version": "1.0.5", diff --git a/packages/drupal/silverback_autosave/LICENSE.txt b/packages/drupal/silverback_autosave/LICENSE.txt deleted file mode 100644 index d159169d1..000000000 --- a/packages/drupal/silverback_autosave/LICENSE.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/packages/drupal/silverback_autosave/README.md b/packages/drupal/silverback_autosave/README.md deleted file mode 100644 index 73b5da62f..000000000 --- a/packages/drupal/silverback_autosave/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## Silverback autosave - -This module provides an autosave functionality on Gutenberg enabled nodes. - -On node edit, the content under edit is automatically saved for use in real time preview. - -The module is based on the [https://www.drupal.org/project/autosave_form](https://www.drupal.org/project/autosave_form). diff --git a/packages/drupal/silverback_autosave/composer.json b/packages/drupal/silverback_autosave/composer.json deleted file mode 100644 index d1359e6b8..000000000 --- a/packages/drupal/silverback_autosave/composer.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "drupal/silverback_autosave", - "type": "drupal-module", - "description": "Adds autosave feature on forms.", - "license": "GPL-2.0-or-later" -} diff --git a/packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml b/packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml deleted file mode 100644 index 338d94046..000000000 --- a/packages/drupal/silverback_autosave/config/install/silverback_autosave.messages.yml +++ /dev/null @@ -1 +0,0 @@ -entity_saved_in_background_alert_message: 'The content has been modified elsewhere. As a result, autosaving has been disabled and autosaved states for the current page have been deleted. Autosave will be reactivated after reloading the page.' \ No newline at end of file diff --git a/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml b/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml deleted file mode 100644 index 034332fef..000000000 --- a/packages/drupal/silverback_autosave/config/install/silverback_autosave.settings.yml +++ /dev/null @@ -1,9 +0,0 @@ -interval: 10000 -only_on_form_change: FALSE -active_on: - content_entity_forms: TRUE -notification: - active: TRUE - message: "Updating preview..." - delay: 1000 -allowed_content_entity_types: { } diff --git a/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml b/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml deleted file mode 100644 index 0fccb354c..000000000 --- a/packages/drupal/silverback_autosave/config/schema/silverback_autosave.schema.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Schema for the configuration files of the Autosave Form module. - -silverback_autosave.settings: - type: config_object - label: 'Autosave Form Settings' - mapping: - interval: - type: integer - label: 'The interval used to trigger autosave.' - only_on_form_change: - type: boolean - label: 'Run autosave submission within the specified interval only if there was a form change.' - active_on: - type: mapping - label: 'Active on' - mapping: - content_entity_forms: - type: boolean - label: 'Content entity forms' - notification: - type: mapping - label: 'Notification' - mapping: - active: - type: boolean - label: 'Show notification each time autosave is triggered' - message: - type: text - label: 'Notification message' - delay: - type: integer - label: 'Notification duration' - allowed_content_entity_types: - type: sequence - label: 'Allowed content entity types' - sequence: - type: mapping - label: 'Entity type' - mapping: - bundles: - type: sequence - label: 'Allowed bundles of the entity type' - sequence: - type: string - label: 'Bundle' - -silverback_autosave.messages: - type: config_object - label: 'Autosave Form Messages' - mapping: - entity_saved_in_background_alert_message: - type: string - label: 'The alert message to show to the user if the entity has been saved meanwhile in the background.' diff --git a/packages/drupal/silverback_autosave/css/silverback_autosave.css b/packages/drupal/silverback_autosave/css/silverback_autosave.css deleted file mode 100644 index 85d069e50..000000000 --- a/packages/drupal/silverback_autosave/css/silverback_autosave.css +++ /dev/null @@ -1,15 +0,0 @@ -#autosave-notification { - display: none; - position: fixed; - bottom: 5px; - right: 5px; - z-index: 9999; - background-color: black; - color: white; - padding: 0.5em 1em; -} - -#autosave-notification:before { - content: "\2714"; - padding-right: 10px; -} diff --git a/packages/drupal/silverback_autosave/js/silverback_autosave.js b/packages/drupal/silverback_autosave/js/silverback_autosave.js deleted file mode 100644 index c4501987f..000000000 --- a/packages/drupal/silverback_autosave/js/silverback_autosave.js +++ /dev/null @@ -1,455 +0,0 @@ -/** - * @file - * Defines Javascript behaviors for the silverback_autosave module. - */ - -(function ($, Drupal, drupalSettings, once) { - 'use strict'; - - /** - * Define defaults. - */ - Drupal.autosaveForm = { - timer: null, - interval: null, - onlyOnFormChange: false, - autosaveFormRunning: false, - autosaveFormInstances: {}, - initialized: false, - formHasErrors: false, - message: '', - dialog_options: [], - autosave_submit_class: 'autosave-form-save', - notification: { - active: true, - message: Drupal.t('Updating preview...'), - delay: 1000, - }, - form: null, - }; - - /** - * Add a variable which determines if the window is being unloaded. - */ - Drupal.autosaveForm.beforeUnloadCalled = false; - - $(window).on('pagehide', function () { - Drupal.autosaveForm.beforeUnloadCalled = true; - }); - - $(window).on('beforeunload', function () { - $('#purge-button').trigger('click'); - Drupal.autosaveForm.beforeUnloadCalled = true; - }); - - /** - * Default dialog options. - */ - Drupal.autosaveForm.defaultDialogOptions = { - open: function () { - $(this).siblings('.ui-dialog-titlebar').remove(); - }, - modal: true, - zIndex: 10000, - position: { my: 'top', at: 'top+25%' }, - autoOpen: true, - width: 'auto', - resizable: false, - closeOnEscape: false, - }; - - /** - * Behaviors the autosave form feature. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the autosave behavior. - */ - Drupal.behaviors.autosaveForm = { - attach: function (context, settings) { - var $context = $(context); - var autosave_submit = $context.find( - 'form .' + Drupal.autosaveForm.autosave_submit_class, - ); - - // Disable autosave when the form is submitted in order to prevent race - // conditions and creating further autosave entries after the entity is - // saved. - if (autosave_submit.length > 0) { - Drupal.autosaveForm.form = $(autosave_submit[0]).parents( - 'form.autosave-form', - ); - - Drupal.autosaveForm.form.submit(function () { - if (Drupal.autosaveForm.autosaveFormRunning) { - Drupal.autosaveForm.autosaveFormRunning = false; - clearTimeout(Drupal.autosaveForm.timer); - Drupal.autosaveForm.timer = null; - } - }); - } - - if (settings.hasOwnProperty('autosaveForm')) { - $.extend(Drupal.autosaveForm, settings.autosaveForm); - } - - if (Drupal.autosaveForm.initialized) { - // If requested so turn off ajax submission. - if ( - !Drupal.autosaveForm.autosaveFormRunning && - Drupal.autosaveForm.timer - ) { - clearTimeout(Drupal.autosaveForm.timer); - Drupal.autosaveForm.timer = null; - } else { - return; - } - } - - // Continue to show the dialog or activate autosave functionality only in - // case the context contains the autosave submit and it is not disabled. - if ( - autosave_submit.length === 0 || - autosave_submit.is(':disabled') || - autosave_submit.hasClass('is-disabled') - ) { - return; - } - Drupal.autosaveForm.autosaveSubmit = autosave_submit; - - if ( - !Drupal.autosaveForm.initialized && - !Drupal.autosaveForm.autosaveFormRunning - ) { - Drupal.autosaveForm.initialized = true; - - $('
') - .appendTo('body') - .append(Drupal.autosaveForm.notification.message); - - autosavePeriodic(); - } - - /** - * Returns the ajax instance corresponding to an element. - * - * @param {string} class_name - * The element class name for which to return its ajax instance. - * - * @return {Drupal.Ajax | null} - * The ajax instance if found, otherwise null. - */ - function findAjaxInstance(class_name) { - if ( - !Drupal.autosaveForm.autosaveFormInstances.hasOwnProperty(class_name) - ) { - var element = document.getElementsByClassName(class_name)[0]; - var ajax = null; - var selector = '#' + element.id; - for (var index in Drupal.ajax.instances) { - if (Drupal.ajax.instances.hasOwnProperty(index)) { - var ajaxInstance = Drupal.ajax.instances[index]; - if (ajaxInstance && ajaxInstance.selector === selector) { - ajax = ajaxInstance; - break; - } - } - } - Drupal.autosaveForm.autosaveFormInstances[class_name] = ajax; - } - return Drupal.autosaveForm.autosaveFormInstances[class_name]; - } - - /** - * Triggers an ajax submit based on the class of the ajax element. - * - * @param {string} ajax_class - * The class of the ajax element. - * @param {boolean} skip_checks - * Skip checks. - */ - function triggerAjaxSubmitWithoutProgressIndication( - ajax_class, - skip_checks = false, - ) { - // If the autosave button suddenly gets the 'is-disabled' class then - // autosave submission should not run until the class is removed. - if ( - Drupal.autosaveForm.autosaveSubmit.is(':disabled') || - Drupal.autosaveForm.autosaveSubmit.hasClass('is-disabled') - ) { - return; - } - - // If configured so run only in the specified interval only if there - // was a form change. - if ( - !skip_checks && - Drupal.autosaveForm.onlyOnFormChange && - !Drupal.autosaveForm.form.data('autosave-form-changed') - ) { - return; - } - - // If there are gutenberg fields, we need to make sure - // the textarea value is updated before autosaving. - const $gutenbergFields = $('.field--gutenberg textarea').first(); - if ($gutenbergFields.length) { - const { data } = window.wp; - $gutenbergFields.each(function (i, element) { - // Update editor textarea with gutenberg content. - $(element).val(data.select('core/editor').getEditedPostContent()); - // We need to update the 'editor-value-is-changed' flag otherwise - // the content won't be updated. - $(element).data({ 'editor-value-is-changed': true }); - $(element).attr('data-editor-value-is-changed', true); - }); - } - - var ajax = findAjaxInstance(ajax_class); - if (ajax) { - if (Drupal.autosaveForm.notification.active) { - $('#autosave-notification') - .fadeIn() - .delay(Drupal.autosaveForm.notification.delay) - .fadeOut(); - } - ajax.success = function (response, status) { - // @todo: On success send a post request to update the preview - - // If interval submission is configured to happen only on form - // change, then reset the changed flag on successful autosave. - if ( - Drupal.autosaveForm.onlyOnFormChange && - Drupal.autosaveForm.form.data('autosave-form-changed') - ) { - Drupal.autosaveForm.form.data('autosave-form-changed', false); - } - - // Call original method with main functionality. - Drupal.Ajax.prototype.success.call(this, response, status); - }; - ajax.options.error = function ( - xmlhttprequest, - text_status, - error_thrown, - ) { - if (xmlhttprequest.status === 0 || xmlhttprequest.status >= 400) { - Drupal.autosaveForm.autosaveFormRunning = false; - clearTimeout(Drupal.autosaveForm.timer); - Drupal.autosaveForm.timer = null; - - if (!Drupal.autosaveForm.beforeUnloadCalled) { - var dialogOptions = { - buttons: { - button_confirm: { - text: Drupal.t('Ok'), - primary: true, - click: function () { - $(this).dialog('close'); - }, - }, - }, - }; - $.extend( - true, - dialogOptions, - Drupal.autosaveForm.defaultDialogOptions, - ); - - $('
') - .appendTo('body') - .html( - '
' + - Drupal.t( - 'A server error occurred during autosaving the current page. As a result autosave is disabled. To activate it please revisit the page and continue the editing from the last autosaved state of the current page.', - ) + - '
', - ) - .dialog(dialogOptions); - } - } - }; - // Disable progress indication. - ajax.progress = false; - $(ajax.element).trigger( - ajax.element_settings - ? ajax.element_settings.event - : ajax.elementSettings.event, - ); - } - } - - /** - * Starts periodic autosave submission. - */ - function autosavePeriodic() { - if (Drupal.autosaveForm.interval) { - Drupal.autosaveForm.autosaveFormRunning = true; - - // Run the autosave submission at the beginning to capture the user - // input and compare it later for changes, however wait for sometime - // until triggering the submission in order to let all the Drupal - // behaviors be executed and probably alter the page before doing the - // first submission, otherwise we might capture not the correct user - // input and on the second submission detect changes even if there - // aren't actually any changes. - // @todo Remove this and let autosave attach itself instead as the - // last behavior as soon as the following issues are fixed: - // @see https://www.drupal.org/node/2367655 - // @see https://www.drupal.org/node/2474019 - if (Drupal.autosaveForm.interval > 500) { - setTimeout(function () { - triggerAjaxSubmitWithoutProgressIndication( - Drupal.autosaveForm.autosave_submit_class, - true, - ); - }, 500); - } - - $('body').on('click keyup', () => { - clearTimeout(Drupal.autosaveForm.timer); - Drupal.autosaveForm.timer = setTimeout(function () { - if (!Drupal.ajax.instances.some(isAjaxing)) { - triggerAjaxSubmitWithoutProgressIndication( - Drupal.autosaveForm.autosave_submit_class, - ); - } - }, Drupal.autosaveForm.interval); - }); - } - } - - /** - * Checks if an ajax instance is currently running a submission. - * - * @param {Drupal.Ajax} instance - * The ajax instance. - * - * @return {boolean} - * TRUE if the ajax instance is in a state of submitting. - */ - function isAjaxing(instance) { - return ( - instance && - instance.hasOwnProperty('ajaxing') && - instance.ajaxing === true - ); - } - }, - }; - - /** - * Command to open a dialog for notifying that autosave has been disabled. - * - * We have to use a dedicated command, as otherwise there is no way to define - * the "click" on the button and close the dialog when the button is clicked. - * - * @param {Drupal.Ajax} ajax - * The Drupal Ajax object. - * @param {object} response - * Object holding the server response. - * @param {number} [status] - * The HTTP status code. - */ - Drupal.AjaxCommands.prototype.openAutosaveDisabledDialog = function ( - ajax, - response, - status, - ) { - response.dialogOptions.buttons = { - button_confirm: { - text: Drupal.t('Ok'), - click: function () { - $(this).dialog('close'); - }, - }, - }; - // Remove the "x" button and force confirmation through the "Ok" button. - response.dialogOptions.open = function () { - $(this) - .siblings('.ui-dialog-titlebar') - .find('.ui-dialog-titlebar-close') - .remove(); - }; - - Drupal.AjaxCommands.prototype.openDialog(ajax, response, status); - }; - - /** - * Attach behaviors to monitor changes on entity forms. - * - * This is a modified version of the monitoring functionality provided by - * https://www.drupal.org/project/entity_form_monitor. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches triggers. - */ - Drupal.behaviors.autosaveFormMonitor = { - attach: function (context, settings) { - var $context = $(context); - var $form = $context.find('form.autosave-form'); - if ($form.length === 0) { - var $form = $context.parents('form.autosave-form'); - } - - // Disable autosave when the form is submitted in order to prevent race - // conditions and creating further autosave entries after the entity is - // saved. - if ($form.length > 0) { - // Detect new elements added through field widgets. - if ($context.find('.ajax-new-content').length > 0) { - $form.data('autosave-form-changed', true); - } - - // Add a change handler that will help us determine if any inputs - // inside the entity forms have changed values. - var inputs = $form - .find(':input, [contenteditable="true"]') - // Filter out buttons - .not('button, input[type="button"]'); - - $(once('autosave-form-input-monitor', inputs)) - .on('change textInput input', function (event) { - var $form = $(event.target).parents('.autosave-form').first(); - if ($form.length) { - var val = $(this).val(); - if (val != $(this).attr('autosave-old-val')) { - $(this).attr('autosave-old-val', val); - $form.data('autosave-form-changed', true); - } - } - }) - // Detect Ajax changes e.g. removing an element. - .on('mousedown', function (event) { - if (event.target.type === 'submit') { - $form.data('autosave-form-changed', true); - } - }); - - // Add change handlers to any CKEditor instances. - if (typeof CKEDITOR !== 'undefined') { - CKEDITOR.on('instanceCreated', function (event) { - event.editor.on('change', function (event) { - // Handle CKEditor change events. - if ( - typeof event.editor !== 'undefined' && - typeof event.target === 'undefined' - ) { - event.target = event.editor.element.$; - } - - var $form = $(event.target).parents('.autosave-form').first(); - if ($form.length) { - $form.data('autosave-form-changed', true); - } - }); - }); - } - } - }, - }; -})(jQuery, Drupal, drupalSettings, once); diff --git a/packages/drupal/silverback_autosave/silverback_autosave.info.yml b/packages/drupal/silverback_autosave/silverback_autosave.info.yml deleted file mode 100644 index 6608a328d..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.info.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Silverback autosave -type: module -description: 'Adds autosave & preview feature on gutenberg nodes.' -package: Content -core_version_requirement: ^9.5 || ^10 -configure: silverback_autosave.admin_settings -dependencies: - - drupal:system diff --git a/packages/drupal/silverback_autosave/silverback_autosave.install b/packages/drupal/silverback_autosave/silverback_autosave.install deleted file mode 100644 index 6cde0cbdf..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.install +++ /dev/null @@ -1,111 +0,0 @@ - 'Saves the form state of partially filled content entity form for restoration by the silverback_autosave module.', - 'fields' => [ - 'form_id' => [ - 'type' => 'varchar_ascii', - 'length' => AutosaveEntityFormStorageInterface::SILVERBACK_AUTOSAVE_FORM_ID_LENGTH, - 'not null' => TRUE, - ], - // We need the form session id as it is possible that the user opens the - // same form in two tabs and concurrently edits it. Therefore we have to - // assign each form session to an unique auto save session. - // We use the form build id for this and add an extra length to cover any - // case. - 'form_session_id' => [ - 'type' => 'varchar_ascii', - 'length' => AutosaveEntityFormStorageInterface::SILVERBACK_AUTOSAVE_FORM_ID_LENGTH, - 'not null' => TRUE, - ], - 'entity_type_id' => [ - 'type' => 'varchar_ascii', - 'length' => EntityTypeInterface::ID_MAX_LENGTH, - 'not null' => TRUE, - ], - 'entity_id' => [ - 'type' => 'varchar_ascii', - 'length' => EntityTypeInterface::ID_MAX_LENGTH, - 'not null' => TRUE, - ], - 'langcode' => [ - 'type' => 'varchar_ascii', - 'length' => 12, - 'not null' => TRUE, - ], - 'uid' => [ - 'type' => 'int', - 'not null' => TRUE, - ], - 'timestamp' => [ - 'type' => 'int', - 'not null' => TRUE, - ], - 'entity' => [ - 'type' => 'blob', - 'not null' => TRUE, - 'size' => 'big', - ], - 'form_state' => [ - 'type' => 'blob', - 'not null' => TRUE, - 'size' => 'big', - ], - ], - 'primary key' => ['form_id', 'form_session_id', 'entity_type_id', 'entity_id', 'langcode', 'uid', 'timestamp'], - ]; - - return $schema; -} - -/** - * Implements hook_install(). - */ -function silverback_autosave_install() { - // For some reasons it might happen that the autosave form library is not - // loaded or the handler is missing because on module install those both - // cache entries have not been invalidated, therefore we do it manually here. - Cache::invalidateTags(['entity_types', 'library_info']); -} - -/** - * Add the settings for the notification to the settings config. - */ -function silverback_autosave_update_8001() { - $config = \Drupal::configFactory()->getEditable('silverback_autosave.settings'); - if ($config->get('notification') === NULL) { - $config->set('notification', [ - 'active' => TRUE, - 'message' => 'Saving draft...', - 'delay' => 1000, - ]); - $config->save(TRUE); - } -} - -/** - * Populates the setting "only_on_form_change". - */ -function silverback_autosave_update_8002() { - $config = \Drupal::configFactory()->getEditable('silverback_autosave.settings'); - if ($config->get('only_on_form_change') === NULL) { - $config->set('only_on_form_change', FALSE); - $config->save(TRUE); - } -} diff --git a/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml b/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml deleted file mode 100644 index fe2005e5b..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.libraries.yml +++ /dev/null @@ -1,16 +0,0 @@ -drupal.silverback_autosave: - version: 1.0 - css: - theme: - css/silverback_autosave.css: {} - js: - js/silverback_autosave.js: {} - dependencies: - - core/jquery - - core/drupal - - core/drupalSettings - - core/drupal.dialog - - core/drupal.dialog.ajax - - core/drupal.ajax - - core/once - - gutenberg/edit-node diff --git a/packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml b/packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml deleted file mode 100644 index cb94aa739..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.links.menu.yml +++ /dev/null @@ -1,6 +0,0 @@ -silverback_autosave.admin_settings: - title: 'Silverback autosave and preview settings' - parent: system.admin_config_content - description: 'Change interval and forms on which autosave is active.' - route_name: silverback_autosave.admin_settings - weight: -20 diff --git a/packages/drupal/silverback_autosave/silverback_autosave.module b/packages/drupal/silverback_autosave/silverback_autosave.module deleted file mode 100644 index 7cc29d715..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.module +++ /dev/null @@ -1,140 +0,0 @@ -hasHandlerClass('silverback_autosave')) { - $entity_type->setHandlerClass('silverback_autosave', AutosaveEntityFormHandler::class); - } - } -} - -/** - * Implements hook_form_alter(). - */ -function silverback_autosave_form_alter(&$form, FormStateInterface $form_state, $form_id) { - $active_on = \Drupal::configFactory()->get('silverback_autosave.settings')->get('active_on'); - $form_object = $form_state->getFormObject(); - - if ($form_object instanceof ContentEntityFormInterface) { - if (!$active_on['content_entity_forms']) { - return; - } - - $entity = $form_object->getEntity(); - $entity_type_id = $entity->getEntityTypeId(); - $allowed_content_entity_types = \Drupal::configFactory() - ->get('silverback_autosave.settings') - ->get('allowed_content_entity_types'); - - // Autosave is enabled if either no restriction has been made or the current - // entity type is part of the restriction and as well either no restriction - // on bundle level has been made or the current bundle is part of that - // restriction. - if (!empty($allowed_content_entity_types)) { - if (!isset($allowed_content_entity_types[$entity_type_id]) || - (!empty($allowed_content_entity_types[$entity_type_id]['bundles']) && !isset($allowed_content_entity_types[$entity_type_id]['bundles'][$entity->bundle()]))) { - return; - } - } - } - else { - return; - } - - // Allow autosave only for entity form routes, as forms might be included in - // blocks and other places and it is impossible to determine to which URL we - // have to post the autosave submit to. Also we don't support embedded forms - // as e.g. it might be surprising for the user getting autosave on the entity - // view, because e.g. a block is using an entity form. - $route = \Drupal::routeMatch()->getRouteObject(); - if ($route && ($route_defaults = $route->getDefaults()) && isset($route_defaults['_entity_form'])) { - [$entity_type_id, $form_op] = explode('.', $route_defaults['_entity_form']); - $entity = $form_object->getEntity(); - if (($entity->getEntityTypeId() != $entity_type_id) || ($form_object->getOperation() != $form_op)) { - return; - } - } - else { - return; - } - - $entity_type_manager = \Drupal::entityTypeManager(); - if ($entity_type_manager->hasHandler($entity->getEntityTypeId(), 'silverback_autosave')) { - $silverback_autosave_handler = $entity_type_manager->getHandler($entity->getEntityTypeId(), 'silverback_autosave'); - if ($silverback_autosave_handler instanceof AutosaveEntityFormHandlerInterface) { - $silverback_autosave_handler->formAlter($form, $form_state); - } - } -} - -/** - * Implements hook_entity_update(). - */ -function silverback_autosave_entity_update(EntityInterface $entity) { - static $conflict_enabled; - static $autosave_entity_form_storage; - - if (!isset($autosave_entity_form_storage)) { - /** @var \Drupal\silverback_autosave\Storage\AutosaveEntityFormStorageInterface $autosave_entity_form_storage */ - $autosave_entity_form_storage = \Drupal::service('silverback_autosave.entity_form_storage'); - } - - if (($conflict_enabled === FALSE) || (is_null($conflict_enabled) && !($conflict_enabled = \Drupal::moduleHandler()->moduleExists('conflict')))) { - // If conflict management is not available the autosaved entity states have - // to be removed when the entity is saved. - $autosave_entity_form_storage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id()); - } - else { - $entity_type = $entity->getEntityType(); - if ($entity_type->hasHandlerClass('silverback_autosave') && ($class = $entity_type->getHandlerClass('silverback_autosave'))) { - // If conflict is enabled and the entity is saved then delete only the - // current autosave session of the current user. - if ($autosave_session_id = $class::getAutosaveSessionID($entity)) { - $autosave_entity_form_storage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id(), $autosave_session_id); - } - } - } -} - -/** - * Implements hook_entity_update() for the user entity type. - * - * If the permissions of a user are changed, then we delete the autosave states - * belonging to that user. It is possible that new permissions the user is - * losing the ability to access certain fields, but the autosave states contain - * data for them. - */ -function silverback_autosave_user_update(EntityInterface $user) { - /** @var \Drupal\user\UserInterface $user */ - $current_roles = $user->getRoles(); - $original_roles = $user->original->getRoles(); - sort($current_roles); - sort($original_roles); - - if ($current_roles !== $original_roles) { - /** @var \Drupal\silverback_autosave\Storage\AutosaveEntityFormStorageInterface $autosave_entity_form_storage */ - $autosave_entity_form_storage = \Drupal::service('silverback_autosave.entity_form_storage'); - $autosave_entity_form_storage->purgeAutosavedEntitiesStates(NULL, NULL, $user->id()); - } -} diff --git a/packages/drupal/silverback_autosave/silverback_autosave.routing.yml b/packages/drupal/silverback_autosave/silverback_autosave.routing.yml deleted file mode 100644 index ad468d60a..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.routing.yml +++ /dev/null @@ -1,7 +0,0 @@ -silverback_autosave.admin_settings: - path: '/admin/config/content/silverback_autosave' - defaults: - _form: '\Drupal\silverback_autosave\Form\AutosaveFormSettingsForm' - _title: 'Silverback autosave settings' - requirements: - _permission: 'administer site configuration' diff --git a/packages/drupal/silverback_autosave/silverback_autosave.services.yml b/packages/drupal/silverback_autosave/silverback_autosave.services.yml deleted file mode 100644 index f09b27af4..000000000 --- a/packages/drupal/silverback_autosave/silverback_autosave.services.yml +++ /dev/null @@ -1,51 +0,0 @@ -services: - silverback_autosave.entity_form_storage: - class: \Drupal\silverback_autosave\Storage\AutosaveEntityFormDatabaseStorage - arguments: [ - '@database', - '@serialization.phpserialize', - '@http_client', - '@logger.channel.default', - ] - - form_validator.silverback_autosave: - public: false - class: \Drupal\silverback_autosave\Form\AutosaveFormValidator - decorates: form_validator - arguments: - [ - '@form_validator.silverback_autosave.inner', - '@request_stack', - '@string_translation', - '@csrf_token', - '@logger.channel.form', - '@form_error_handler', - ] - form_builder.silverback_autosave: - public: false - class: \Drupal\silverback_autosave\Form\AutosaveFormBuilder - decorates: form_builder - # The decorated autosave form builder should be the first applied as it - # will not call further decorators, but directly override the core form - # builder by extending from it and just calling the parent methods. - decoration_priority: 10000 - arguments: - [ - '@form_builder.silverback_autosave.inner', - '@form_validator', - '@form_submitter', - '@form_cache', - '@module_handler', - '@event_dispatcher', - '@request_stack', - '@class_resolver', - '@element_info', - '@theme.manager', - '@?csrf_token', - '@silverback_autosave.entity_form_storage', - ] - form_error_handler.silverback_autosave: - public: false - class: Drupal\silverback_autosave\Form\AutosaveFormErrorHandler - decorates: form_error_handler - arguments: ['@form_error_handler.silverback_autosave.inner'] diff --git a/packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php b/packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php deleted file mode 100644 index 7fad3b04f..000000000 --- a/packages/drupal/silverback_autosave/src/Ajax/OpenAutosaveDisabledDialog.php +++ /dev/null @@ -1,21 +0,0 @@ - 'openAutosaveDisabledDialog'] + parent::render(); - } - -} diff --git a/packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php b/packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php deleted file mode 100644 index ce2975c10..000000000 --- a/packages/drupal/silverback_autosave/src/EmptyAlter/Extension/ModuleHandlerEmptyAlter.php +++ /dev/null @@ -1,22 +0,0 @@ -getTriggeringElement(); - if (is_null($triggering_element)) { - $user_input = $form_state->getUserInput(); - $autosave = isset($user_input['_triggering_element_name']) && ($user_input['_triggering_element_name'] == AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME); - } - else { - $autosave = $triggering_element && !empty($triggering_element['#silverback_autosave']); - } - return $autosave; - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php b/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php deleted file mode 100644 index 562f1b3a2..000000000 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandler.php +++ /dev/null @@ -1,327 +0,0 @@ -entityTypeId = $entity_type->id(); - $this->entityType = $entity_type; - $this->entityStorage = $entity_type_manager->getStorage($entity_type->id()); - $this->currentUser = $current_user; - $this->autosaveEntityFormStorage = $autosave_entity_form_storage; - $this->time = $time; - $this->dateFormatter = $date_formatter; - $this->configFactory = $config_factory; - $this->keyValueExpirableFactory = $key_value_expirable_factory; - $this->conflictEnabled = $module_handler->moduleExists('conflict'); - } - - /** - * {@inheritdoc} - */ - public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { - return new static( - $entity_type, - $container->get('entity_type.manager'), - $container->get('current_user'), - $container->get('silverback_autosave.entity_form_storage'), - $container->get('datetime.time'), - $container->get('date.formatter'), - $container->get('config.factory'), - $container->get('keyvalue.expirable'), - $container->get('module_handler') - ); - } - - /** - * Performs the needed alterations to the entity form. - * - * @param array $form - * The entity form to be altered to provide the autosave functionality. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function formAlter(array &$form, FormStateInterface $form_state) { - $this->traitFormAlter($form, $form_state); - // Flag the entity with the current autosave session ID. - $form['#entity_builders'][] = [static::class, 'entityFormEntityBuild']; - } - - /** - * Ajax callback for autosaving content entity forms. - */ - public function autosaveFormAjax($form, FormStateInterface $form_state) { - $response = $this->traitAutosaveFormAjax($form, $form_state); - $timestamp = $form_state->getTemporaryValue('silverback_autosave_last_autosave_timestamp'); - if ($timestamp == 'entity_saved_meanwhile') { - $input = $form_state->getUserInput(); - // Autosave has run already or this is the first run. - if (is_numeric($input['silverback_autosave_last_autosave_timestamp']) || empty($input['silverback_autosave_last_autosave_timestamp'])) { - // Alert the user about turning off autosave and deleting all the - // autosaved states. - $message = $this->configFactory->get('silverback_autosave.messages') - ->get('entity_saved_in_background_alert_message'); - - $options = [ - 'width' => 'auto', - 'closeOnEscape' => FALSE, - ]; - $response->addCommand(new OpenAutosaveDisabledDialog($this->t('Autosave has been disabled'), $message, $options)); - - // Reset the last autosaved timestamp on the page. - $response->addCommand(new InvokeCommand('input[name="silverback_autosave_last_autosave_timestamp"]', 'attr', ['value', (string) $this->t('Autosave is turned off.')])); - // Stop the autosave submissions. - $attachments = []; - $attachments['drupalSettings']['autosaveForm']['autosaveFormRunning'] = FALSE; - $response->addAttachments($attachments); - } - } - - return $response; - } - - /** - * Entity builder method. - * - * Flags the entity with the autosave form session ID. - * - * @param string $entity_type - * The type of the entity. - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity whose form is being built. - */ - public static function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) { - if ($silverback_autosave_session_id = static::getAutosaveFormSessionID($form_state)) { - $entity->{static::AUTOSAVE_SESSION_ID} = $silverback_autosave_session_id; - } - } - - /** - * {@inheritdoc} - */ - public function isAutosaveSubmitValid(FormStateInterface $form_state) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - - // Check that the entity is still valid. - if ($entity instanceof EntityChangedInterface) { - $changed_time = $entity->getChangedTime(); - - $input = $form_state->getUserInput(); - $changed_form_value = $input['changed'] ?? NULL; - $entity->setChangedTime($changed_form_value ?: $changed_time); - - if (!$this->conflictEnabled && ($unchanged = $this->entityStorage->loadUnchanged($entity->id())) && ($unchanged->getChangedTimeAcrossTranslations() > $entity->getChangedTimeAcrossTranslations())) { - $form_state->setTemporaryValue('silverback_autosave_last_autosave_timestamp', 'entity_saved_meanwhile'); - return FALSE; - } - else { - $entity->setChangedTime($changed_time); - } - } - return TRUE; - } - - /** - * {@inheritdoc} - */ - public function getLastAutosavedFormState(FormStateInterface $form_state, $silverback_autosave_session_id, $uid) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - return $this->autosaveEntityFormStorage->getFormState($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid, $silverback_autosave_session_id); - } - - /** - * {@inheritdoc} - */ - public function storeState(FormStateInterface $form_state, $silverback_autosave_session_id, $autosave_timestamp, $uid) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - $this->autosaveEntityFormStorage->storeEntityAndFormState($form_id, $silverback_autosave_session_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid, $autosave_timestamp, $entity, $form_state); - } - - /** - * {@inheritdoc} - */ - public function getLastAutosavedTimestamp(FormStateInterface $form_state, $uid) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - return $entity->isNew() ? NULL : $this->autosaveEntityFormStorage->getLastAutosavedStateTimestamp($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid); - } - - /** - * {@inheritdoc} - */ - public function purgeCurrentAutosavedState(FormStateInterface $form_state, $uid) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - $this->autosaveEntityFormStorage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id(), $this->getAutosaveFormSessionID($form_state), $form_id, $entity->language()->getId(), $uid); - } - - /** - * {@inheritdoc} - */ - public function purgeAllAutosavedStates(FormStateInterface $form_state, $uid) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - $this->autosaveEntityFormStorage->purgeAutosavedEntityState($entity->getEntityTypeId(), $entity->id(), NULL, $form_id, $entity->language()->getId(), $uid); - } - - /** - * {@inheritdoc} - */ - public function isAutosaveEnabled(FormStateInterface $form_state) { - [$form_id, $entity] = $this->getFormIDandEntity($form_state); - $allowed = !$entity->isNew() && !$this->currentUser->isAnonymous(); - return $allowed; - } - - /** - * {@inheritdoc} - */ - public static function getAutosaveSessionID(EntityInterface $entity) { - if (isset($entity->{static::AUTOSAVE_SESSION_ID})) { - return $entity->{static::AUTOSAVE_SESSION_ID}; - } - else { - return NULL; - } - } - - /** - * Retrieves the form ID and the form entity object from the form state. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return array - * The first value is the form ID and the second the form entity object. - */ - protected function getFormIDandEntity(FormStateInterface $form_state) { - /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */ - $form_object = $form_state->getFormObject(); - $form_id = $form_object->getFormId(); - $entity = $form_object->getEntity(); - return [$form_id, $entity]; - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php b/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php deleted file mode 100644 index 73533bb0c..000000000 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveEntityFormHandlerInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -isAutosaveEnabled($form_state)) { - return; - } - - $form['#attributes']['class'][] = 'autosave-form'; - $form['#attached']['library'][] = 'silverback_autosave/drupal.silverback_autosave'; - $form['#attached']['drupalSettings']['autosaveForm']['interval'] = $this->configFactory->get('silverback_autosave.settings')->get('interval'); - $form['#attached']['drupalSettings']['autosaveForm']['onlyOnFormChange'] = $this->configFactory->get('silverback_autosave.settings')->get('only_on_form_change'); - $form['#attached']['drupalSettings']['autosaveForm']['notification'] = $this->configFactory->get('silverback_autosave.settings')->get('notification'); - $input = $form_state->getUserInput(); - - $silverback_autosave_session_id = $this->getAutosaveFormSessionID($form_state); - if (!$silverback_autosave_session_id) { - $silverback_autosave_session_id = !empty($input['silverback_autosave_session_id']) ? - $input['silverback_autosave_session_id'] : - $form['#build_id']; - $this->setAutosaveFormSessionID($form_state, $silverback_autosave_session_id); - } - - $form['silverback_autosave_session_id'] = [ - '#type' => 'hidden', - '#value' => $silverback_autosave_session_id, - '#name' => 'silverback_autosave_session_id', - // Form processing and validation requires this value, so ensure the - // submitted form value appears literally, regardless of custom #tree - // and #parents being set elsewhere. - '#parents' => ['silverback_autosave_session_id'], - ]; - - $form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME] = [ - '#type' => 'submit', - '#name' => AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME, - '#value' => $this->t('Autosave save'), - '#attributes' => ['class' => ['autosave-form-save', 'visually-hidden']], - '#submit' => [[$this, 'autosaveFormSubmit']], - '#ajax' => [ - 'callback' => [$this, 'autosaveFormAjax'], - // Do not refocus to prevent losing focus of the element the user might - // be currently editing when the autosave submission is triggered. - 'disable-refocus' => TRUE, - 'progress' => FALSE, - ], - '#silverback_autosave' => TRUE, - // Retrieve the "silverback_autosave_session_id" also from the form state as on - // autosave restore the one from the restored state will be present in - // the form state storage and we want to continue using that session for - // the further autosave states after the restoration. - '#silverback_autosave_session_id' => $silverback_autosave_session_id, - ]; - - $form['purge'] = [ - '#type' => 'submit', - '#id' => 'purge-button', - '#name' => 'autosave_form_purge', - '#value' => $this->t('Autosave purge'), - '#limit_validation_errors' => [], - '#attributes' => ['class' => ['autosave-form-purge', 'visually-hidden']], - '#submit' => [[$this, 'autosaveFormPurgeSubmit']], - '#ajax' => [ - 'callback' => [$this, 'autosaveFormPurgeAjax'], - 'event' => 'click', - ], - ]; - - $form['silverback_autosave_last_autosave_timestamp'] = [ - '#type' => 'hidden', - '#name' => 'silverback_autosave_last_autosave_timestamp', - '#value' => $form_state->get('silverback_autosave_last_autosave_timestamp') ?: '', - ]; - } - - /** - * Form submission handler for rejecting autosaved states. - */ - public function autosaveFormPurgeSubmit($form, FormStateInterface $form_state) { - \Drupal::logger('debug')->debug(__METHOD__); - $this->purgeAllAutosavedStates($form_state, $this->currentUser->id()); - } - - /** - * Ajax callback for rejecting autosaved states. - */ - public function autosaveFormPurgeAjax($form, FormStateInterface $form_state) { - \Drupal::logger('debug')->debug(__METHOD__); - return new AjaxResponse(); - } - - /** - * Form submission handler for autosaving forms. - */ - public function autosaveFormSubmit($form, FormStateInterface $form_state) { - // As this processing might take some time we want to prevent that if the - // connection is terminated the user input will be lost. - ignore_user_abort(TRUE); - if (!$this->isAutosaveSubmitValid($form_state)) { - $form_state->disableCache(); - return; - } - // Having an autosave form session id also ensures that after resuming - // editing the new autosaved entities will be saved to the same autosave - // session id. - $silverback_autosave_session_id = $this->getAutosaveFormSessionID($form_state); - $current_user_id = $this->currentUser->id(); - $autosaved_form_state = $this->getLastAutosavedFormState($form_state, $silverback_autosave_session_id, $current_user_id); - // If there is non-autosaved state for this session then we have to put the - // user input into a temporary store and on each autosave submit compare - // against it for changes and after the first change compare with the last - // autosaved state. - if (is_null($autosaved_form_state)) { - if ($initial_user_input = $this->keyValueExpirableFactory->get('silverback_autosave')->get($silverback_autosave_session_id)) { - $autosaved_form_state_input = $initial_user_input; - } - else { - // 6 hours cache life time for forms should be plenty, like the form - // cache. - $expire = 21600; - $this->keyValueExpirableFactory->get('silverback_autosave')->setWithExpire($silverback_autosave_session_id, $form_state->getUserInput(), $expire); - - // This is the first where we cache the user input initially and we are - // done. - $form_state->disableCache(); - return; - } - } - else { - $autosaved_form_state_input = $autosaved_form_state->getUserInput(); - } - - // Subsequent autosaving - compare the user input only. This should be - // sufficient to detect changes in the fields. - $form_state_input = $form_state->getUserInput(); - - $skip_from_comparison_keys = [ - 'form_build_id', - 'form_token', - 'ajax_page_state', - 'silverback_autosave_last_autosave_timestamp', - ]; - foreach ($skip_from_comparison_keys as $skip_from_comparison_key) { - unset($autosaved_form_state_input[$skip_from_comparison_key]); - unset($form_state_input[$skip_from_comparison_key]); - } - - $store = $autosaved_form_state_input != $form_state_input; - - if ($store) { - $autosave_timestamp = $this->time->getRequestTime(); - $form_state->set('silverback_autosave_last_autosave_timestamp', $autosave_timestamp); - $form_state->setTemporaryValue('silverback_autosave_last_autosave_timestamp', $autosave_timestamp); - - $this->storeState($form_state, $silverback_autosave_session_id, $autosave_timestamp, $current_user_id); - $this->keyValueExpirableFactory->get('silverback_autosave')->delete($silverback_autosave_session_id); - } - - // We don't have to cache the form each time an autosave submission is - // triggered, especially when we've skipped the form validation. - $form_state->disableCache(); - } - - /** - * Ajax callback for autosaving forms. - */ - public function autosaveFormAjax($form, FormStateInterface $form_state) { - $response = new AjaxResponse(); - - $timestamp = $form_state->getTemporaryValue('silverback_autosave_last_autosave_timestamp'); - if (is_numeric($timestamp)) { - $response->addCommand(new InvokeCommand('input[name="silverback_autosave_last_autosave_timestamp"]', 'attr', ['value', $timestamp])); - } - - return $response; - } - - /** - * Retrieves the autosave form session ID. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return string|null - * The autosave form session ID or NULL if none present yet. - */ - protected static function getAutosaveFormSessionID(FormStateInterface $form_state) { - return $form_state->get('silverback_autosave_session_id'); - } - - /** - * Sets the autosave form session ID into the form state. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * @param string $silverback_autosave_session_id - * The autosave form session ID. - */ - protected function setAutosaveFormSessionID(FormStateInterface $form_state, $silverback_autosave_session_id) { - $form_state->set('silverback_autosave_session_id', $silverback_autosave_session_id); - } - - /** - * Returns the HTTP method used by the request that is building the form. - * - * @return string - * Can be any valid HTTP method, such as GET, POST, HEAD, etc. - */ - protected function getRequestMethod() { - return \Drupal::requestStack()->getCurrentRequest()->getMethod(); - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php deleted file mode 100644 index 3c8bd0b7f..000000000 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormBuilder.php +++ /dev/null @@ -1,251 +0,0 @@ -formBuilder = $form_builder; - $this->autosaveEntityFormStorage = $autosave_entity_form_storage; - } - - /** - * - */ - public function buildForm($form_id, FormStateInterface &$form_state) { - $form = parent::buildForm($form_id, $form_state); - if ($form_state::hasAnyErrors()) { - // Under circumstances it might happen that the form is submitted but - // returned with validation errors and the form alter hooks are executed - // thus leading to the autosave form alter code being executed as well and - // putting the autosave resume/discard message to the form, which should - // not happen if the form is being returned to the browser with validation - // errors. In order to prevent this we have to add the resume/discard - // message and options only on HTTP GET requests or on POST requests if - // restore or reject submit operations have been performed or in a more - // complex case if the message has not been yet confirmed but other - // AJAX / POST requests are being triggered in the background. As we could - // not detect the last case we still put the form elements into the form, - // but on the client side we will not show the message if the form is - // returned with validation errors. - $form['#attached']['drupalSettings']['autosaveForm']['formHasErrors'] = TRUE; - - // Additionally unset the form elements and settings which might have been - // added, but aren't actually needed. - unset($form['#attached']['drupalSettings']['autosaveForm']['message']); - } - return $form; - } - - /** - * {@inheritdoc} - */ - public function processForm($form_id, &$form, FormStateInterface &$form_state) { - if ($this->isAutosaveTriggered($form_state)) { - // @todo should we add a condition, that the form state is already cached - // in order to stop fully processing the form? - $this->doBuildFormSkip = TRUE; - - // As we'll skip doBuildForm we have to take care of setting the - // triggering element. - $form_state->setTriggeringElement($form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME]); - - // Needed to execute the submit handler, as this will not be done if - // duBuildForm is not being executed. - $form_state->setSubmitHandlers($form[AutosaveFormInterface::AUTOSAVE_ELEMENT_NAME]['#submit']); - $form_state->setProcessInput(); - $form_state->setSubmitted(); - } - - $response = parent::processForm($form_id, $form, $form_state); - $this->doBuildFormSkip = FALSE; - - return $response; - } - - /** - * {@inheritdoc} - */ - public function doBuildForm($form_id, &$element, FormStateInterface &$form_state) { - return $this->doBuildFormSkip ? $element : parent::doBuildForm($form_id, $element, $form_state); - } - - /** - * {@inheritdoc} - */ - public function rebuildForm($form_id, FormStateInterface &$form_state, $old_form = NULL) { - $this->restoreAutosavedState($form_id, $form_state); - return parent::rebuildForm($form_id, $form_state, $old_form); - } - - /** - * Restores an autosaved form state. - * - * @param $form_id - * The form id. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function restoreAutosavedState($form_id, FormStateInterface $form_state) { - if (!$form_state->get('silverback_autosave_restored') && ($autosaved_timestamp = $form_state->get('silverback_autosave_state_timestamp'))) { - $form_object = $form_state->getFormObject(); - - // Restore entity form. - if ($form_object instanceof EntityFormInterface) { - $entity = $form_object->getEntity(); - $autosaved_state = $this->autosaveEntityFormStorage->getEntityAndFormState($form_id, $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $this->currentUser()->id(), NULL, $autosaved_timestamp); - - if (is_null($autosaved_state)) { - // @todo Cover the case that the autosaved state has been purged - // meanwhile. - return; - } - - /** @var \Drupal\Core\Entity\EntityInterface $autosaved_entity */ - $autosaved_entity = $autosaved_state['entity']; - /** @var \Drupal\Core\Form\FormStateInterface $autosaved_form_state */ - $autosaved_form_state = $autosaved_state['form_state']; - - // Restore the form with the entity from the autosaved state. - $form_object->setEntity($autosaved_entity); - // Restore the user input. - $current_user_input = $form_state->getUserInput(); - $autosaved_user_input = $autosaved_form_state->getUserInput(); - // We have to rebuild the form and keep the generated form token - // instead of putting the one from the autosaved input, otherwise the - // form builder will set an form state error and, which is going to - // result into an exception, as setting form state errors after the - // validation phase is forbidden. - if (isset($current_user_input['form_token'])) { - $autosaved_user_input['form_token'] = $current_user_input['form_token']; - } - $form_state->setUserInput($autosaved_user_input); - // Recover the form state storage, which is needed to continue from the - // state at which the form was left. - $form_state->setStorage($autosaved_form_state->getStorage()); - - // Flag the form state as being restored from autosave. - $form_state->set('silverback_autosave_restored', TRUE); - } - elseif ($form_object instanceof FormInterface) { - // @todo add support for regular forms. - } - } - } - - /** - * {@inheritdoc} - */ - public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { - $prevent_hooks = FALSE; - - if ($this->isAutosaveTriggered($form_state)) { - // There is no need of generating a new form build id after triggering - // autosave. - $form['#build_id'] = $form_state->getUserInput()['form_build_id']; - - if ($form_state->isCached()) { - $prevent_hooks = TRUE; - } - } - - if ($prevent_hooks) { - // Prevent running hooks. - $module_handler = $this->moduleHandler; - $theme_manager = $this->themeManager; - $this->moduleHandler = new ModuleHandlerEmptyAlter(); - $this->themeManager = new ThemeManagerEmptyAlter(); - } - - parent::prepareForm($form_id, $form, $form_state); - - if ($prevent_hooks) { - $this->moduleHandler = $module_handler; - $this->themeManager = $theme_manager; - } - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php deleted file mode 100644 index cd8689c24..000000000 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormErrorHandler.php +++ /dev/null @@ -1,43 +0,0 @@ -formErrorHandler = $form_error_handler; - } - - /** - * {@inheritdoc} - */ - public function handleFormErrors(array &$form, FormStateInterface $form_state) { - if ($this->isAutosaveTriggered($form_state)) { - return $this; - } - return $this->formErrorHandler->handleFormErrors($form, $form_state); - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php deleted file mode 100644 index d08030316..000000000 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormInterface.php +++ /dev/null @@ -1,109 +0,0 @@ -entityTypeManager = $entity_type_manager; - $this->entityTypeBundleInfo = $entity_type_bundle_info; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity_type.manager'), - $container->get('entity_type.bundle.info') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'silverback_autosave_admin_settings'; - } - - /** - * {@inheritdoc} - */ - protected function getEditableConfigNames() { - return [ - 'silverback_autosave.settings', - ]; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $config = $this->config('silverback_autosave.settings'); - - $form['interval'] = [ - '#type' => 'number', - '#title' => $this->t('The interval to use for triggering autosave in milliseconds.'), - '#default_value' => $config->get('interval'), - ]; - $form['only_on_form_change'] = [ - '#type' => 'checkbox', - '#access' => FALSE, - '#title' => $this->t('Run only on form change.') . ' ' . $this->t('(Experimental)'), - '#description' => $this->t('If enabled an autosave submission will only occur if the form changed since the previous autosave submission.'), - '#default_value' => $config->get('only_on_form_change'), - ]; - - $form['active_on'] = [ - '#type' => 'fieldset', - '#title' => $this->t('Active on:'), - '#tree' => TRUE, - ]; - $form['active_on']['content_entity_forms'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Content Entity Forms'), - '#default_value' => $config->get('active_on')['content_entity_forms'], - ]; - - $form['notification'] = [ - '#type' => 'fieldset', - '#title' => $this->t('Notification settings'), - '#description' => $this->t('Display a simple notification every time content is saved'), - '#tree' => TRUE, - ]; - $form['notification']['active'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Active'), - '#default_value' => $config->get('notification')['active'], - ]; - $form['notification']['message'] = [ - '#type' => 'textfield', - '#title' => $this->t('Message'), - '#default_value' => $config->get('notification')['message'], - '#states' => [ - 'visible' => [ - ':input[name="notification[active]"]' => ['checked' => TRUE], - ], - ], - ]; - $form['notification']['delay'] = [ - '#type' => 'number', - '#title' => $this->t('The duration of the notification in milliseconds.'), - '#default_value' => $config->get('notification')['delay'], - '#states' => [ - 'visible' => [ - ':input[name="notification[active]"]' => ['checked' => TRUE], - ], - ], - ]; - - $allowed_content_entity_types = $config->get('allowed_content_entity_types'); - $form['allowed_content_entities'] = [ - '#type' => 'details', - '#open' => !empty($allowed_content_entity_types), - '#title' => $this->t('Allowed Content Entity Forms'), - '#description' => $this->t('In case no entity type is selected then autosave is enabled on all entity forms, otherwise it will be enabled only on the selected ones. Selecting only the entity type will enable all corresponding bundles and selecting only a subset of the bundles will enable autosave only for those bundles and will be disabled for the others.'), - '#tree' => TRUE, - '#states' => [ - 'visible' => [ - ':input[name="active_on[content_entity_forms]"]' => ['checked' => TRUE], - ], - ], - ]; - - /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ - foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { - if ($entity_type instanceof ContentEntityTypeInterface) { - $entity_type_label = $entity_type->getLabel(); - $bundles_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); - $allowed_bundles = !empty($allowed_content_entity_types[$entity_type_id]['bundles']) ? $allowed_content_entity_types[$entity_type_id]['bundles'] : []; - $bundles = []; - foreach ($bundles_info as $key => $bundle) { - $bundles[$key] = $bundle['label']; - } - - $form['allowed_content_entities'][$entity_type_id]['active'] = [ - '#type' => 'checkbox', - '#title' => $entity_type_label, - '#default_value' => isset($allowed_content_entity_types[$entity_type_id]), - ]; - $form['allowed_content_entities'][$entity_type_id]['bundles'] = [ - '#type' => 'details', - '#open' => !empty($allowed_bundles), - '#title' => $entity_type_label . ' ' . $this->t('bundles'), - '#states' => [ - 'visible' => [ - ':input[name="allowed_content_entities[' . $entity_type_id . '][active]"]' => ['checked' => TRUE], - ], - ], - ]; - $form['allowed_content_entities'][$entity_type_id]['bundles']['selection'] = [ - '#type' => 'checkboxes', - '#default_value' => $allowed_bundles, - '#options' => $bundles, - '#prefix' => '
', - '#suffix' => '
', - ]; - } - } - - return parent::buildForm($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - /** @var \Drupal\Core\Config\Config $config */ - $config = $this->config('silverback_autosave.settings'); - $allowed_content_entity_types = []; - foreach ($form_state->getValue('allowed_content_entities') as $entity_type_id => $data) { - if (!$data['active']) { - continue; - } - $allowed_bundles = array_filter($data['bundles']['selection']); - $allowed_content_entity_types[$entity_type_id]['bundles'] = $allowed_bundles; - } - $config->set('interval', $form_state->getValue('interval')) - ->set('only_on_form_change', $form_state->getValue('only_on_form_change')) - ->set('active_on', $form_state->getValue('active_on')) - ->set('notification', $form_state->getValue('notification')) - ->set('allowed_content_entity_types', $allowed_content_entity_types) - ->save(); - - parent::submitForm($form, $form_state); - } - -} diff --git a/packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php b/packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php deleted file mode 100644 index ad0fd6e00..000000000 --- a/packages/drupal/silverback_autosave/src/Form/AutosaveFormValidator.php +++ /dev/null @@ -1,80 +0,0 @@ -formValidator = $form_validator; - parent::__construct($request_stack, $string_translation, $csrf_token, $logger, $form_error_handler); - } - - /** - * {@inheritdoc} - */ - public function validateForm($form_id, &$form, FormStateInterface &$form_state) { - $autosave_submission = $this->isAutosaveTriggered($form_state); - - if ($autosave_submission) { - // On subsequent autosaving we don't need to execute the form validation - // as we are not going to build the intermediate entity. However it might - // happen that between here and the autosave submission handler the - // autosaved state has been purged and therefore we have to check - // explicitly for that there instead of building the intermediate entity. - $form_state->setTemporaryValue('silverback_autosave_form_validation_skipped', TRUE); - } - else { - // We have to execute the validation in the case of autosave submission - // for the very first time as in this case we'll build the intermediate - // entity for comparison and some input values are being prepared in the - // validate functions. This is the case with e.g. autocomplete for entity - // references. - $this->formValidator->validateForm($form_id, $form, $form_state); - } - - // In order for the autosave submit callback to be executed we have to - // clear the errors caused from the validation, otherwise no submit - // callbacks will be executed. - if ($autosave_submission && $form_state::hasAnyErrors()) { - $form_state->clearErrors(); - } - } - -} diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php deleted file mode 100644 index d03b89365..000000000 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormDatabaseStorage.php +++ /dev/null @@ -1,348 +0,0 @@ -connection = $connection; - $this->serializer = $serializer; - $this->httpClient = $httpClient; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function storeEntityAndFormState($form_id, $form_session_id, $entity_type_id, $entity_id, $langcode, $uid, $timestamp, EntityInterface $entity, FormStateInterface $form_state) { - $serialized_entity = $this->serializeEntity($entity, $form_state); - $serialized_form_state = $this->serializer->encode([ - 'storage' => $form_state->getStorage(), - 'input' => $form_state->getUserInput(), - ]); - - if (!$entity->isNew()) { - - // @todo FIX This causes new previews to restore the saved one (instead of current form state). - // Purge previous stored states for the entity, - // e.g. when user is navigating away from the page. - // Also, this approach ensures that there will be always - // a stored form state in the db, in case we need to restore. - // $this->purgeAutosavedEntityState($entity_type_id, $entity_id, NULL, $form_id, $langcode, $uid); - $this->connection->insert(static::AUTOSAVE_ENTITY_FORM_TABLE) - ->fields([ - 'form_id', - 'form_session_id', - 'entity_type_id', - 'entity_id', - 'langcode', - 'uid', - 'timestamp', - 'entity', - 'form_state', - ]) - ->values([ - $form_id, - $form_session_id, - $entity_type_id, - $entity_id, - $langcode, - $uid, - $timestamp, - $serialized_entity, - $serialized_form_state, - ]) - ->execute(); - try { - $this->httpClient->post((getenv('PREVIEW_URL') ?: 'http://localhost:8001') . '/__preview', [ - RequestOptions::HEADERS => [ - 'Content-Type' => 'application/json', - ], - RequestOptions::JSON => [ - 'entity_type_id' => $entity_type_id, - 'entity_id' => $entity_id, - 'langcode' => $langcode, - ], - ]); - } catch (GuzzleException $exc) { - $this->logger->critical('Error while to update preview.'); - $this->logger->critical($exc->getMessage()); - } - } - } - - /** - * {@inheritdoc} - */ - public function getEntityAndFormState($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL, $autosaved_timestamp = NULL) { - $result = NULL; - $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') - ->fields('cefa', ['entity', 'form_state', 'timestamp']) - ->orderBy('timestamp', 'DESC') - ->condition('form_id', $form_id); - - if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } - - $query->condition('entity_type_id', $entity_type_id) - ->condition('entity_id', $entity_id) - ->condition('langcode', $langcode) - ->condition('uid', $uid); - - if (isset($autosaved_timestamp)) { - $query->condition('timestamp', $autosaved_timestamp); - } - - $data = $query->execute()->fetchAssoc(); - - if ($data) { - // Prepare the entity object. - $result['entity'] = $this->serializer->decode($data['entity']); - // Prepare the form state. - $form_state_data = $this->serializer->decode($data['form_state']); - $result['form_state'] = new FormState(); - $result['form_state']->setStorage($form_state_data['storage']); - $result['form_state']->setUserInput($form_state_data['input']); - $result['form_state']->set('silverback_autosave_state_timestamp', $data['timestamp']); - } - - return $result; - } - - /** - * {@inheritdoc} - */ - public function getEntity($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL, $autosaved_timestamp = NULL) { - $entity = NULL; - $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') - ->fields('cefa', ['entity']) - ->orderBy('timestamp', 'DESC') - ->condition('form_id', $form_id); - - if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } - - $query->condition('entity_type_id', $entity_type_id) - ->condition('entity_id', $entity_id) - ->condition('langcode', $langcode) - ->condition('uid', $uid); - - if (isset($autosaved_timestamp)) { - $query->condition('timestamp', $autosaved_timestamp); - } - - $data = $query->execute()->fetchAssoc(); - - if ($data) { - // Prepare the entity object. - $entity = $this->serializer->decode($data['entity']); - } - - return $entity; - } - - /** - * {@inheritdoc} - */ - public function getFormState($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL, $autosaved_timestamp = NULL) { - $form_state = NULL; - $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') - ->fields('cefa', ['form_state', 'timestamp']) - ->orderBy('timestamp', 'DESC') - ->condition('form_id', $form_id); - - if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } - - $query->condition('entity_type_id', $entity_type_id) - ->condition('entity_id', $entity_id) - ->condition('langcode', $langcode) - ->condition('uid', $uid); - - if (isset($autosaved_timestamp)) { - $query->condition('timestamp', $autosaved_timestamp); - } - - $data = $query->execute()->fetchAssoc(); - - if ($data) { - // Prepare the form state. - $form_state_data = $this->serializer->decode($data['form_state']); - $form_state = new FormState(); - $form_state->setStorage($form_state_data['storage']); - $form_state->setUserInput($form_state_data['input']); - $form_state->set('silverback_autosave_state_timestamp', $data['timestamp']); - } - - return $form_state; - } - - /** - * {@inheritdoc} - */ - public function hasAutosavedStateForFormState(FormStateInterface $form_state, $uid) { - $result = FALSE; - if (($form_object = $form_state->getFormObject()) && ($form_object instanceof EntityFormInterface) && ($entity = $form_object->getEntity()) && !$entity->isNew()) { - $result = $this->hasAutosavedState($form_object->getFormId(), $entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $uid, $form_state->get('silverback_autosave_session_id')); - } - return $result; - } - - /** - * {@inheritdoc} - */ - public function hasAutosavedState($form_id, $entity_type_id, $entity_id, $langcode, $uid, $form_session_id = NULL) { - $query = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') - ->condition('form_id', $form_id); - - if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } - - $query->condition('entity_type_id', $entity_type_id) - ->condition('entity_id', $entity_id) - ->condition('langcode', $langcode) - ->condition('uid', $uid); - - $count = $query->countQuery() - ->execute() - ->fetchField(); - return (bool) $count; - } - - /** - * {@inheritdoc} - */ - public function getLastAutosavedStateTimestamp($form_id, $entity_type_id, $entity_id, $langcode, $uid) { - $timestamp = $this->connection->select(static::AUTOSAVE_ENTITY_FORM_TABLE, 'cefa') - ->fields('cefa', ['timestamp']) - ->orderBy('timestamp', 'DESC') - ->condition('form_id', $form_id) - ->condition('entity_type_id', $entity_type_id) - ->condition('entity_id', $entity_id) - ->condition('langcode', $langcode) - ->condition('uid', $uid) - ->execute() - ->fetchField(); - - return is_bool($timestamp) ? NULL : $timestamp; - } - - /** - * {@inheritdoc} - */ - public function purgeAutosavedEntityState($entity_type_id, $entity_id, $form_session_id = NULL, $form_id = NULL, $langcode = NULL, $uid = NULL) { - $query = $this->connection->delete(static::AUTOSAVE_ENTITY_FORM_TABLE); - - if (isset($form_session_id)) { - $query->condition('form_session_id', $form_session_id); - } - - if (isset($form_id)) { - $query->condition('form_id', $form_id); - } - - $query->condition('entity_type_id', $entity_type_id) - ->condition('entity_id', $entity_id); - - if (isset($langcode)) { - $query->condition('langcode', $langcode); - } - - if (isset($uid)) { - $query->condition('uid', $uid); - } - - $query->execute(); - } - - /** - * {@inheritdoc} - */ - public function purgeAutosavedEntitiesStates($entity_type_id = NULL, $langcode = NULL, $uid = NULL) { - $query = $this->connection->delete(static::AUTOSAVE_ENTITY_FORM_TABLE); - if (isset($entity_type_id)) { - $query->condition('entity_type_id', $entity_type_id); - } - if (isset($langcode)) { - $query->condition('langcode', $langcode); - } - if (isset($uid)) { - $query->condition('uid', $uid); - } - $query->execute(); - } - - /** - * Returns the serialized entity object. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The entity object to serialize. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state to retrieve meta information about the serialization. - * - * @return string - * The serialized entity. - */ - protected function serializeEntity(EntityInterface $entity, FormStateInterface $form_state) { - if (($form_object = $form_state->getFormObject()) && ($form_object instanceof ContentEntityFormInterface) && method_exists($form_object, 'isEntityDeepSerializationRequired') && $form_object->isEntityDeepSerializationRequired()) { - /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ - if (method_exists($entity, 'setDeepSerialization')) { - $entity->setDeepSerialization(TRUE); - } - } - - return $this->serializer->encode($entity); - } - -} diff --git a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php b/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php deleted file mode 100644 index 36cf6b0f7..000000000 --- a/packages/drupal/silverback_autosave/src/Storage/AutosaveEntityFormStorageInterface.php +++ /dev/null @@ -1,203 +0,0 @@ - Date: Thu, 25 Apr 2024 22:14:16 +0200 Subject: [PATCH 085/221] fix: lagoon prod env --- apps/cms/.lagoon.env.prod | 6 +++--- apps/preview/.lagoon.env.prod | 2 +- apps/website/.lagoon.env.prod | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/cms/.lagoon.env.prod b/apps/cms/.lagoon.env.prod index 0309eb662..3e820b07e 100644 --- a/apps/cms/.lagoon.env.prod +++ b/apps/cms/.lagoon.env.prod @@ -1,7 +1,7 @@ PROJECT_NAME=example -PUBLISHER_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.build.amazeelabs.dev" -NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" -PREVIEW_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.preview.amazeelabs.dev" +PUBLISHER_URL="https://example.build.amazeelabs.dev" +NETLIFY_URL="https://example.amazeelabs.dev" +PREVIEW_URL="https://example.preview.amazeelabs.dev" # Used to set the original client secret. PUBLISHER_OAUTH2_CLIENT_SECRET=REPLACE_ME diff --git a/apps/preview/.lagoon.env.prod b/apps/preview/.lagoon.env.prod index 2c80dc9c1..364cf1a4d 100644 --- a/apps/preview/.lagoon.env.prod +++ b/apps/preview/.lagoon.env.prod @@ -1 +1 @@ -DRUPAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" +DRUPAL_URL="https://example.cms.amazeelabs.dev" diff --git a/apps/website/.lagoon.env.prod b/apps/website/.lagoon.env.prod index 89379ad89..0cd30cc45 100644 --- a/apps/website/.lagoon.env.prod +++ b/apps/website/.lagoon.env.prod @@ -1,7 +1,7 @@ PROJECT_NAME=example DRUPAL_INTERNAL_URL="http://nginx:8080" -DRUPAL_EXTERNAL_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.cms.amazeelabs.dev" -NETLIFY_URL="https://${LAGOON_GIT_BRANCH}-${PROJECT_NAME}.amazeelabs.dev" +DRUPAL_EXTERNAL_URL="https://example.cms.amazeelabs.dev" +NETLIFY_URL="https://example.amazeelabs.dev" # ----------------------------------------------- # Publisher authentication with Drupal (OAuth2). From 432d4dd29008cfe7c3ee178c337b753f66547471 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Mon, 29 Apr 2024 12:53:00 +0300 Subject: [PATCH 086/221] chore(SLB-314): use Link instead of a tag --- .../components/Organisms/PageContent/BlockCta.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx index ab5df05d0..cdf574951 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx @@ -1,21 +1,27 @@ -import { BlockCtaFragment, CtaIconPosition, CtaIconType } from '@custom/schema'; +import { + BlockCtaFragment, + CtaIconPosition, + CtaIconType, + Link, + Url, +} from '@custom/schema'; import clsx from 'clsx'; import React from 'react'; export function BlockCta(props: BlockCtaFragment) { return ( - {props.text} {!!props.icon && props.icon === CtaIconType.Arrow && } - + ); } From 0db95250010de3db394eb8fa68a4db81bd8f5ce7 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 29 Apr 2024 18:25:56 +0200 Subject: [PATCH 087/221] feat(SLB-287): add custom class to teaser container --- .../Organisms/PageContent/BlockBackgroundImageCards.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx index b8133f8d0..55783c56a 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx @@ -11,8 +11,8 @@ import { isTruthy } from '../../../utils/isTruthy'; export function BlockBackgroundImageCards(props: BlockImageTeasers) { return ( // eslint-disable-next-line tailwindcss/no-custom-classname -
-
+
+
{props.teasers.filter(isTruthy).map((teaser, index) => ( @@ -25,10 +25,10 @@ export function BlockBackgroundImageCards(props: BlockImageTeasers) { export function BlockBackgroundImageCard(props: BlockImageTeaser) { return ( -
+
{props.image ? ( {props.image.alt} From d51c6b34848878ef604a563267c1fed1c1dbbde7 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 29 Apr 2024 18:27:14 +0200 Subject: [PATCH 088/221] feat(SLB-287): create style to remove margin top on default hero --- packages/ui/src/components/Organisms/PageHero.tsx | 2 +- packages/ui/src/tailwind.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index 55a1088ca..5e96cfcee 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -15,7 +15,7 @@ export function PageHero(props: NonNullable) { function DefaultHero(props: NonNullable) { return ( -
+
{props.image ? ( {props.image.alt} :first-child.block-background-image-cards { + @apply mt-2 px-0; +} \ No newline at end of file From f06d8f87c2b8744c8a115e1950d7783ae82ab5db Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 29 Apr 2024 18:27:40 +0200 Subject: [PATCH 089/221] feat(SLB-287): add component to the page and add stories --- .../src/components/Organisms/PageDisplay.tsx | 86 ++++++++----------- .../ui/src/components/Routes/Page.stories.tsx | 9 ++ 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..6eb63aca2 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { UnreachableCaseError } from '../../utils/unreachable-case-error'; import { PageTransition } from '../Molecules/PageTransition'; +import { BlockBackgroundImageCards } from './PageContent/BlockBackgroundImageCards'; import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; import { BlockMarkup } from './PageContent/BlockMarkup'; @@ -14,58 +15,41 @@ import { PageHero } from './PageHero'; export function PageDisplay(page: PageFragment) { return ( + {page.hero ? : null}
- {page.hero ? : null} -
-
- {page?.content?.filter(isTruthy).map((block, index) => { - switch (block.__typename) { - case 'BlockMedia': - return ; - case 'BlockMarkup': - return ; - case 'BlockForm': - return ; - case 'BlockImageTeasers': - return ( - // TODO: Implement BlockImageTeasers -
- BlockImageTeasers goes here -
- ); - case 'BlockCta': - return ; - case 'BlockImageWithText': - return ( - // TODO: Implement BlockImageWithText -
- BlockImageWithText goes here -
- ); - case 'BlockQuote': - return
; - default: - throw new UnreachableCaseError(block); - } - })} -
-
+ {page?.content?.filter(isTruthy).map((block, index) => { + switch (block.__typename) { + case 'BlockMedia': + return ; + case 'BlockMarkup': + return ; + case 'BlockForm': + return ; + case 'BlockImageTeasers': + return ; + case 'BlockCta': + return ; + case 'BlockImageWithText': + return ( + // TODO: Implement BlockImageWithText +
+ BlockImageWithText goes here +
+ ); + case 'BlockQuote': + return
; + default: + throw new UnreachableCaseError(block); + } + })}
); diff --git a/packages/ui/src/components/Routes/Page.stories.tsx b/packages/ui/src/components/Routes/Page.stories.tsx index f3d048c25..2b2478cd4 100644 --- a/packages/ui/src/components/Routes/Page.stories.tsx +++ b/packages/ui/src/components/Routes/Page.stories.tsx @@ -10,6 +10,7 @@ import { Meta, StoryObj } from '@storybook/react'; import React from 'react'; import { image } from '../../helpers/image'; +import { Default as BlockBackgroundImageCards } from '../Organisms/PageContent/BlockBackgroundImageCards.stories'; import { Mixed, Paragraph } from '../Organisms/PageContent/BlockMarkup.stories'; import { WithCaption } from '../Organisms/PageContent/BlockMedia.stories'; import { Default as FrameStory } from './Frame.stories'; @@ -47,6 +48,10 @@ export const Default = { ctaText: 'Call to action', }, content: [ + { + __typename: 'BlockImageTeasers', + ...BlockBackgroundImageCards.args, + }, { __typename: 'BlockMarkup', ...Mixed.args, @@ -59,6 +64,10 @@ export const Default = { __typename: 'BlockMarkup', ...Paragraph.args, }, + { + __typename: 'BlockImageTeasers', + ...BlockBackgroundImageCards.args, + }, ] as Exclude['content'], }, }, From 7cdbac7892ebc4f01e6da131da76dfd0d7d6cebb Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Tue, 30 Apr 2024 09:34:05 +0300 Subject: [PATCH 090/221] feat(SLB-288): add image with text component --- packages/drupal/gutenberg_blocks/css/edit.css | 43 ++++++- .../PageContent/BlockImageWithText.gql | 1 + .../PageContent/BlockImageWithText.stories.ts | 107 ++++++++++++++++++ .../PageContent/BlockImageWithText.tsx | 37 ++++++ .../Organisms/PageContent/BlockMarkup.tsx | 23 ---- packages/ui/src/tailwind.css | 38 +++++++ packages/ui/static/icons/arrow.svg | 5 + packages/ui/static/icons/check.svg | 4 + .../ui/static/icons/question-mark-circle.svg | 5 + 9 files changed, 237 insertions(+), 26 deletions(-) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockImageWithText.stories.ts create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx create mode 100644 packages/ui/static/icons/arrow.svg create mode 100644 packages/ui/static/icons/check.svg create mode 100644 packages/ui/static/icons/question-mark-circle.svg diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 39ab66473..e951d483f 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -1,10 +1,10 @@ /* We can put any editor specific styling in here */ .gutenberg__editor .editor-styles-wrapper { - font-family:inherit; + font-family: inherit; } .gutenberg__editor blockquote { - margin:0; + margin: 0; } .gutenberg__editor blockquote .quote-image img { @@ -40,4 +40,41 @@ left: 0; transform-origin: 0 0; transform: rotate(90deg); -} \ No newline at end of file +} + +.gutenberg__editor ul li { + line-height: 1.5rem; +} +.gutenberg__editor .list-style--arrows li, +.gutenberg__editor .list-style--checkmarks li, +.gutenberg__editor .list-style--question-marks li { + list-style-type: none; + position: relative; +} + +.gutenberg__editor .list-style--arrows li::before { + position: absolute; + left: -1.25rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='arrow-narrow-right'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M9.83401 4.23435C9.98403 4.08437 10.1875 4.00012 10.3996 4.00012C10.6117 4.00012 10.8152 4.08437 10.9652 4.23435L14.1652 7.43435C14.3152 7.58437 14.3994 7.78782 14.3994 7.99995C14.3994 8.21208 14.3152 8.41553 14.1652 8.56555L10.9652 11.7656C10.8143 11.9113 10.6122 11.9919 10.4025 11.9901C10.1927 11.9883 9.99208 11.9041 9.84375 11.7558C9.69543 11.6075 9.61129 11.4068 9.60947 11.1971C9.60765 10.9873 9.68828 10.7852 9.83401 10.6344L11.6684 8.79995H2.39961C2.18744 8.79995 1.98395 8.71567 1.83392 8.56564C1.68389 8.41561 1.59961 8.21212 1.59961 7.99995C1.59961 7.78778 1.68389 7.5843 1.83392 7.43427C1.98395 7.28424 2.18744 7.19995 2.39961 7.19995H11.6684L9.83401 5.36555C9.68403 5.21553 9.59978 5.01208 9.59978 4.79995C9.59978 4.58782 9.68403 4.38437 9.83401 4.23435Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); +} + +.gutenberg__editor .list-style--checkmarks li::before { + position: absolute; + left: -1.25rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); +} + +.gutenberg__editor .list-style--question-marks li::before { + position: absolute; + left: -1.25rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='question-mark-circle'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M18 10C18 12.1217 17.1571 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18C7.87827 18 5.84344 17.1571 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C12.1217 2 14.1566 2.84285 15.6569 4.34315C17.1571 5.84344 18 7.87827 18 10ZM10 7C9.8243 6.99983 9.65165 7.04595 9.49945 7.13373C9.34724 7.22151 9.22085 7.34784 9.133 7.5C9.06957 7.61788 8.98311 7.72182 8.87876 7.80566C8.77441 7.8895 8.65429 7.95154 8.52552 7.9881C8.39675 8.02466 8.26194 8.03499 8.12911 8.01849C7.99627 8.00198 7.86809 7.95897 7.75218 7.89201C7.63628 7.82505 7.53499 7.7355 7.45433 7.62867C7.37367 7.52184 7.31529 7.3999 7.28263 7.27008C7.24997 7.14027 7.24371 7.00522 7.26421 6.87294C7.28472 6.74065 7.33157 6.61384 7.402 6.5C7.73222 5.92811 8.24191 5.48116 8.85203 5.22846C9.46214 4.97576 10.1386 4.93144 10.7765 5.10236C11.4143 5.27328 11.978 5.64989 12.38 6.1738C12.782 6.6977 13 7.33962 13 8C13.0002 8.62062 12.8079 9.22603 12.4498 9.73285C12.0916 10.2397 11.5851 10.623 11 10.83V11C11 11.2652 10.8946 11.5196 10.7071 11.7071C10.5196 11.8946 10.2652 12 10 12C9.73478 12 9.48043 11.8946 9.29289 11.7071C9.10536 11.5196 9 11.2652 9 11V10C9 9.73478 9.10536 9.48043 9.29289 9.29289C9.48043 9.10536 9.73478 9 10 9C10.2652 9 10.5196 8.89464 10.7071 8.70711C10.8946 8.51957 11 8.26522 11 8C11 7.73478 10.8946 7.48043 10.7071 7.29289C10.5196 7.10536 10.2652 7 10 7ZM10 15C10.2652 15 10.5196 14.8946 10.7071 14.7071C10.8946 14.5196 11 14.2652 11 14C11 13.7348 10.8946 13.4804 10.7071 13.2929C10.5196 13.1054 10.2652 13 10 13C9.73478 13 9.48043 13.1054 9.29289 13.2929C9.10536 13.4804 9 13.7348 9 14C9 14.2652 9.10536 14.5196 9.29289 14.7071C9.48043 14.8946 9.73478 15 10 15Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); +} diff --git a/packages/schema/src/fragments/PageContent/BlockImageWithText.gql b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql index b026e8676..6fda48e0d 100644 --- a/packages/schema/src/fragments/PageContent/BlockImageWithText.gql +++ b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql @@ -6,4 +6,5 @@ fragment BlockImageWithText on BlockImageWithText { textContent { markup } + imagePosition } diff --git a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.stories.ts new file mode 100644 index 000000000..133a619f9 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.stories.ts @@ -0,0 +1,107 @@ +import { ImagePosition, Markup } from '@custom/schema'; +import Landscape from '@stories/landscape.jpg?as=metadata'; +import { Meta, StoryObj } from '@storybook/react'; + +import { image } from '../../../helpers/image'; +import { BlockImageWithText } from './BlockImageWithText'; + +export default { + component: BlockImageWithText, +} satisfies Meta; + +export const ImageRight = { + args: { + image: { + source: image(Landscape), + alt: 'Landscape', + }, + imagePosition: ImagePosition.Right, + textContent: { + markup: ` +

Image Right

+
    +
  • short item.
  • +
  • Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo.
  • +
  • Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.
  • +
+ ` as Markup, + }, + }, +} satisfies StoryObj; + +export const ImageLeft = { + args: { + image: { + source: image(Landscape), + alt: 'Landscape', + }, + imagePosition: ImagePosition.Left, + textContent: { + markup: ` +

Image Left

+
    +
  • short item.
  • +
  • Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo.
  • +
  • Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.
  • +
+ ` as Markup, + }, + }, +} satisfies StoryObj; + +export const ArrowList = { + args: { + image: { + source: image(Landscape), + alt: 'Landscape', + }, + textContent: { + markup: ` +

Arrow List

+
    +
  • short item.
  • +
  • Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo.
  • +
  • Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.
  • +
+ ` as Markup, + }, + }, +} satisfies StoryObj; + +export const QuestionMarkList = { + args: { + image: { + source: image(Landscape), + alt: 'Landscape', + }, + textContent: { + markup: ` +

Question Mark List

+
    +
  • short item.
  • +
  • Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo.
  • +
  • Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.
  • +
+ ` as Markup, + }, + }, +} satisfies StoryObj; + +export const CheckMarkList = { + args: { + image: { + source: image(Landscape), + alt: 'Landscape', + }, + textContent: { + markup: ` +

Check Mark List

+
    +
  • short item.
  • +
  • 2 liners item Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo.
  • +
  • Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus. Et magna sit morbi lobortis.
  • +
+ ` as Markup, + }, + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx new file mode 100644 index 000000000..d9736386b --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx @@ -0,0 +1,37 @@ +import { + BlockImageWithTextFragment, + Image, + ImagePosition, +} from '@custom/schema'; +import clsx from 'clsx'; +import React from 'react'; + +import { BlockMarkup } from './BlockMarkup'; + +export function BlockImageWithText(props: BlockImageWithTextFragment) { + console.log(props); + return ( +
+ {!!props.image?.source && ( +
+ {props.image.alt +
+ )} + +
+ {props.textContent?.markup && } +
+
+ ); +} diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index a83f81b79..ae4ef62f9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -1,5 +1,4 @@ import { BlockMarkupFragment, Html } from '@custom/schema'; -import { ArrowRightCircleIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; import type { Element } from 'hast'; import { selectAll } from 'hast-util-select'; @@ -21,33 +20,11 @@ export function BlockMarkup(props: BlockMarkupFragment) { 'prose-em:text-indigo-600', 'prose-strong:text-indigo-600', 'marker:text-indigo-600 marker:font-bold', - 'prose-h2:text-indigo-600 prose-h2:font-bold', ])} > ) => { - return ( -
  • - {unordered ? ( - - ) : null} - {children} -
  • - ); - }, blockquote: ({ children }: PropsWithChildren<{}>) => { return (
    diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 944e19871..30c8dbd70 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -34,3 +34,41 @@ )::after { content: '' !important; } + +ul li { + @apply !text-base !font-medium !text-gray-900; +} + +.list-style--arrows li, +.list-style--checkmarks li, +.list-style--question-marks li { + list-style-type: none; + position: relative; +} + +.list-style--arrows li::before { + position: absolute; + left: -1.25rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url('static/icons/arrow.svg'); +} + +.list-style--checkmarks li::before { + position: absolute; + left: -1.25rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url('static/icons/check.svg'); +} + +.list-style--question-marks li::before { + position: absolute; + left: -1.25rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 1px; + content: url('static/icons/question-mark-circle.svg'); +} diff --git a/packages/ui/static/icons/arrow.svg b/packages/ui/static/icons/arrow.svg new file mode 100644 index 000000000..f282f2a20 --- /dev/null +++ b/packages/ui/static/icons/arrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/static/icons/check.svg b/packages/ui/static/icons/check.svg new file mode 100644 index 000000000..8309f1dc3 --- /dev/null +++ b/packages/ui/static/icons/check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/static/icons/question-mark-circle.svg b/packages/ui/static/icons/question-mark-circle.svg new file mode 100644 index 000000000..90114df4b --- /dev/null +++ b/packages/ui/static/icons/question-mark-circle.svg @@ -0,0 +1,5 @@ + + + + + From 9afa08fbae04a919fa941c57a3dc622c3919ec5a Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Tue, 30 Apr 2024 13:49:05 +0400 Subject: [PATCH 091/221] chore: make publisher work again locally --- apps/website/publisher.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/website/publisher.config.ts b/apps/website/publisher.config.ts index abaf98f01..6c5d95797 100644 --- a/apps/website/publisher.config.ts +++ b/apps/website/publisher.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ // cannot report it. // Workaround: Do a double build on the first build. 'if test -d public; then echo "Single build" && pnpm build:gatsby; else echo "Double build" && pnpm build:gatsby && pnpm build:gatsby; fi' - : 'pnpm build:gatsby', + : 'DRUPAL_EXTERNAL_URL=http://127.0.0.1:8888 pnpm build:gatsby', outputTimeout: 1000 * 60 * 10, }, clean: 'pnpm clean', From 731027ad295cd563155905af8e8630fa76b350ff Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 18:45:24 +0400 Subject: [PATCH 092/221] chore: ensure iframeResizer script is present before saving the form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Somehow it was missing on the "error" page 🤷 --- tests/e2e/webform-snapshots/webforms.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/e2e/webform-snapshots/webforms.spec.ts b/tests/e2e/webform-snapshots/webforms.spec.ts index bdd7fbb51..c3eb894e5 100644 --- a/tests/e2e/webform-snapshots/webforms.spec.ts +++ b/tests/e2e/webform-snapshots/webforms.spec.ts @@ -5,6 +5,8 @@ import { execSync } from 'child_process'; import { cmsUrl } from '../helpers/url'; const baseDir = '../../packages/ui/static/stories/webforms'; +const iframeResizerSelector = + 'script[src*="silverback_iframe/js/iframeResizer.contentWindow.min.js"]'; test('Export webforms for styling', async ({ page }) => { execSync(`rm -rf ${baseDir}`); @@ -12,6 +14,7 @@ test('Export webforms for styling', async ({ page }) => { const baseUrl = cmsUrl('/en/form/styling?iframe=true&no_css=true'); await page.goto(baseUrl); + await expect(page.locator(iframeResizerSelector)).toHaveCount(1); await savePage(page, 'idle'); await page.getByLabel('Email with a confirmation').fill('asd@asd'); @@ -19,6 +22,7 @@ test('Export webforms for styling', async ({ page }) => { await expect( page.locator('[data-drupal-messages] [role="alert"]'), ).toBeVisible(); + await expect(page.locator(iframeResizerSelector)).toHaveCount(1); await savePage(page, 'error'); }); From 264ec90f346e0946c9e5bf08a43fc10050dbfa9d Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 18:58:33 +0400 Subject: [PATCH 093/221] chore: let chromatic's turbosnap know when webforms are changed --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8f39a873..e93af97c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,7 @@ jobs: storybookBaseDir: packages/ui onlyChanged: true exitOnceUploaded: true + externals: packages/ui/static/stories/webforms/** if: ${{ steps.chromatic-check.outputs.available == 'true' }} - name: Deploy storybook to netlify From b3487bc964c0b455a8695c1cb82d258c9e874535 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 19:13:04 +0400 Subject: [PATCH 094/221] chore: redo "Test without Turbo cache" workflow Previously: No felixmosh/turborepo-gh-artifacts, Turbo can't read/write persisted caches Now: Turbo is executed with --force flag This way we can override broken Turbo caches --- .github/workflows/test_without_turbo_cache.yml | 17 +++++++++++++++-- package.json | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_without_turbo_cache.yml b/.github/workflows/test_without_turbo_cache.yml index 837d24c68..c247a595e 100644 --- a/.github/workflows/test_without_turbo_cache.yml +++ b/.github/workflows/test_without_turbo_cache.yml @@ -3,11 +3,14 @@ on: workflow_dispatch: jobs: - test: name: Test runs-on: ubuntu-20.04 steps: + - name: Init check + if: ${{ github.repository != 'AmazeeLabs/silverback-template'}} + run: echo 'Please run the INIT script. See the root README.md for instructions.' && false + - name: Checkout uses: actions/checkout@v3 with: @@ -16,8 +19,18 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: TurboRepo local server + uses: felixmosh/turborepo-gh-artifacts@v2 + with: + server-token: 'local' + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Test - run: pnpm turbo:test + run: pnpm turbo:test:force + env: + TURBO_API: 'http://127.0.0.1:9080' + TURBO_TOKEN: 'local' + TURBO_TEAM: 'local' - name: Upload Playwright report uses: actions/upload-artifact@v3 diff --git a/package.json b/package.json index e4b531976..12321130c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "test:format:workspaces": "pnpm --workspace-concurrency=1 -r exec prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}' --ignore-path='./.gitignore'", "turbo:local": "if [ -z $CI ]; then echo $(date)$RANDOM > apps/cms/turbo-seed.txt; fi", "turbo:test": "pnpm turbo:local && pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only && pnpm tb test:integration --no-daemon --go-fallback --output-logs=new-only --concurrency=1", + "turbo:test:force": "pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only --force && pnpm tb test:integration --no-daemon --go-fallback --output-logs=new-only --concurrency=1 --force", "turbo:test:quick": "pnpm turbo:local && pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only", "turbo:prep": "pnpm turbo:local && pnpm tb prep --no-daemon --go-fallback --output-logs=new-only", "turbo:prep:force": "rm -f apps/cms/web/sites/default/files/.sqlite && pnpm tb prep --no-daemon --go-fallback --force", From 91859833de474b66c245b2d307251616aaed492b Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 19:44:28 +0400 Subject: [PATCH 095/221] chore: remove useless classname Also trigger BlockForm stories re-test in Chromatic. --- .../src/components/Organisms/PageContent/BlockForm.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx index 4b9471c37..9d255cea9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx @@ -19,6 +19,5 @@ export const Error = { args: { url: 'webforms/error/index.html' as Url, cssStylesToInject: cmsCss, - className: 'mt-16', }, } satisfies StoryObj; From b018dfadf4aff8699bddab7fc0cb350694fc1fb8 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Thu, 2 May 2024 08:05:17 +0300 Subject: [PATCH 096/221] chore(SLB-288): change icon paths --- packages/ui/src/tailwind.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 30c8dbd70..3a3f4a561 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -52,7 +52,7 @@ ul li { vertical-align: middle; line-height: 1.5rem; margin-top: 2px; - content: url('static/icons/arrow.svg'); + content: url('../static/icons/arrow.svg'); } .list-style--checkmarks li::before { @@ -61,7 +61,7 @@ ul li { vertical-align: middle; line-height: 1.5rem; margin-top: 2px; - content: url('static/icons/check.svg'); + content: url('../static/icons/check.svg'); } .list-style--question-marks li::before { @@ -70,5 +70,5 @@ ul li { vertical-align: middle; line-height: 1.5rem; margin-top: 1px; - content: url('static/icons/question-mark-circle.svg'); + content: url('../static/icons/question-mark-circle.svg'); } From 21601df40446c08778a36855de20bb2d3b0a7b33 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Fri, 3 May 2024 08:38:34 +0300 Subject: [PATCH 097/221] fix(SLB-288): adjust spacing --- .../PageContent/BlockImageWithText.tsx | 2 +- packages/ui/src/tailwind.css | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx index d9736386b..9b804f6d9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx @@ -13,7 +13,7 @@ export function BlockImageWithText(props: BlockImageWithTextFragment) { return (
    Date: Fri, 3 May 2024 08:21:42 +0200 Subject: [PATCH 098/221] feat(SLB-287): update height on mobile --- .../Organisms/PageContent/BlockBackgroundImageCards.tsx | 4 ++-- packages/ui/src/tailwind.css | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx index 55783c56a..f3cbc828b 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockBackgroundImageCards.tsx @@ -25,10 +25,10 @@ export function BlockBackgroundImageCards(props: BlockImageTeasers) { export function BlockBackgroundImageCard(props: BlockImageTeaser) { return ( -
    +
    {props.image ? ( {props.image.alt} diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index 8e10ec3a8..a6ea88154 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -35,6 +35,9 @@ content: '' !important; } +/* Selects the .block-background-image-cards only when it is the first child of the
    + element immediately following the .default-hero section. + */ .default-hero + div > :first-child.block-background-image-cards { @apply mt-2 px-0; } \ No newline at end of file From 28e7ba37b6c30c4cdb92da3be41857e6ada7601f Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Fri, 3 May 2024 10:38:56 +0300 Subject: [PATCH 099/221] chore(SLB-288): seperate list styles --- packages/ui/src/components/Atoms/List.css | 44 ++++++++++++++++ .../PageContent/BlockImageWithText.tsx | 41 +++++++-------- packages/ui/src/tailwind.css | 50 +------------------ 3 files changed, 66 insertions(+), 69 deletions(-) create mode 100644 packages/ui/src/components/Atoms/List.css diff --git a/packages/ui/src/components/Atoms/List.css b/packages/ui/src/components/Atoms/List.css new file mode 100644 index 000000000..0d9ea8fa8 --- /dev/null +++ b/packages/ui/src/components/Atoms/List.css @@ -0,0 +1,44 @@ +ul li { + @apply !text-base !font-medium !text-gray-900; +} + +ul { + @apply !pl-4; +} + +.list-style--arrows li, +.list-style--checkmarks li, +.list-style--question-marks li { + list-style-type: none; + position: relative; + padding-left: 0.625rem; + margin-left: 0; +} + +.list-style--arrows li::before { + position: absolute; + left: -1rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='arrow-narrow-right'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M9.83401 4.23435C9.98403 4.08437 10.1875 4.00012 10.3996 4.00012C10.6117 4.00012 10.8152 4.08437 10.9652 4.23435L14.1652 7.43435C14.3152 7.58437 14.3994 7.78782 14.3994 7.99995C14.3994 8.21208 14.3152 8.41553 14.1652 8.56555L10.9652 11.7656C10.8143 11.9113 10.6122 11.9919 10.4025 11.9901C10.1927 11.9883 9.99208 11.9041 9.84375 11.7558C9.69543 11.6075 9.61129 11.4068 9.60947 11.1971C9.60765 10.9873 9.68828 10.7852 9.83401 10.6344L11.6684 8.79995H2.39961C2.18744 8.79995 1.98395 8.71567 1.83392 8.56564C1.68389 8.41561 1.59961 8.21212 1.59961 7.99995C1.59961 7.78778 1.68389 7.5843 1.83392 7.43427C1.98395 7.28424 2.18744 7.19995 2.39961 7.19995H11.6684L9.83401 5.36555C9.68403 5.21553 9.59978 5.01208 9.59978 4.79995C9.59978 4.58782 9.68403 4.38437 9.83401 4.23435Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); + list-style-position: inside; +} + +.list-style--checkmarks li::before { + position: absolute; + left: -1.125rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 2px; + content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); +} + +.list-style--question-marks li::before { + position: absolute; + left: -1.125rem; + vertical-align: middle; + line-height: 1.5rem; + margin-top: 1px; + content: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='question-mark-circle'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M18 10C18 12.1217 17.1571 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18C7.87827 18 5.84344 17.1571 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C12.1217 2 14.1566 2.84285 15.6569 4.34315C17.1571 5.84344 18 7.87827 18 10ZM10 7C9.8243 6.99983 9.65165 7.04595 9.49945 7.13373C9.34724 7.22151 9.22085 7.34784 9.133 7.5C9.06957 7.61788 8.98311 7.72182 8.87876 7.80566C8.77441 7.8895 8.65429 7.95154 8.52552 7.9881C8.39675 8.02466 8.26194 8.03499 8.12911 8.01849C7.99627 8.00198 7.86809 7.95897 7.75218 7.89201C7.63628 7.82505 7.53499 7.7355 7.45433 7.62867C7.37367 7.52184 7.31529 7.3999 7.28263 7.27008C7.24997 7.14027 7.24371 7.00522 7.26421 6.87294C7.28472 6.74065 7.33157 6.61384 7.402 6.5C7.73222 5.92811 8.24191 5.48116 8.85203 5.22846C9.46214 4.97576 10.1386 4.93144 10.7765 5.10236C11.4143 5.27328 11.978 5.64989 12.38 6.1738C12.782 6.6977 13 7.33962 13 8C13.0002 8.62062 12.8079 9.22603 12.4498 9.73285C12.0916 10.2397 11.5851 10.623 11 10.83V11C11 11.2652 10.8946 11.5196 10.7071 11.7071C10.5196 11.8946 10.2652 12 10 12C9.73478 12 9.48043 11.8946 9.29289 11.7071C9.10536 11.5196 9 11.2652 9 11V10C9 9.73478 9.10536 9.48043 9.29289 9.29289C9.48043 9.10536 9.73478 9 10 9C10.2652 9 10.5196 8.89464 10.7071 8.70711C10.8946 8.51957 11 8.26522 11 8C11 7.73478 10.8946 7.48043 10.7071 7.29289C10.5196 7.10536 10.2652 7 10 7ZM10 15C10.2652 15 10.5196 14.8946 10.7071 14.7071C10.8946 14.5196 11 14.2652 11 14C11 13.7348 10.8946 13.4804 10.7071 13.2929C10.5196 13.1054 10.2652 13 10 13C9.73478 13 9.48043 13.1054 9.29289 13.2929C9.10536 13.4804 9 13.7348 9 14C9 14.2652 9.10536 14.5196 9.29289 14.7071C9.48043 14.8946 9.73478 15 10 15Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); +} diff --git a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx index 9b804f6d9..813f12ca0 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx @@ -9,28 +9,29 @@ import React from 'react'; import { BlockMarkup } from './BlockMarkup'; export function BlockImageWithText(props: BlockImageWithTextFragment) { - console.log(props); return ( -
    - {!!props.image?.source && ( -
    - {props.image.alt -
    - )} +
    +
    + {!!props.image?.source && ( +
    + {props.image.alt +
    + )} -
    - {props.textContent?.markup && } +
    + {props.textContent?.markup && } +
    ); diff --git a/packages/ui/src/tailwind.css b/packages/ui/src/tailwind.css index bc30df44c..f977bbc41 100644 --- a/packages/ui/src/tailwind.css +++ b/packages/ui/src/tailwind.css @@ -1,5 +1,6 @@ /* Import all atom stylesheets. */ @import './components/Atoms/Container.css'; +@import './components/Atoms/List.css'; @tailwind base; @tailwind components; @@ -34,52 +35,3 @@ )::after { content: '' !important; } - -ul li { - @apply !text-base !font-medium !text-gray-900; -} - -ul { - @apply !pl-4; - -} - -.list-style--arrows li, -.list-style--checkmarks li, -.list-style--question-marks li { - list-style-type: none; - position: relative; - padding-left: 0.625rem; - margin-left:0; -} - -.list-style--arrows li::before { - position: absolute; - left:-1rem; - vertical-align: middle; - line-height: 1.5rem; - margin-top: 2px; - content: url('../static/icons/arrow.svg'); - list-style-position: inside; - /* width: 14px; - margin-left: -14px; */ - -} - -.list-style--checkmarks li::before { - position: absolute; - left:-1.125rem; - vertical-align: middle; - line-height: 1.5rem; - margin-top: 2px; - content: url('../static/icons/check.svg'); -} - -.list-style--question-marks li::before { - position: absolute; - left:-1.125rem; - vertical-align: middle; - line-height: 1.5rem; - margin-top: 1px; - content: url('../static/icons/question-mark-circle.svg'); -} From 2f80ac1b1660f860b0a3894530ba1e32a02abe29 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Fri, 3 May 2024 11:54:00 +0300 Subject: [PATCH 100/221] chore(SLB-288): add block to page --- .../ui/src/components/Organisms/PageDisplay.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..bf26afcb3 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -6,6 +6,7 @@ import { UnreachableCaseError } from '../../utils/unreachable-case-error'; import { PageTransition } from '../Molecules/PageTransition'; import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; +import { BlockImageWithText } from './PageContent/BlockImageWithText'; import { BlockMarkup } from './PageContent/BlockMarkup'; import { BlockMedia } from './PageContent/BlockMedia'; import { BlockQuote } from './PageContent/BlockQuote'; @@ -44,20 +45,7 @@ export function PageDisplay(page: PageFragment) { case 'BlockCta': return ; case 'BlockImageWithText': - return ( - // TODO: Implement BlockImageWithText -
    - BlockImageWithText goes here -
    - ); + return ; case 'BlockQuote': return
    ; default: From 60c91344a7fbe193fc7edaf3c067df1a749a71c8 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Fri, 3 May 2024 13:02:10 +0300 Subject: [PATCH 101/221] test(SLB-288): adjust e2e tests for new element --- .../content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml | 3 --- .../components/Organisms/PageContent/BlockImageWithText.tsx | 2 +- tests/e2e/specs/drupal/blocks.spec.ts | 4 ++-- tests/schema/specs/blocks.spec.ts | 2 -- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index ebd66747b..21dda2902 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -74,9 +74,6 @@ default:

    Heading

    - -
    12
    34
    Caption
    -

    Quote

    Citation
    diff --git a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx index 813f12ca0..475d00384 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockImageWithText.tsx @@ -10,7 +10,7 @@ import { BlockMarkup } from './BlockMarkup'; export function BlockImageWithText(props: BlockImageWithTextFragment) { return ( -
    +
    { page.locator('a:text("link")[href="/en/architecture"]'), ).toHaveCount(1); - // Image + // Image and ImageWithText block await expect( page.locator( 'img:not([data-test-id=hero-image])[alt="A beautiful landscape."]', ), - ).toHaveCount(1); + ).toHaveCount(2); await expect(page.locator('figcaption:text("Media image")')).toHaveCount(1); // Video diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 4730f8d2a..7b0940115 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -139,8 +139,6 @@ test('Blocks', async () => {

    Heading

    -
    12
    34
    Caption
    -

    Quote

    Citation

    From f939768dc3d3fe3cb2af428dff32df90dbc7859b Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 22:37:27 +0400 Subject: [PATCH 102/221] chore: probably externals should be relative to storybookBaseDir --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e93af97c4..d9e6ac435 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,8 @@ jobs: storybookBaseDir: packages/ui onlyChanged: true exitOnceUploaded: true - externals: packages/ui/static/stories/webforms/** + externals: | + static/stories/webforms/** if: ${{ steps.chromatic-check.outputs.available == 'true' }} - name: Deploy storybook to netlify From 383cd03c31b998b169ead9499733fd2a2e576d0f Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 22:38:25 +0400 Subject: [PATCH 103/221] chore(SLB-272): extend styling webform with terms-of-service components --- .../drupal/test_content/webforms/styling.yml | 17 +++++++++ .../PageContent/BlockForm.stories.tsx | 14 ++++++++ tests/e2e/tsconfig.json | 2 +- tests/e2e/webform-snapshots/webforms.spec.ts | 36 +++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/drupal/test_content/webforms/styling.yml b/packages/drupal/test_content/webforms/styling.yml index 7cfdd2291..8b6848977 100644 --- a/packages/drupal/test_content/webforms/styling.yml +++ b/packages/drupal/test_content/webforms/styling.yml @@ -30,6 +30,7 @@ elements: |- '#title': Textfield '#description': 'Textfield description.' '#placeholder': 'Placeholder goes here' + '#required': true email_with_a_conformation: '#type': webform_email_confirm '#title': 'Email with a confirmation' @@ -56,6 +57,22 @@ elements: |- Foo: Foo Bar: Bar Baz: Baz + terms_of_service: + '#type': webform_terms_of_service + '#title': 'I agree to the {terms of service} in modal' + '#required': true + '#terms_content': |- + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a felis convallis, pharetra mauris a, eleifend justo. Sed massa ipsum, suscipit sit amet varius a, dignissim at ex. Integer euismod a sapien id auctor. Etiam dignissim scelerisque nunc in porttitor. Nam rutrum molestie mauris, a bibendum ligula. Etiam eget posuere enim. Quisque id condimentum ligula. + + Duis sit amet sapien justo. Suspendisse potenti. Aliquam venenatis congue quam, quis eleifend tellus. Duis aliquam sollicitudin purus sed rutrum. Nullam vitae ante lorem. Curabitur malesuada nibh lectus, ut ultricies elit mattis sed. Ut pharetra urna nec lacus pellentesque vestibulum. Nulla turpis sem, aliquam finibus pharetra at, interdum sit amet ligula. In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce ullamcorper justo in risus condimentum suscipit. Nam in nunc sit amet neque fringilla suscipit. Sed nec metus tempus, rhoncus dolor et, vehicula urna. Donec ultricies leo pharetra ante vulputate vulputate. Nullam tincidunt pharetra nisi eu faucibus. Duis efficitur, massa eget sagittis sodales, magna dolor dapibus justo, et facilisis tellus augue et est. + terms_of_service_01: + '#type': webform_terms_of_service + '#title': 'I agree to the {terms of service} in slideout' + '#terms_type': slideout + '#terms_content': |- + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a felis convallis, pharetra mauris a, eleifend justo. Sed massa ipsum, suscipit sit amet varius a, dignissim at ex. Integer euismod a sapien id auctor. Etiam dignissim scelerisque nunc in porttitor. Nam rutrum molestie mauris, a bibendum ligula. Etiam eget posuere enim. Quisque id condimentum ligula. + + Duis sit amet sapien justo. Suspendisse potenti. Aliquam venenatis congue quam, quis eleifend tellus. Duis aliquam sollicitudin purus sed rutrum. Nullam vitae ante lorem. Curabitur malesuada nibh lectus, ut ultricies elit mattis sed. Ut pharetra urna nec lacus pellentesque vestibulum. Nulla turpis sem, aliquam finibus pharetra at, interdum sit amet ligula. In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce ullamcorper justo in risus condimentum suscipit. Nam in nunc sit amet neque fringilla suscipit. Sed nec metus tempus, rhoncus dolor et, vehicula urna. Donec ultricies leo pharetra ante vulputate vulputate. Nullam tincidunt pharetra nisi eu faucibus. Duis efficitur, massa eget sagittis sodales, magna dolor dapibus justo, et facilisis tellus augue et est. actions: '#type': webform_actions '#title': 'Submit button(s)' diff --git a/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx index 9d255cea9..8e9ce2c94 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx @@ -21,3 +21,17 @@ export const Error = { cssStylesToInject: cmsCss, }, } satisfies StoryObj; + +export const TermsOfServiceModal = { + args: { + url: 'webforms/terms-of-service-modal/index.html' as Url, + cssStylesToInject: cmsCss, + }, +} satisfies StoryObj; + +export const TermsOfServiceSlideout = { + args: { + url: 'webforms/terms-of-service-slideout/index.html' as Url, + cssStylesToInject: cmsCss, + }, +} satisfies StoryObj; diff --git a/tests/e2e/tsconfig.json b/tests/e2e/tsconfig.json index 87555134a..ed6fc0aa9 100644 --- a/tests/e2e/tsconfig.json +++ b/tests/e2e/tsconfig.json @@ -3,7 +3,7 @@ "strict": true, "skipLibCheck": true, "esModuleInterop": true, - "lib": ["es2015"], + "lib": ["es2015", "DOM"], "types": ["node"] } } diff --git a/tests/e2e/webform-snapshots/webforms.spec.ts b/tests/e2e/webform-snapshots/webforms.spec.ts index c3eb894e5..1fe4ba862 100644 --- a/tests/e2e/webform-snapshots/webforms.spec.ts +++ b/tests/e2e/webform-snapshots/webforms.spec.ts @@ -13,10 +13,28 @@ test('Export webforms for styling', async ({ page }) => { const baseUrl = cmsUrl('/en/form/styling?iframe=true&no_css=true'); + page.setViewportSize({ + // In the frontend the form is wrapped into a div with max-w-3xl class, + // which translates to 48 rem, which is 768 px. Roughly. + // We try to match the width because the modal is centered dynamically by + // Drupal JS, but then, when we save the page, the size and position are + // fixed in the HTML. + width: 768, + // Does not matter. + height: 768, + }); + await page.goto(baseUrl); await expect(page.locator(iframeResizerSelector)).toHaveCount(1); await savePage(page, 'idle'); + // Disable frontend validation. + await page.evaluate(() => { + document.querySelectorAll('[required]').forEach((element) => { + element.removeAttribute('required'); + }); + }); + await page.getByLabel('Email with a confirmation').fill('asd@asd'); await page.getByRole('button', { name: 'Submit' }).click(); await expect( @@ -24,6 +42,24 @@ test('Export webforms for styling', async ({ page }) => { ).toBeVisible(); await expect(page.locator(iframeResizerSelector)).toHaveCount(1); await savePage(page, 'error'); + + await page.goto(baseUrl); + await page + .locator('label') + .filter({ hasText: 'I agree to the terms of service in modal' }) + .getByRole('button') + .click(); + await page.waitForTimeout(500); + await savePage(page, 'terms-of-service-modal'); + + await page.goto(baseUrl); + await page + .locator('label') + .filter({ hasText: 'I agree to the terms of service in slideout' }) + .getByRole('button') + .click(); + await page.waitForTimeout(500); + await savePage(page, 'terms-of-service-slideout'); }); async function savePage(page: Page, name: string) { From c6bf2bde145802ee881856b156d85963ef84381b Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 23:02:39 +0400 Subject: [PATCH 104/221] chore(SLB-272): add flexbox component --- .../drupal/test_content/webforms/styling.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/drupal/test_content/webforms/styling.yml b/packages/drupal/test_content/webforms/styling.yml index 8b6848977..b73dbb9c2 100644 --- a/packages/drupal/test_content/webforms/styling.yml +++ b/packages/drupal/test_content/webforms/styling.yml @@ -25,12 +25,17 @@ elements: |- Foo: Foo Bar: 'Bar -- Bar has a description.' Baz: Baz - textfield: - '#type': textfield - '#title': Textfield - '#description': 'Textfield description.' - '#placeholder': 'Placeholder goes here' - '#required': true + flexbox: + '#type': webform_flexbox + textfield: + '#type': textfield + '#title': 'First name' + '#description': 'Textfield description.' + '#placeholder': 'Placeholder goes here' + '#required': true + last_name: + '#type': textfield + '#title': 'Last name' email_with_a_conformation: '#type': webform_email_confirm '#title': 'Email with a confirmation' From b728fbf38a87df70dce120a0fe42ab0a6809e182 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 1 May 2024 23:07:29 +0400 Subject: [PATCH 105/221] chore(SLB-272): chromatic to use a single viewport on modal story --- .../components/Organisms/PageContent/BlockForm.stories.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx index 8e9ce2c94..a0311eca8 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockForm.stories.tsx @@ -27,6 +27,13 @@ export const TermsOfServiceModal = { url: 'webforms/terms-of-service-modal/index.html' as Url, cssStylesToInject: cmsCss, }, + parameters: { + chromatic: { + // Makes no sense to test on different viewports because the modal size + // and position are hardcoded in the form snapshot. + viewports: [1440], + }, + }, } satisfies StoryObj; export const TermsOfServiceSlideout = { From 6af014617be5c427539d6ce90d7dad3025436fb0 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 6 May 2024 23:05:58 +0200 Subject: [PATCH 106/221] feat(SLB-272): add horizontal padding to avoid focus style to be cropped --- packages/ui/src/iframe.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index f0599c01a..6d100c94e 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -7,6 +7,11 @@ h1 { @apply hidden; } +/* Horizontal padding to avoid focus style to be cropped by the iframe */ +.webform-submission-form { + @apply px-1 text-gray-900; +} + /* Vertical spacing */ .webform-submission-form > *:not(:last-child) { @apply mb-10; From 420e47bc6a01dffd7d904c714d381e4a6d4e0eab Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 6 May 2024 23:06:34 +0200 Subject: [PATCH 107/221] feat(SLB-272): update checkbox style --- packages/ui/src/iframe.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 6d100c94e..4b1e3884d 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -36,7 +36,7 @@ h1 { /* field description */ .form-item .webform-element-description { - @apply text-sm text-gray-600; + @apply text-sm text-gray-500; } .form-item .description { @@ -68,7 +68,7 @@ h1 { /* checkbox */ .form-item input[type="checkbox"] { - @apply rounded; + @apply rounded -mt-1; } .form-item input[type="checkbox"]:not(:checked), .form-item input[type="radio"]:not(:checked) { From 23beb9d062d3441a9d70f83e87371d50bae21862 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Mon, 6 May 2024 23:07:06 +0200 Subject: [PATCH 108/221] feat(SLB-272): add 'terms of service' style --- packages/ui/src/iframe.css | 46 +++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 4b1e3884d..4c7353615 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -88,10 +88,15 @@ h1 { @apply text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center; } -.form-item input.error { +.form-item input.error, .form-item textarea.error, .form-item select.error, .form-item input[type="checkbox"].error { @apply border-red-500; } +.form-required:after { + @apply content-['*'] text-gray-900 pl-1; +} + + .fieldset-legend { @apply font-bold mb-2 block; } @@ -99,3 +104,42 @@ h1 { [data-drupal-messages] [role="alert"] { @apply text-red-500; } + +/* terms of service field */ +.form-type-webform-terms-of-service { + @apply text-gray-500; +} + +.form-type-webform-terms-of-service a { + @apply text-gray-900 underline; +} + +.form-type-webform-terms-of-service a:hover { + @apply text-gray-900 no-underline; +} + +/* terms of service field - Modal */ +.ui-widget-overlay { + @apply opacity-0 pointer-events-none; +} + +.ui-widget.ui-widget-content { + @apply bg-white border border-gray-300 rounded-lg px-0; +} + +.ui-widget.ui-widget-content .ui-widget-header { + @apply bg-white border-0 border-b border-gray-300 py-3 px-10; +} + +.ui-widget.ui-widget-content .ui-button { + @apply border-0; +} + +.ui-dialog .ui-dialog-titlebar-close { + @apply right-5; +} + +/* terms of service field - Dropdown */ +.webform-terms-of-service-details { + @apply text-gray-900 border-gray-300 border rounded-lg p-5 leading-relaxed; +} From f2bda6e933e331cd56ba976e1f3334ae338023c2 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Tue, 7 May 2024 07:39:04 +0200 Subject: [PATCH 109/221] feat(SLB-272): customize close icon --- packages/ui/src/iframe.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 4c7353615..d03100f2d 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -139,6 +139,20 @@ h1 { @apply right-5; } +.ui-dialog .ui-button .ui-icon-closethick, .ui-dialog .ui-button:hover .ui-icon-closethick { + background-image: url("data:image/svg+xml;utf8,"); + @apply bg-no-repeat bg-center h-10 w-10 -left-0.5 -top-0.5 rounded-lg; +} + +.ui-dialog .ui-button:hover .ui-icon-closethick { + background-image: url("data:image/svg+xml;utf8,"); + @apply text-red-600 bg-gray-100; +} + +.ui-dialog .ui-button:focus { + @apply bg-white; +} + /* terms of service field - Dropdown */ .webform-terms-of-service-details { @apply text-gray-900 border-gray-300 border rounded-lg p-5 leading-relaxed; From bf852c98ac33cd45575f2b1a8e1c00a4318cda9f Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Tue, 7 May 2024 07:39:47 +0200 Subject: [PATCH 110/221] feat(SLB-272): increase hero form width to enable drupal field flexbox --- packages/ui/src/components/Organisms/PageHero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index 55a1088ca..61b9ea3ea 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -107,7 +107,7 @@ function FormHero(props: NonNullable) {
    {props.formUrl ? (
    -
    +
    From 78386c635002efdbbf46ee0df9fd75183406ab7b Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Tue, 7 May 2024 08:37:58 +0200 Subject: [PATCH 111/221] feat(SLB-272): style 'skip to main content' link inside iframe --- packages/ui/src/iframe.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index d03100f2d..8633ffd05 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -7,6 +7,15 @@ h1 { @apply hidden; } +/* Skip to main content link on iframe */ +.visually-hidden.focusable { + @apply sr-only; +} + +.visually-hidden.focusable:active, .visually-hidden.focusable:focus { + @apply not-sr-only ml-1; +} + /* Horizontal padding to avoid focus style to be cropped by the iframe */ .webform-submission-form { @apply px-1 text-gray-900; @@ -107,7 +116,7 @@ h1 { /* terms of service field */ .form-type-webform-terms-of-service { - @apply text-gray-500; + @apply text-gray-500 text-sm; } .form-type-webform-terms-of-service a { From 067f1abcb37d2a8de918472cf71591d03c3e81aa Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 8 May 2024 07:56:30 +0200 Subject: [PATCH 112/221] fix(SLB-360): update fields and labels font size --- packages/ui/src/iframe.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 8633ffd05..d831fd558 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -32,7 +32,7 @@ h1 { /* Label */ .form-item label { - @apply block mb-2; + @apply block mb-2 text-sm; } .form-item input[type="checkbox"] + label, .form-item input[type="radio"] + label { @@ -72,7 +72,7 @@ h1 { .form-item input[type="color"], .form-item textarea, .form-item select { - @apply bg-gray-50 border border-gray-300 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5; + @apply bg-gray-50 border border-gray-300 text-base rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5; } /* checkbox */ From 46f10cbf5145df66269d18886537c8b7739ed36b Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 8 May 2024 07:58:14 +0200 Subject: [PATCH 113/221] fix(SLB-361): add style to visually-hidden class globally --- packages/ui/src/iframe.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index d831fd558..affa0ef69 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -21,6 +21,11 @@ h1 { @apply px-1 text-gray-900; } +/* add style to visually-hidden class globally */ +.webform-submission-form .visually-hidden { + @apply sr-only; +} + /* Vertical spacing */ .webform-submission-form > *:not(:last-child) { @apply mb-10; From d842ece3505e404eae06ecbde0d2cb131a35a5f2 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 8 May 2024 07:58:51 +0200 Subject: [PATCH 114/221] fix(SLB-358): reduce vertical spacing between fields --- packages/ui/src/iframe.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index affa0ef69..4a8695e58 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -27,10 +27,15 @@ h1 { } /* Vertical spacing */ +.webform-submission-form .webform-flexbox { + @apply md:my-0 gap-y-4 md:gap-y-0 flex flex-col md:flex-row; +} + .webform-submission-form > *:not(:last-child) { - @apply mb-10; + @apply mb-4 md:mb-6; } + .fieldset-wrapper > *:not(:last-child) { @apply mb-5; } From d34be48eb7da45f86970ae4a5bb45965d3cde211 Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 8 May 2024 09:53:01 +0200 Subject: [PATCH 115/221] fix(SLB-359): terms of service dropdown starts closed --- packages/ui/src/iframe.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 4a8695e58..36a13ad02 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -174,5 +174,5 @@ h1 { /* terms of service field - Dropdown */ .webform-terms-of-service-details { - @apply text-gray-900 border-gray-300 border rounded-lg p-5 leading-relaxed; + @apply text-gray-900 border-gray-300 border rounded-lg p-5 leading-relaxed hidden; } From 893f369837a9845278417501d8d9d2ee3496f607 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 8 May 2024 15:48:01 +0400 Subject: [PATCH 116/221] chore: update prettier An attempt to resolve prettier errors in dev branch, --- package.json | 2 +- pnpm-lock.yaml | 181 +++++++++++++++++++++++++++++-------------------- 2 files changed, 108 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 12321130c..9d06dcc45 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "^10.0.0", "husky": "^8.0.3", - "prettier": "^3.1.1", + "prettier": "^3.2.5", "turbo": "^1.11.2", "typescript": "^5.3.3", "vitest": "^1.1.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05006d49f..cf1905123 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^18.4.3 - version: 18.4.3(@types/node@18.19.31)(typescript@5.3.3) + version: 18.4.3(@types/node@20.11.17)(typescript@5.3.3) '@commitlint/config-conventional': specifier: ^18.4.3 version: 18.4.3 @@ -54,8 +54,8 @@ importers: specifier: ^8.0.3 version: 8.0.3 prettier: - specifier: ^3.1.1 - version: 3.1.1 + specifier: ^3.2.5 + version: 3.2.5 turbo: specifier: ^1.11.2 version: 1.11.2 @@ -64,13 +64,13 @@ importers: version: 5.3.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@18.19.31) + version: 1.1.1(@types/node@20.11.17) apps/cms: dependencies: '@amazeelabs/gatsby-source-silverback': specifier: '*' - version: 1.14.0(@types/node@18.19.31)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.3)(typescript@5.4.4) + version: 1.14.0(@types/node@20.11.17)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.3)(typescript@5.4.4) '@custom/custom': specifier: workspace:* version: link:../../packages/drupal/custom @@ -279,7 +279,7 @@ importers: version: 1.0.1 '@amazeelabs/gatsby-source-silverback': specifier: ^1.14.0 - version: 1.14.0(@types/node@18.19.31)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.1)(typescript@4.9.5) + version: 1.14.0(@types/node@20.11.17)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.1)(typescript@4.9.5) '@amazeelabs/publisher': specifier: ^2.4.17 version: 2.4.17(@types/react@18.2.46)(react@18.2.0)(typescript@4.9.5) @@ -339,7 +339,7 @@ importers: version: 2.1.35 netlify-cli: specifier: ^17.21.1 - version: 17.21.1(@types/node@18.19.31) + version: 17.21.1(@types/node@20.11.17) react: specifier: ^18.2.0 version: 18.2.0 @@ -373,7 +373,7 @@ importers: version: 2.0.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@18.19.31)(happy-dom@12.10.3) + version: 1.1.1(@types/node@20.11.17)(happy-dom@12.10.3) packages/drupal/custom: {} @@ -601,7 +601,7 @@ importers: version: 8.0.0-alpha.14(jest@29.7.0)(vitest@1.1.1) '@storybook/test-runner': specifier: ^0.16.0 - version: 0.16.0(@types/node@18.19.31) + version: 0.16.0(@types/node@20.11.17) '@swc/cli': specifier: ^0.1.63 version: 0.1.63(@swc/core@1.3.102) @@ -694,7 +694,7 @@ importers: version: 5.3.3 vite: specifier: ^5.0.10 - version: 5.0.10(@types/node@18.19.31) + version: 5.0.10(@types/node@20.11.17) vite-imagetools: specifier: ^6.2.9 version: 6.2.9 @@ -703,7 +703,7 @@ importers: version: 1.0.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@18.19.31)(happy-dom@12.10.3) + version: 1.1.1(@types/node@20.11.17)(happy-dom@12.10.3) tests/e2e: devDependencies: @@ -996,7 +996,7 @@ packages: - utf-8-validate dev: false - /@amazeelabs/gatsby-source-silverback@1.14.0(@types/node@18.19.31)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.1)(typescript@4.9.5): + /@amazeelabs/gatsby-source-silverback@1.14.0(@types/node@20.11.17)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.1)(typescript@4.9.5): resolution: {integrity: sha512-tIL4lPx7mQDBH5XiouXgTEhOIXF/oKDss0OYbHJEbxXVofv4IDifZcZZO1Hw9oWmrTSaJhYoC2Bdm+2kdvxf6g==} peerDependencies: gatsby-plugin-sharp: ^5.13.1 @@ -1006,7 +1006,7 @@ packages: gatsby-graphql-source-toolkit: 2.0.4(gatsby@5.13.1) gatsby-plugin-sharp: 5.13.1(gatsby@5.13.1)(graphql@16.8.1) graphql: 16.8.1 - graphql-config: 5.0.3(@types/node@18.19.31)(graphql@16.8.1)(typescript@4.9.5) + graphql-config: 5.0.3(@types/node@20.11.17)(graphql@16.8.1)(typescript@4.9.5) isomorphic-fetch: 3.0.0 lodash-es: 4.17.21 node-fetch: 3.3.2 @@ -1021,7 +1021,7 @@ packages: - utf-8-validate dev: false - /@amazeelabs/gatsby-source-silverback@1.14.0(@types/node@18.19.31)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.3)(typescript@5.4.4): + /@amazeelabs/gatsby-source-silverback@1.14.0(@types/node@20.11.17)(gatsby-plugin-sharp@5.13.1)(gatsby@5.13.3)(typescript@5.4.4): resolution: {integrity: sha512-tIL4lPx7mQDBH5XiouXgTEhOIXF/oKDss0OYbHJEbxXVofv4IDifZcZZO1Hw9oWmrTSaJhYoC2Bdm+2kdvxf6g==} peerDependencies: gatsby-plugin-sharp: ^5.13.1 @@ -1031,7 +1031,7 @@ packages: gatsby-graphql-source-toolkit: 2.0.4(gatsby@5.13.3) gatsby-plugin-sharp: 5.13.1(gatsby@5.13.3)(graphql@16.8.1) graphql: 16.8.1 - graphql-config: 5.0.3(@types/node@18.19.31)(graphql@16.8.1)(typescript@5.4.4) + graphql-config: 5.0.3(@types/node@20.11.17)(graphql@16.8.1)(typescript@5.4.4) isomorphic-fetch: 3.0.0 lodash-es: 4.17.21 node-fetch: 3.3.2 @@ -2687,14 +2687,14 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - /@commitlint/cli@18.4.3(@types/node@18.19.31)(typescript@5.3.3): + /@commitlint/cli@18.4.3(@types/node@20.11.17)(typescript@5.3.3): resolution: {integrity: sha512-zop98yfB3A6NveYAZ3P1Mb6bIXuCeWgnUfVNkH4yhIMQpQfzFwseadazOuSn0OOfTt0lWuFauehpm9GcqM5lww==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.6.1 '@commitlint/lint': 18.6.1 - '@commitlint/load': 18.6.1(@types/node@18.19.31)(typescript@5.3.3) + '@commitlint/load': 18.6.1(@types/node@20.11.17)(typescript@5.3.3) '@commitlint/read': 18.6.1 '@commitlint/types': 18.6.1 execa: 5.1.1 @@ -2765,7 +2765,7 @@ packages: '@commitlint/types': 18.6.1 dev: true - /@commitlint/load@18.6.1(@types/node@18.19.31)(typescript@5.3.3): + /@commitlint/load@18.6.1(@types/node@20.11.17)(typescript@5.3.3): resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} engines: {node: '>=v18'} dependencies: @@ -2775,7 +2775,7 @@ packages: '@commitlint/types': 18.6.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.3.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -4720,7 +4720,7 @@ packages: transitivePeerDependencies: - '@types/node' - /@graphql-tools/executor-http@1.0.9(@types/node@18.19.31)(graphql@16.8.1): + /@graphql-tools/executor-http@1.0.9(@types/node@20.11.17)(graphql@16.8.1): resolution: {integrity: sha512-+NXaZd2MWbbrWHqU4EhXcrDbogeiCDmEbrAN+rMn4Nu2okDjn2MTFDbTIab87oEubQCH4Te1wDkWPKrzXup7+Q==} engines: {node: '>=16.0.0'} peerDependencies: @@ -4731,7 +4731,7 @@ packages: '@whatwg-node/fetch': 0.9.17 extract-files: 11.0.0 graphql: 16.8.1 - meros: 1.3.0(@types/node@18.19.31) + meros: 1.3.0(@types/node@20.11.17) tslib: 2.6.2 value-or-promise: 1.0.12 transitivePeerDependencies: @@ -5044,7 +5044,7 @@ packages: - encoding - utf-8-validate - /@graphql-tools/url-loader@8.0.2(@types/node@18.19.31)(graphql@16.8.1): + /@graphql-tools/url-loader@8.0.2(@types/node@20.11.17)(graphql@16.8.1): resolution: {integrity: sha512-1dKp2K8UuFn7DFo1qX5c1cyazQv2h2ICwA9esHblEqCYrgf69Nk8N7SODmsfWg94OEaI74IqMoM12t7eIGwFzQ==} engines: {node: '>=16.0.0'} peerDependencies: @@ -5053,7 +5053,7 @@ packages: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/delegate': 10.0.4(graphql@16.8.1) '@graphql-tools/executor-graphql-ws': 1.1.2(graphql@16.8.1) - '@graphql-tools/executor-http': 1.0.9(@types/node@18.19.31)(graphql@16.8.1) + '@graphql-tools/executor-http': 1.0.9(@types/node@20.11.17)(graphql@16.8.1) '@graphql-tools/executor-legacy-ws': 1.0.6(graphql@16.8.1) '@graphql-tools/utils': 10.1.2(graphql@16.8.1) '@graphql-tools/wrap': 10.0.5(graphql@16.8.1) @@ -5683,7 +5683,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.3.3) typescript: 5.3.3 - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) dev: true /@jridgewell/gen-mapping@0.3.5: @@ -6032,7 +6032,7 @@ packages: yargs: 17.7.2 dev: false - /@netlify/build@29.36.6(@opentelemetry/api@1.8.0)(@types/node@18.19.31): + /@netlify/build@29.36.6(@opentelemetry/api@1.8.0)(@types/node@20.11.17): resolution: {integrity: sha512-crNoY5Vr7tAodBfYdz8weM+NTw5q6W6ArkowNw6QhKXa4iRXT5MY6H0c2ztsge9o5gAYs55bDhBpKiPcZlzDlA==} engines: {node: ^14.16.0 || >=16.0.0} hasBin: true @@ -6097,7 +6097,7 @@ packages: strip-ansi: 7.1.0 supports-color: 9.4.0 terminal-link: 3.0.0 - ts-node: 10.9.2(@types/node@18.19.31)(typescript@5.4.4) + ts-node: 10.9.2(@types/node@20.11.17)(typescript@5.4.4) typescript: 5.4.4 uuid: 9.0.0 yargs: 17.7.2 @@ -8352,7 +8352,7 @@ packages: magic-string: 0.30.9 rollup: 3.29.4 typescript: 5.3.3 - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - encoding - supports-color @@ -8861,7 +8861,7 @@ packages: react: 18.2.0 react-docgen: 7.0.3 react-dom: 18.2.0(react@18.2.0) - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -8935,7 +8935,7 @@ packages: - supports-color dev: true - /@storybook/test-runner@0.16.0(@types/node@18.19.31): + /@storybook/test-runner@0.16.0(@types/node@20.11.17): resolution: {integrity: sha512-LDmNbKFoEDW/VS9o6KR8e1r5MnbCc5ZojUfi5yqLdq80gFD7BvilgKgV0lUh/xWHryzoy+Ids5LYgrPJZmU2dQ==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -8955,7 +8955,7 @@ packages: commander: 9.5.0 expect-playwright: 0.8.0 glob: 10.3.12 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) jest-circus: 29.7.0 jest-environment-node: 29.7.0 jest-junit: 16.0.0 @@ -9413,10 +9413,10 @@ packages: chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) lodash: 4.17.21 redent: 3.0.0 - vitest: 1.1.1(@types/node@18.19.31)(happy-dom@12.10.3) + vitest: 1.1.1(@types/node@20.11.17)(happy-dom@12.10.3) dev: true /@testing-library/react@14.1.2(react-dom@18.2.0)(react@18.2.0): @@ -11044,7 +11044,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 5.0.10(@types/node@18.19.31) + vite: 5.0.10(@types/node@20.11.17) transitivePeerDependencies: - supports-color dev: true @@ -14425,7 +14425,7 @@ packages: object-assign: 4.1.1 vary: 1.1.2 - /cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3): + /cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3): resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: @@ -14433,7 +14433,7 @@ packages: cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 cosmiconfig: 8.3.6(typescript@5.3.3) jiti: 1.21.0 typescript: 5.3.3 @@ -14559,7 +14559,7 @@ packages: dependencies: '@babel/runtime': 7.24.4 - /create-jest@29.7.0(@types/node@18.19.31): + /create-jest@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -14568,7 +14568,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.19.31) + jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -20576,7 +20576,7 @@ packages: - typescript - utf-8-validate - /graphql-config@5.0.3(@types/node@18.19.31)(graphql@16.8.1)(typescript@4.9.5): + /graphql-config@5.0.3(@types/node@20.11.17)(graphql@16.8.1)(typescript@4.9.5): resolution: {integrity: sha512-BNGZaoxIBkv9yy6Y7omvsaBUHOzfFcII3UN++tpH8MGOKFPFkCPZuwx09ggANMt8FgyWP1Od8SWPmrUEZca4NQ==} engines: {node: '>= 16.0.0'} peerDependencies: @@ -20590,7 +20590,7 @@ packages: '@graphql-tools/json-file-loader': 8.0.1(graphql@16.8.1) '@graphql-tools/load': 8.0.2(graphql@16.8.1) '@graphql-tools/merge': 9.0.3(graphql@16.8.1) - '@graphql-tools/url-loader': 8.0.2(@types/node@18.19.31)(graphql@16.8.1) + '@graphql-tools/url-loader': 8.0.2(@types/node@20.11.17)(graphql@16.8.1) '@graphql-tools/utils': 10.1.2(graphql@16.8.1) cosmiconfig: 8.3.6(typescript@4.9.5) graphql: 16.8.1 @@ -20606,7 +20606,7 @@ packages: - utf-8-validate dev: false - /graphql-config@5.0.3(@types/node@18.19.31)(graphql@16.8.1)(typescript@5.4.4): + /graphql-config@5.0.3(@types/node@20.11.17)(graphql@16.8.1)(typescript@5.4.4): resolution: {integrity: sha512-BNGZaoxIBkv9yy6Y7omvsaBUHOzfFcII3UN++tpH8MGOKFPFkCPZuwx09ggANMt8FgyWP1Od8SWPmrUEZca4NQ==} engines: {node: '>= 16.0.0'} peerDependencies: @@ -20620,7 +20620,7 @@ packages: '@graphql-tools/json-file-loader': 8.0.1(graphql@16.8.1) '@graphql-tools/load': 8.0.2(graphql@16.8.1) '@graphql-tools/merge': 9.0.3(graphql@16.8.1) - '@graphql-tools/url-loader': 8.0.2(@types/node@18.19.31)(graphql@16.8.1) + '@graphql-tools/url-loader': 8.0.2(@types/node@20.11.17)(graphql@16.8.1) '@graphql-tools/utils': 10.1.2(graphql@16.8.1) cosmiconfig: 8.3.6(typescript@5.4.4) graphql: 16.8.1 @@ -22480,7 +22480,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@18.19.31): + /jest-cli@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -22494,10 +22494,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.19.31) + create-jest: 29.7.0(@types/node@20.11.17) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@18.19.31) + jest-config: 29.7.0(@types/node@20.11.17) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -22548,6 +22548,46 @@ packages: - supports-color dev: true + /jest-config@29.7.0(@types/node@20.11.17): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.24.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.17 + babel-jest: 29.7.0(@babel/core@7.24.4) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -22686,7 +22726,7 @@ packages: jest-runner: ^29.3.1 dependencies: expect-playwright: 0.8.0 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) jest-circus: 29.7.0 jest-environment-node: 29.7.0 jest-process-manager: 0.4.0 @@ -22897,7 +22937,7 @@ packages: dependencies: ansi-escapes: 6.2.1 chalk: 5.3.0 - jest: 29.7.0(@types/node@18.19.31) + jest: 29.7.0(@types/node@20.11.17) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -22945,7 +22985,7 @@ packages: supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@18.19.31): + /jest@29.7.0(@types/node@20.11.17): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -22958,7 +22998,7 @@ packages: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@18.19.31) + jest-cli: 29.7.0(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -24285,7 +24325,7 @@ packages: dependencies: '@types/node': 18.0.0 - /meros@1.3.0(@types/node@18.19.31): + /meros@1.3.0(@types/node@20.11.17): resolution: {integrity: sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w==} engines: {node: '>=13'} peerDependencies: @@ -24294,7 +24334,7 @@ packages: '@types/node': optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 dev: false /methods@1.1.2: @@ -25009,7 +25049,7 @@ packages: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} dev: false - /netlify-cli@17.21.1(@types/node@18.19.31): + /netlify-cli@17.21.1(@types/node@20.11.17): resolution: {integrity: sha512-B8QveV55h2dFCTnk5LInVW1MiXPINTQ61IkEtih15CVYpvVSQy+he8M6hdpucq83VqaF/phaJkb3Si2ligOxxw==} engines: {node: '>=18.14.0'} hasBin: true @@ -25018,7 +25058,7 @@ packages: '@bugsnag/js': 7.20.2 '@fastify/static': 6.10.2 '@netlify/blobs': 7.0.1 - '@netlify/build': 29.36.6(@opentelemetry/api@1.8.0)(@types/node@18.19.31) + '@netlify/build': 29.36.6(@opentelemetry/api@1.8.0)(@types/node@20.11.17) '@netlify/build-info': 7.13.2 '@netlify/config': 20.12.1 '@netlify/edge-bundler': 11.3.0 @@ -27374,19 +27414,11 @@ packages: hasBin: true dev: true - /prettier@3.1.1: - resolution: {integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==} - engines: {node: '>=14'} - hasBin: true - dev: true - /prettier@3.2.5: resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} engines: {node: '>=14'} hasBin: true requiresBuild: true - dev: false - optional: true /pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} @@ -31507,7 +31539,7 @@ packages: resolution: {integrity: sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA==} dev: true - /ts-node@10.9.2(@types/node@18.19.31)(typescript@5.4.4): + /ts-node@10.9.2(@types/node@20.11.17)(typescript@5.4.4): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -31526,7 +31558,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 18.19.31 + '@types/node': 20.11.17 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -32619,7 +32651,7 @@ packages: - terser dev: true - /vite-node@1.1.1(@types/node@18.19.31): + /vite-node@1.1.1(@types/node@20.11.17): resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -32628,7 +32660,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.8(@types/node@18.19.31) + vite: 5.2.8(@types/node@20.11.17) transitivePeerDependencies: - '@types/node' - less @@ -32691,7 +32723,7 @@ packages: fsevents: 2.3.3 dev: true - /vite@5.0.10(@types/node@18.19.31): + /vite@5.0.10(@types/node@20.11.17): resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -32719,7 +32751,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 esbuild: 0.19.12 postcss: 8.4.32 rollup: 4.14.1 @@ -32797,6 +32829,7 @@ packages: rollup: 4.14.1 optionalDependencies: fsevents: 2.3.3 + optional: true /vite@5.2.8(@types/node@20.11.17): resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} @@ -33026,7 +33059,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31): + /vitest@1.1.1(@types/node@20.11.17): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -33051,7 +33084,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 '@vitest/expect': 1.1.1 '@vitest/runner': 1.1.1 '@vitest/snapshot': 1.1.1 @@ -33070,8 +33103,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.8(@types/node@18.19.31) - vite-node: 1.1.1(@types/node@18.19.31) + vite: 5.2.8(@types/node@20.11.17) + vite-node: 1.1.1(@types/node@20.11.17) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -33083,7 +33116,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@18.19.31)(happy-dom@12.10.3): + /vitest@1.1.1(@types/node@20.11.17)(happy-dom@12.10.3): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -33108,7 +33141,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 18.19.31 + '@types/node': 20.11.17 '@vitest/expect': 1.1.1 '@vitest/runner': 1.1.1 '@vitest/snapshot': 1.1.1 @@ -33128,8 +33161,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.8(@types/node@18.19.31) - vite-node: 1.1.1(@types/node@18.19.31) + vite: 5.2.8(@types/node@20.11.17) + vite-node: 1.1.1(@types/node@20.11.17) why-is-node-running: 2.2.2 transitivePeerDependencies: - less From 7f703ce123c7812d98bb94467a42457aa66b5175 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 8 May 2024 15:51:48 +0400 Subject: [PATCH 117/221] chore: fix formatting --- packages/schema/src/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index d3da5ed9d..e2034acb5 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -199,7 +199,7 @@ type Hero { } union PageContent @resolveEditorBlockType = - BlockMarkup + | BlockMarkup | BlockMedia | BlockForm | BlockImageTeasers From d4fb4a1b67403fc086f7de379c552a8cf51e42b3 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 8 May 2024 17:56:25 +0400 Subject: [PATCH 118/221] chore: update default content to let tests pass --- .../content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index dd286b895..ae90a656e 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -7,8 +7,8 @@ _meta: depends: 3a0fe860-a6d6-428a-9474-365bd57509aa: media 478c4289-961d-4ce8-85d6-578ae05f3019: media - 72187a1f-3e48-4b45-a9b7-189c6fd7ee26: media 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a: media + 72187a1f-3e48-4b45-a9b7-189c6fd7ee26: media default: revision_uid: - @@ -104,7 +104,7 @@ default: - + @@ -115,7 +115,6 @@ default: - From 422ac83dd82c6eb363198a78aa7c6f8b7533056c Mon Sep 17 00:00:00 2001 From: Mattia Simonato Date: Wed, 8 May 2024 16:27:23 +0200 Subject: [PATCH 119/221] fix(SLB-358): webform-address class now has consistent vertical spacing --- packages/ui/src/iframe.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 36a13ad02..6c9528261 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -40,6 +40,10 @@ h1 { @apply mb-5; } +.webform-address { + @apply flex flex-col gap-y-4 md:gap-y-6; +} + /* Label */ .form-item label { @apply block mb-2 text-sm; From c9b834595c35934edeeb9ebb07ebcd9951f9c2a1 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:24:05 +0200 Subject: [PATCH 120/221] chore: fix gitpod configuration --- .gitpod.Dockerfile | 5 ----- .gitpod.yml | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index 913f8189c..7061f94f0 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -28,8 +28,3 @@ RUN chmod a+x phpactor.phar RUN sudo mv phpactor.phar /usr/local/bin/phpactor # Install gh cli RUN sudo install-packages gh - -RUN curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_0.40.2_Linux_x86_64.tar.gz" \ - && tar xf lazygit.tar.gz lazygit \ - && sudo install lazygit /usr/local/bin - diff --git a/.gitpod.yml b/.gitpod.yml index b8f8aab49..ccd012215 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,6 @@ tasks: - name: Setup - init: pnpm install && pnpm turbo build --no-daemon --go-fallback && gp sync-done setup + init: pnpm install && pnpm turbo build gp sync-done setup env: NETLIFY_URL: http://localhost:8000 DRUPAL_EXTERNAL_URL: http://localhost:8888 From 3754c24e156240f61cf5910752cca344f21f179e Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:24:31 +0200 Subject: [PATCH 121/221] style: typography refactoring --- .../Organisms/PageContent/BlockMarkup.tsx | 8 +-- packages/ui/src/tailwind.config.cjs | 64 ++++++++++++++++++- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index a83f81b79..f0f3c82a5 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -17,11 +17,7 @@ export function BlockMarkup(props: BlockMarkupFragment) {
    {unordered ? ( - + ) : null} {children} diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index bbdaea5bf..c2bf31165 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -1,9 +1,69 @@ /** @type {import('tailwindcss').Config} */ -const theme = require('./stylingAssets.json'); +const stylingAssets = require('./stylingAssets.json'); module.exports = { content: ['./src/**/*.{tsx, mdx}'], - ...theme, + theme: { + "extend": { + "typography": ({theme}) => ({ + "DEFAULT": { + css: [ + { + 'a, p a': { + }, + 'ul, ol': { + fontSize: '1.125rem', + lineHeight: '1.688rem' + }, + ol: { + }, + 'ul>li::marker, ol>li::marker': { + }, + strong: { + color: theme('colors.gray.900'), + fontWeight: '700', + }, + '.prose p': { + color: theme('colors.gray.500'), + fontSize: '1.125rem', + lineHeight: '1.688rem' + }, + '.prose a, .prose p a': { + color: theme('colors.blue.600'), + }, + '.prose em': { + color: theme('colors.gray.900'), + }, + 'prose marker': { + fontWeight: '700', + }, + 'blockquote': { + }, + '.prose blockquote p': { + fontWeight: '700', + color: '#111928' + }, + cite: { + }, + 'h1, h2, h3, h4, h5, h6': { + }, + '.prose h1': { + }, + '.prose h2': { + fontWeight: '700', + color: theme('colors.gray.900'), + }, + '.prose h3': { + }, + '.prose h4': { + } + }, + ] + } + }), + }, + ...stylingAssets.theme, + }, plugins: [ require('@tailwindcss/forms'), require('@tailwindcss/aspect-ratio'), From 63c956bdff0f4e17d6644bde65e2e9a64e094a69 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:33:54 +0200 Subject: [PATCH 122/221] chore: prettier --- packages/ui/src/tailwind.config.cjs | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index c2bf31165..031674664 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -4,21 +4,18 @@ const stylingAssets = require('./stylingAssets.json'); module.exports = { content: ['./src/**/*.{tsx, mdx}'], theme: { - "extend": { - "typography": ({theme}) => ({ - "DEFAULT": { + extend: { + typography: ({ theme }) => ({ + DEFAULT: { css: [ { - 'a, p a': { - }, + 'a, p a': {}, 'ul, ol': { fontSize: '1.125rem', - lineHeight: '1.688rem' - }, - ol: { - }, - 'ul>li::marker, ol>li::marker': { + lineHeight: '1.688rem', }, + ol: {}, + 'ul>li::marker, ol>li::marker': {}, strong: { color: theme('colors.gray.900'), fontWeight: '700', @@ -26,7 +23,7 @@ module.exports = { '.prose p': { color: theme('colors.gray.500'), fontSize: '1.125rem', - lineHeight: '1.688rem' + lineHeight: '1.688rem', }, '.prose a, .prose p a': { color: theme('colors.blue.600'), @@ -37,29 +34,23 @@ module.exports = { 'prose marker': { fontWeight: '700', }, - 'blockquote': { - }, + blockquote: {}, '.prose blockquote p': { fontWeight: '700', - color: '#111928' - }, - cite: { - }, - 'h1, h2, h3, h4, h5, h6': { - }, - '.prose h1': { + color: '#111928', }, + cite: {}, + 'h1, h2, h3, h4, h5, h6': {}, + '.prose h1': {}, '.prose h2': { fontWeight: '700', color: theme('colors.gray.900'), }, - '.prose h3': { - }, - '.prose h4': { - } + '.prose h3': {}, + '.prose h4': {}, }, - ] - } + ], + }, }), }, ...stylingAssets.theme, From a785d2147ee12e8624ba7c8af296a7064c93d06a Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 07:49:07 +0200 Subject: [PATCH 123/221] style: typography weight on p a --- packages/ui/src/tailwind.config.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index 031674664..c6f9477ec 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -27,6 +27,7 @@ module.exports = { }, '.prose a, .prose p a': { color: theme('colors.blue.600'), + fontWeight: '400', }, '.prose em': { color: theme('colors.gray.900'), From f03492459c90b28b004f258652573827e0ecb4dd Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 20:25:08 +0200 Subject: [PATCH 124/221] style: adjustments --- .../ui/src/components/Organisms/PageContent/BlockMarkup.tsx | 3 ++- packages/ui/src/tailwind.config.cjs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index f0f3c82a5..1b4f20d0c 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -17,7 +17,8 @@ export function BlockMarkup(props: BlockMarkupFragment) {
    li::marker, ol>li::marker': {}, strong: { color: theme('colors.gray.900'), From d514c2b5e24ce0c22856aca2a72de603c221321f Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 3 May 2024 20:54:07 +0200 Subject: [PATCH 125/221] chore: prettier --- packages/ui/src/tailwind.config.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs index 2bdeee963..e66fff3a7 100644 --- a/packages/ui/src/tailwind.config.cjs +++ b/packages/ui/src/tailwind.config.cjs @@ -13,7 +13,7 @@ module.exports = { 'ul, ol': { fontSize: '1.125rem', lineHeight: '1.688rem', - paddingLeft: '2.5rem' + paddingLeft: '2.5rem', }, 'ul>li::marker, ol>li::marker': {}, strong: { From bd518ed77e0d1eb510fa10d3930abaff3980c5bc Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 9 May 2024 12:44:21 +0400 Subject: [PATCH 126/221] chore: simplify prettier commands .gitignore and .prettierignore are taken into account by default. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9d06dcc45..29e537f47 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "tb": "pnpm turbo --filter @custom/website", "test:format": "pnpm test:format:root --check && pnpm test:format:workspaces --check", "test:format:fix": "pnpm test:format:root --write && pnpm test:format:workspaces --write", - "test:format:root": "pnpm prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}' --ignore-path='./.prettierignore'", - "test:format:workspaces": "pnpm --workspace-concurrency=1 -r exec prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}' --ignore-path='./.gitignore'", + "test:format:root": "pnpm prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}'", + "test:format:workspaces": "pnpm --workspace-concurrency=1 -r exec prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}'", "turbo:local": "if [ -z $CI ]; then echo $(date)$RANDOM > apps/cms/turbo-seed.txt; fi", "turbo:test": "pnpm turbo:local && pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only && pnpm tb test:integration --no-daemon --go-fallback --output-logs=new-only --concurrency=1", "turbo:test:force": "pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only --force && pnpm tb test:integration --no-daemon --go-fallback --output-logs=new-only --concurrency=1 --force", From 7c696239d25eb155cd4ec720075322e7afe22ac7 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 9 May 2024 13:00:28 +0400 Subject: [PATCH 127/221] chore: simplify prettier config even more prettify all the things --- .idea/prettier.xml | 2 +- .prettierignore | 1 + apps/cms/.prettierignore | 2 ++ package.json | 4 ++-- packages/drupal/test_content/.prettierignore | 2 ++ 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 apps/cms/.prettierignore create mode 100644 packages/drupal/test_content/.prettierignore diff --git a/.idea/prettier.xml b/.idea/prettier.xml index 6e16fd106..50edf175d 100644 --- a/.idea/prettier.xml +++ b/.idea/prettier.xml @@ -3,6 +3,6 @@ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 6ad8630f6..95e9082a1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ apps packages tests .turbo +pnpm-lock.yaml diff --git a/apps/cms/.prettierignore b/apps/cms/.prettierignore new file mode 100644 index 000000000..d851b213c --- /dev/null +++ b/apps/cms/.prettierignore @@ -0,0 +1,2 @@ +config/sync +composer.lock diff --git a/package.json b/package.json index 29e537f47..c86941856 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "tb": "pnpm turbo --filter @custom/website", "test:format": "pnpm test:format:root --check && pnpm test:format:workspaces --check", "test:format:fix": "pnpm test:format:root --write && pnpm test:format:workspaces --write", - "test:format:root": "pnpm prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}'", - "test:format:workspaces": "pnpm --workspace-concurrency=1 -r exec prettier '**/*.{js,cjs,mjs,ts,jsx,tsx,gql,graphql,graphqls,md,mdx,json,htm,html}'", + "test:format:root": "pnpm prettier --ignore-unknown '**/**'", + "test:format:workspaces": "pnpm --workspace-concurrency=1 -r exec prettier --ignore-unknown '**/**'", "turbo:local": "if [ -z $CI ]; then echo $(date)$RANDOM > apps/cms/turbo-seed.txt; fi", "turbo:test": "pnpm turbo:local && pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only && pnpm tb test:integration --no-daemon --go-fallback --output-logs=new-only --concurrency=1", "turbo:test:force": "pnpm tb test:unit --no-daemon --go-fallback --output-logs=new-only --force && pnpm tb test:integration --no-daemon --go-fallback --output-logs=new-only --concurrency=1 --force", diff --git a/packages/drupal/test_content/.prettierignore b/packages/drupal/test_content/.prettierignore new file mode 100644 index 000000000..dd0f08c94 --- /dev/null +++ b/packages/drupal/test_content/.prettierignore @@ -0,0 +1,2 @@ +content +webforms From abd5a578259386c70ea7950830d9d38f74fde920 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 9 May 2024 13:00:49 +0400 Subject: [PATCH 128/221] chore: prettier --- .github/workflows/deploy.yml | 19 ++++-- .github/workflows/high_content_volume.yml | 8 ++- .github/workflows/merge_dev_to_stage.yml | 2 +- .github/workflows/test.yml | 12 +++- .../workflows/test_without_turbo_cache.yml | 4 +- apps/cms/scaffold/all.services.yml | 8 +-- .../netlify/functions/github-proxy.mts | 7 ++- docker-compose.yml | 11 ++-- packages/drupal/gutenberg_blocks/css/edit.css | 6 +- .../gutenberg_blocks.gutenberg.yml | 2 +- .../gutenberg_blocks.info.yml | 2 +- .../gutenberg_blocks.libraries.yml | 1 - .../ui/src/components/Atoms/Container.css | 5 +- packages/ui/src/iframe.css | 60 ++++++++++--------- pnpm-workspace.yaml | 8 +-- 15 files changed, 90 insertions(+), 65 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9e9897278..83587e7ac 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,11 +23,15 @@ jobs: - name: Extract branch name shell: bash - run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + run: + echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> + $GITHUB_OUTPUT id: extract_branch - name: Prepare deployment package - run: pnpm turbo:prep && pnpm deploy --filter "@custom/website" ../deploy --prod + run: + pnpm turbo:prep && pnpm deploy --filter "@custom/website" ../deploy + --prod env: VITE_DECAP_REPO: ${{ github.repository }} VITE_DECAP_BRANCH: ${{ steps.extract_branch.outputs.branch }} @@ -54,7 +58,10 @@ jobs: - name: Deploy to dev run: pnpm netlify deploy --prod --filter "@custom/website" working-directory: ../deploy - if: github.ref == 'refs/heads/dev' && steps.netlify-check.outputs.available == 'true' && vars.NETLIFY_DEV_ID != '' + if: + github.ref == 'refs/heads/dev' && + steps.netlify-check.outputs.available == 'true' && vars.NETLIFY_DEV_ID + != '' env: NETLIFY_SITE_ID: ${{ vars.NETLIFY_DEV_ID }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} @@ -62,8 +69,10 @@ jobs: - name: Deploy to prod run: pnpm netlify deploy --prod --filter "@custom/website" working-directory: ../deploy - if: github.ref == 'refs/heads/prod' && steps.netlify-check.outputs.available == 'true' && vars.NETLIFY_PROD_ID != '' + if: + github.ref == 'refs/heads/prod' && + steps.netlify-check.outputs.available == 'true' && + vars.NETLIFY_PROD_ID != '' env: NETLIFY_SITE_ID: ${{ vars.NETLIFY_PROD_ID }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - diff --git a/.github/workflows/high_content_volume.yml b/.github/workflows/high_content_volume.yml index 02edab017..88bbda211 100644 --- a/.github/workflows/high_content_volume.yml +++ b/.github/workflows/high_content_volume.yml @@ -35,7 +35,9 @@ jobs: run: pnpm --filter "@custom/website" clean - name: 'Drupal: Create content' - run: pnpm --filter "@custom/cms" drush php:script scripts/create-lots-of-content/run.php + run: + pnpm --filter "@custom/cms" drush php:script + scripts/create-lots-of-content/run.php - name: 'Gatsby: Full build' run: pnpm --filter "@custom/website" build:gatsby @@ -44,7 +46,9 @@ jobs: run: pnpm --filter "@custom/website" build:gatsby - name: 'Drupal: Create more content' - run: pnpm --filter "@custom/cms" drush php:script scripts/create-lots-of-content/create-100-pages.php + run: + pnpm --filter "@custom/cms" drush php:script + scripts/create-lots-of-content/create-100-pages.php - name: 'Gatsby: Incremental build with new content' run: pnpm --filter "@custom/website" build:gatsby diff --git a/.github/workflows/merge_dev_to_stage.yml b/.github/workflows/merge_dev_to_stage.yml index 49741615b..55ca2f714 100644 --- a/.github/workflows/merge_dev_to_stage.yml +++ b/.github/workflows/merge_dev_to_stage.yml @@ -18,4 +18,4 @@ jobs: with: type: now target_branch: stage - github_token: ${{ github.token }} \ No newline at end of file + github_token: ${{ github.token }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9e6ac435..2d403a404 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,9 @@ jobs: steps: - name: Init check if: ${{ github.repository != 'AmazeeLabs/silverback-template'}} - run: echo 'Please run the INIT script. See the root README.md for instructions.' && false + run: + echo 'Please run the INIT script. See the root README.md for + instructions.' && false - name: Checkout uses: actions/checkout@v3 @@ -77,7 +79,9 @@ jobs: - name: Merge release to prod (silverback-template only) uses: devmasx/merge-branch@1.4.0 - if: ${{ github.repository == 'AmazeeLabs/silverback-template' && github.ref == 'refs/heads/release'}} + if: + ${{ github.repository == 'AmazeeLabs/silverback-template' && + github.ref == 'refs/heads/release'}} with: type: now from_branch: release @@ -86,7 +90,9 @@ jobs: docker_build: name: Docker Build - if: startsWith(github.ref_name, 'test-all/') || startsWith(github.head_ref, 'test-all/') + if: + startsWith(github.ref_name, 'test-all/') || startsWith(github.head_ref, + 'test-all/') runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/test_without_turbo_cache.yml b/.github/workflows/test_without_turbo_cache.yml index c247a595e..757b757c0 100644 --- a/.github/workflows/test_without_turbo_cache.yml +++ b/.github/workflows/test_without_turbo_cache.yml @@ -9,7 +9,9 @@ jobs: steps: - name: Init check if: ${{ github.repository != 'AmazeeLabs/silverback-template'}} - run: echo 'Please run the INIT script. See the root README.md for instructions.' && false + run: + echo 'Please run the INIT script. See the root README.md for + instructions.' && false - name: Checkout uses: actions/checkout@v3 diff --git a/apps/cms/scaffold/all.services.yml b/apps/cms/scaffold/all.services.yml index d1e0a45c9..6eb08a2b2 100644 --- a/apps/cms/scaffold/all.services.yml +++ b/apps/cms/scaffold/all.services.yml @@ -3,10 +3,10 @@ # To activate this feature, follow the instructions at the top of the # 'example.settings.local.php' file, which sits next to this file. parameters: - # Configure Cross-Site HTTP requests (CORS). - # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS - # for more information about the topic in general. - # Note: By default the configuration is disabled. + # Configure Cross-Site HTTP requests (CORS). + # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + # for more information about the topic in general. + # Note: By default the configuration is disabled. cors.config: enabled: true # Specify allowed headers, like 'x-allowed-header'. diff --git a/apps/website/netlify/functions/github-proxy.mts b/apps/website/netlify/functions/github-proxy.mts index f7a7e2008..d47302636 100644 --- a/apps/website/netlify/functions/github-proxy.mts +++ b/apps/website/netlify/functions/github-proxy.mts @@ -5,6 +5,9 @@ export default function (request: Request, context: Context) { if (!process.env.DECAP_GITHUB_TOKEN) { throw new Error('Missing environment variable DECAP_GITHUB_TOKEN.'); } - return githubProxy(request, process.env.DECAP_GITHUB_TOKEN, '/.netlify/functions/github-proxy'); + return githubProxy( + request, + process.env.DECAP_GITHUB_TOKEN, + '/.netlify/functions/github-proxy', + ); } - diff --git a/docker-compose.yml b/docker-compose.yml index c5ad2e374..663f65444 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,12 @@ version: '2.3' x-volumes: - &default-volumes - # Define all volumes you would like to have real-time mounted into the docker containers + &default-volumes # Define all volumes you would like to have real-time mounted into the docker containers volumes: - ./packages/drupal:/app/web/modules/custom:delegated - ./apps/cms:/app:delegated -x-environment: - &default-environment +x-environment: &default-environment LAGOON_PROJECT: &lagoon-project ${COMPOSE_PROJECT_NAME:-slbtemplate} # Route that should be used locally, if you are using pygmy, this route *must* end with .docker.amazee.io LAGOON_ROUTE: &default-url http://${COMPOSE_PROJECT_NAME:-slbtemplate}.docker.amazee.io @@ -17,8 +15,7 @@ x-environment: # See example.docker-compose.override.yml for XDEBUG_ENABLE option x-user: - &default-user - # The default user under which the containers should run. Change this if you are on linux and run with another user than id `1000` + &default-user # The default user under which the containers should run. Change this if you are on linux and run with another user than id `1000` user: '1000' services: @@ -58,7 +55,7 @@ services: depends_on: - cli # basically just tells docker-compose to build the cli first environment: - << : *default-environment # loads the defined environment variables from the top + <<: *default-environment # loads the defined environment variables from the top networks: - amazeeio-network - default diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 39ab66473..8e8d0594e 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -1,10 +1,10 @@ /* We can put any editor specific styling in here */ .gutenberg__editor .editor-styles-wrapper { - font-family:inherit; + font-family: inherit; } .gutenberg__editor blockquote { - margin:0; + margin: 0; } .gutenberg__editor blockquote .quote-image img { @@ -40,4 +40,4 @@ left: 0; transform-origin: 0 0; transform: rotate(90deg); -} \ No newline at end of file +} diff --git a/packages/drupal/gutenberg_blocks/gutenberg_blocks.gutenberg.yml b/packages/drupal/gutenberg_blocks/gutenberg_blocks.gutenberg.yml index 8ed456c02..42c51a1e5 100644 --- a/packages/drupal/gutenberg_blocks/gutenberg_blocks.gutenberg.yml +++ b/packages/drupal/gutenberg_blocks/gutenberg_blocks.gutenberg.yml @@ -3,4 +3,4 @@ theme-support: typography: dropCap: false libraries-edit: - - gutenberg_blocks/edit \ No newline at end of file + - gutenberg_blocks/edit diff --git a/packages/drupal/gutenberg_blocks/gutenberg_blocks.info.yml b/packages/drupal/gutenberg_blocks/gutenberg_blocks.info.yml index 039c68d3e..193f1a006 100644 --- a/packages/drupal/gutenberg_blocks/gutenberg_blocks.info.yml +++ b/packages/drupal/gutenberg_blocks/gutenberg_blocks.info.yml @@ -5,4 +5,4 @@ package: Custom core_version_requirement: ^9 || ^10 dependencies: - gutenberg:gutenberg - - silverback_gutenberg:silverback_gutenberg \ No newline at end of file + - silverback_gutenberg:silverback_gutenberg diff --git a/packages/drupal/gutenberg_blocks/gutenberg_blocks.libraries.yml b/packages/drupal/gutenberg_blocks/gutenberg_blocks.libraries.yml index 89370de15..87a7ad6d9 100644 --- a/packages/drupal/gutenberg_blocks/gutenberg_blocks.libraries.yml +++ b/packages/drupal/gutenberg_blocks/gutenberg_blocks.libraries.yml @@ -13,4 +13,3 @@ customisations: /gutenberg.css: { preprocess: false } dependencies: - core/drupalSettings - diff --git a/packages/ui/src/components/Atoms/Container.css b/packages/ui/src/components/Atoms/Container.css index 8985bc6dc..5bd018f50 100644 --- a/packages/ui/src/components/Atoms/Container.css +++ b/packages/ui/src/components/Atoms/Container.css @@ -27,7 +27,7 @@ } .nested-container .container-page:first-child .container-text, -.nested-container .container-page:first-child .container-text *:first-child{ +.nested-container .container-page:first-child .container-text *:first-child { @apply mt-0; } .nested-container .container-page:last-child .container-text, @@ -35,6 +35,7 @@ @apply mb-0; } -.container-nested .prose ul:not(ul ul), .container-nested .prose ol:not(ol ol) { +.container-nested .prose ul:not(ul ul), +.container-nested .prose ol:not(ol ol) { @apply mt-0; } diff --git a/packages/ui/src/iframe.css b/packages/ui/src/iframe.css index 6c9528261..302b70921 100644 --- a/packages/ui/src/iframe.css +++ b/packages/ui/src/iframe.css @@ -12,7 +12,8 @@ h1 { @apply sr-only; } -.visually-hidden.focusable:active, .visually-hidden.focusable:focus { +.visually-hidden.focusable:active, +.visually-hidden.focusable:focus { @apply not-sr-only ml-1; } @@ -35,7 +36,6 @@ h1 { @apply mb-4 md:mb-6; } - .fieldset-wrapper > *:not(:last-child) { @apply mb-5; } @@ -49,11 +49,13 @@ h1 { @apply block mb-2 text-sm; } -.form-item input[type="checkbox"] + label, .form-item input[type="radio"] + label { +.form-item input[type='checkbox'] + label, +.form-item input[type='radio'] + label { @apply inline; } -.form-item input[type="checkbox"], .form-item input[type="radio"] { +.form-item input[type='checkbox'], +.form-item input[type='radio'] { @apply mr-1; } @@ -66,52 +68,54 @@ h1 { @apply mt-2; } -.form-item input[type="checkbox"] + label + .description, -.form-item input[type="radio"] + label + .description { +.form-item input[type='checkbox'] + label + .description, +.form-item input[type='radio'] + label + .description { @apply mt-0; } - /* field */ -.form-item input[type="email"], -.form-item input[type="password"], -.form-item input[type="date"], -.form-item input[type="text"], -.form-item input[type="number"], -.form-item input[type="datetime-local"], -.form-item input[type="time"], -.form-item input[type="url"], -.form-item input[type="tel"], -.form-item input[type="search"], -.form-item input[type="color"], +.form-item input[type='email'], +.form-item input[type='password'], +.form-item input[type='date'], +.form-item input[type='text'], +.form-item input[type='number'], +.form-item input[type='datetime-local'], +.form-item input[type='time'], +.form-item input[type='url'], +.form-item input[type='tel'], +.form-item input[type='search'], +.form-item input[type='color'], .form-item textarea, .form-item select { @apply bg-gray-50 border border-gray-300 text-base rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5; } /* checkbox */ -.form-item input[type="checkbox"] { +.form-item input[type='checkbox'] { @apply rounded -mt-1; } -.form-item input[type="checkbox"]:not(:checked), .form-item input[type="radio"]:not(:checked) { +.form-item input[type='checkbox']:not(:checked), +.form-item input[type='radio']:not(:checked) { @apply bg-gray-50 border-gray-300; } -.form-item input[type="file"] { +.form-item input[type='file'] { @apply block cursor-pointer bg-gray-50 border border-gray-300 rounded-lg block text-sm w-full; } -.form-item input[type="file"]::file-selector-button { +.form-item input[type='file']::file-selector-button { @apply text-white bg-blue-700 hover:bg-blue-800 rounded-l-lg text-sm w-full sm:w-auto px-6 py-2.5 text-center shadow-none border-0 font-medium mr-4; } - -.form-actions input[type="submit"] { +.form-actions input[type='submit'] { @apply text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center; } -.form-item input.error, .form-item textarea.error, .form-item select.error, .form-item input[type="checkbox"].error { +.form-item input.error, +.form-item textarea.error, +.form-item select.error, +.form-item input[type='checkbox'].error { @apply border-red-500; } @@ -119,12 +123,11 @@ h1 { @apply content-['*'] text-gray-900 pl-1; } - .fieldset-legend { @apply font-bold mb-2 block; } -[data-drupal-messages] [role="alert"] { +[data-drupal-messages] [role='alert'] { @apply text-red-500; } @@ -162,7 +165,8 @@ h1 { @apply right-5; } -.ui-dialog .ui-button .ui-icon-closethick, .ui-dialog .ui-button:hover .ui-icon-closethick { +.ui-dialog .ui-button .ui-icon-closethick, +.ui-dialog .ui-button:hover .ui-icon-closethick { background-image: url("data:image/svg+xml;utf8,"); @apply bg-no-repeat bg-center h-10 w-10 -left-0.5 -top-0.5 rounded-lg; } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 75af705aa..1069ced0c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - - "apps/*" - - "packages/*" - - "packages/drupal/*" - - "tests/*" + - 'apps/*' + - 'packages/*' + - 'packages/drupal/*' + - 'tests/*' From fef0484bd732abf9fb89692f8e8f6f8e1d9d62b9 Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 9 May 2024 13:02:42 +0400 Subject: [PATCH 129/221] chore: make format check more obvious --- .github/actions/setup/action.yml | 4 ---- .github/workflows/test.yml | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 06cfe2bac..bd2280348 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -35,10 +35,6 @@ runs: shell: bash run: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 pnpm i - - name: Check formatting - shell: bash - run: pnpm test:format - - name: Cache playwright binaries uses: actions/cache@v3 id: playwright-cache diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d403a404..9c1f520d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,9 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Check formatting + run: pnpm test:format + - name: TurboRepo local server uses: felixmosh/turborepo-gh-artifacts@v2 with: From 87acfcb56a0b62544c7aa5957e1c368f6008f498 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 2 May 2024 16:20:00 +0200 Subject: [PATCH 130/221] refactor: refactor parent menu item to be clickable --- packages/ui/src/components/Organisms/Header.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/ui/src/components/Organisms/Header.tsx b/packages/ui/src/components/Organisms/Header.tsx index dacd4f058..d8d05907b 100644 --- a/packages/ui/src/components/Organisms/Header.tsx +++ b/packages/ui/src/components/Organisms/Header.tsx @@ -76,6 +76,13 @@ export function Header() { ) : ( + + {item.title} + {item.children.map((child) => child.children.length === 0 ? ( + + {item.title} + {item.children.map((child) => child.children.length === 0 ? ( Date: Tue, 14 May 2024 00:01:54 +0300 Subject: [PATCH 131/221] chore(SLB-288): move container containing to each block --- .../ui/src/components/Atoms/Container.css | 2 +- .../Organisms/PageContent/BlockCta.tsx | 26 ++++++++++--------- .../Organisms/PageContent/BlockQuote.tsx | 2 +- .../src/components/Organisms/PageDisplay.tsx | 4 +-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/components/Atoms/Container.css b/packages/ui/src/components/Atoms/Container.css index 5bd018f50..b30f83468 100644 --- a/packages/ui/src/components/Atoms/Container.css +++ b/packages/ui/src/components/Atoms/Container.css @@ -3,7 +3,7 @@ } .container-content { - @apply mx-auto max-w-7xl; + @apply mx-auto max-w-3xl; } .container-text { diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx index cdf574951..9a43189f9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx @@ -10,18 +10,20 @@ import React from 'react'; export function BlockCta(props: BlockCtaFragment) { return ( - - {props.text} - {!!props.icon && props.icon === CtaIconType.Arrow && } - +
    + + {props.text} + {!!props.icon && props.icon === CtaIconType.Arrow && } + +
    ); } diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index c99955e06..2d6c870bc 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -3,7 +3,7 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return ( -
    +
    {page.hero ? : null} -
    -
    +
    +
    {page?.content?.filter(isTruthy).map((block, index) => { switch (block.__typename) { case 'BlockMedia': From eda31f1852e70ea3acd40e4139959d67d64b3c51 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Tue, 14 May 2024 00:02:00 +0300 Subject: [PATCH 132/221] fix(SLB-288): center list icons --- packages/ui/src/components/Atoms/List.css | 15 +++++++++------ .../ui/src/components/Routes/Page.stories.tsx | 5 +++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/Atoms/List.css b/packages/ui/src/components/Atoms/List.css index 0d9ea8fa8..636916653 100644 --- a/packages/ui/src/components/Atoms/List.css +++ b/packages/ui/src/components/Atoms/List.css @@ -13,14 +13,16 @@ ul { position: relative; padding-left: 0.625rem; margin-left: 0; + line-height: 1.25rem !important; } .list-style--arrows li::before { position: absolute; left: -1rem; - vertical-align: middle; - line-height: 1.5rem; + line-height: 1rem; + height: 1rem; margin-top: 2px; + vertical-align: middle; content: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='arrow-narrow-right'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M9.83401 4.23435C9.98403 4.08437 10.1875 4.00012 10.3996 4.00012C10.6117 4.00012 10.8152 4.08437 10.9652 4.23435L14.1652 7.43435C14.3152 7.58437 14.3994 7.78782 14.3994 7.99995C14.3994 8.21208 14.3152 8.41553 14.1652 8.56555L10.9652 11.7656C10.8143 11.9113 10.6122 11.9919 10.4025 11.9901C10.1927 11.9883 9.99208 11.9041 9.84375 11.7558C9.69543 11.6075 9.61129 11.4068 9.60947 11.1971C9.60765 10.9873 9.68828 10.7852 9.83401 10.6344L11.6684 8.79995H2.39961C2.18744 8.79995 1.98395 8.71567 1.83392 8.56564C1.68389 8.41561 1.59961 8.21212 1.59961 7.99995C1.59961 7.78778 1.68389 7.5843 1.83392 7.43427C1.98395 7.28424 2.18744 7.19995 2.39961 7.19995H11.6684L9.83401 5.36555C9.68403 5.21553 9.59978 5.01208 9.59978 4.79995C9.59978 4.58782 9.68403 4.38437 9.83401 4.23435Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); list-style-position: inside; } @@ -28,9 +30,10 @@ ul { .list-style--checkmarks li::before { position: absolute; left: -1.125rem; + line-height: 1.25rem; + height: 1.25rem; vertical-align: middle; - line-height: 1.5rem; - margin-top: 2px; + margin-top: 0.5px; content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); } @@ -38,7 +41,7 @@ ul { position: absolute; left: -1.125rem; vertical-align: middle; - line-height: 1.5rem; - margin-top: 1px; + line-height: 1.25rem; + height: 1.25rem; content: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='question-mark-circle'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M18 10C18 12.1217 17.1571 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18C7.87827 18 5.84344 17.1571 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C12.1217 2 14.1566 2.84285 15.6569 4.34315C17.1571 5.84344 18 7.87827 18 10ZM10 7C9.8243 6.99983 9.65165 7.04595 9.49945 7.13373C9.34724 7.22151 9.22085 7.34784 9.133 7.5C9.06957 7.61788 8.98311 7.72182 8.87876 7.80566C8.77441 7.8895 8.65429 7.95154 8.52552 7.9881C8.39675 8.02466 8.26194 8.03499 8.12911 8.01849C7.99627 8.00198 7.86809 7.95897 7.75218 7.89201C7.63628 7.82505 7.53499 7.7355 7.45433 7.62867C7.37367 7.52184 7.31529 7.3999 7.28263 7.27008C7.24997 7.14027 7.24371 7.00522 7.26421 6.87294C7.28472 6.74065 7.33157 6.61384 7.402 6.5C7.73222 5.92811 8.24191 5.48116 8.85203 5.22846C9.46214 4.97576 10.1386 4.93144 10.7765 5.10236C11.4143 5.27328 11.978 5.64989 12.38 6.1738C12.782 6.6977 13 7.33962 13 8C13.0002 8.62062 12.8079 9.22603 12.4498 9.73285C12.0916 10.2397 11.5851 10.623 11 10.83V11C11 11.2652 10.8946 11.5196 10.7071 11.7071C10.5196 11.8946 10.2652 12 10 12C9.73478 12 9.48043 11.8946 9.29289 11.7071C9.10536 11.5196 9 11.2652 9 11V10C9 9.73478 9.10536 9.48043 9.29289 9.29289C9.48043 9.10536 9.73478 9 10 9C10.2652 9 10.5196 8.89464 10.7071 8.70711C10.8946 8.51957 11 8.26522 11 8C11 7.73478 10.8946 7.48043 10.7071 7.29289C10.5196 7.10536 10.2652 7 10 7ZM10 15C10.2652 15 10.5196 14.8946 10.7071 14.7071C10.8946 14.5196 11 14.2652 11 14C11 13.7348 10.8946 13.4804 10.7071 13.2929C10.5196 13.1054 10.2652 13 10 13C9.73478 13 9.48043 13.1054 9.29289 13.2929C9.10536 13.4804 9 13.7348 9 14C9 14.2652 9.10536 14.5196 9.29289 14.7071C9.48043 14.8946 9.73478 15 10 15Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); } diff --git a/packages/ui/src/components/Routes/Page.stories.tsx b/packages/ui/src/components/Routes/Page.stories.tsx index f3d048c25..c99d17520 100644 --- a/packages/ui/src/components/Routes/Page.stories.tsx +++ b/packages/ui/src/components/Routes/Page.stories.tsx @@ -10,6 +10,7 @@ import { Meta, StoryObj } from '@storybook/react'; import React from 'react'; import { image } from '../../helpers/image'; +import { ImageRight } from '../Organisms/PageContent/BlockImageWithText.stories'; import { Mixed, Paragraph } from '../Organisms/PageContent/BlockMarkup.stories'; import { WithCaption } from '../Organisms/PageContent/BlockMedia.stories'; import { Default as FrameStory } from './Frame.stories'; @@ -59,6 +60,10 @@ export const Default = { __typename: 'BlockMarkup', ...Paragraph.args, }, + { + __typename: 'BlockImageWithText', + ...ImageRight.args, + }, ] as Exclude['content'], }, }, From 40fd77a4f8f3d599eabe9911aaf93b0f9ab11db2 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Tue, 14 May 2024 09:12:42 +0300 Subject: [PATCH 133/221] chore(SLB-288): remove repeated padding --- packages/ui/src/components/Organisms/PageDisplay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 6ea44d0ca..c2d21e1ec 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -17,7 +17,7 @@ export function PageDisplay(page: PageFragment) {
    {page.hero ? : null} -
    +
    {page?.content?.filter(isTruthy).map((block, index) => { switch (block.__typename) { From d8c5934d4b370a7672f9ff33385045f32e1c9bec Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Tue, 14 May 2024 23:10:53 +0300 Subject: [PATCH 134/221] fix(SLB-288): update styles --- packages/drupal/gutenberg_blocks/css/edit.css | 26 +++++++++++-------- .../PageContent/BlockImageWithText.gql | 2 +- packages/ui/src/components/Atoms/List.css | 18 +++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index e951d483f..13520cab5 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -50,31 +50,35 @@ .gutenberg__editor .list-style--question-marks li { list-style-type: none; position: relative; + padding-left: 0.625rem; + margin-left: 0; + line-height: 1.25rem !important; } .gutenberg__editor .list-style--arrows li::before { position: absolute; left: -1.25rem; + line-height: 1.25rem; + height: 1.25rem; vertical-align: middle; - line-height: 1.5rem; - margin-top: 2px; - content: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='arrow-narrow-right'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M9.83401 4.23435C9.98403 4.08437 10.1875 4.00012 10.3996 4.00012C10.6117 4.00012 10.8152 4.08437 10.9652 4.23435L14.1652 7.43435C14.3152 7.58437 14.3994 7.78782 14.3994 7.99995C14.3994 8.21208 14.3152 8.41553 14.1652 8.56555L10.9652 11.7656C10.8143 11.9113 10.6122 11.9919 10.4025 11.9901C10.1927 11.9883 9.99208 11.9041 9.84375 11.7558C9.69543 11.6075 9.61129 11.4068 9.60947 11.1971C9.60765 10.9873 9.68828 10.7852 9.83401 10.6344L11.6684 8.79995H2.39961C2.18744 8.79995 1.98395 8.71567 1.83392 8.56564C1.68389 8.41561 1.59961 8.21212 1.59961 7.99995C1.59961 7.78778 1.68389 7.5843 1.83392 7.43427C1.98395 7.28424 2.18744 7.19995 2.39961 7.19995H11.6684L9.83401 5.36555C9.68403 5.21553 9.59978 5.01208 9.59978 4.79995C9.59978 4.58782 9.68403 4.38437 9.83401 4.23435Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); + content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%236B7280' %3E%3Cpath fill-rule='evenodd' d='M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM6.75 9.25a.75.75 0 0 0 0 1.5h4.59l-2.1 1.95a.75.75 0 0 0 1.02 1.1l3.5-3.25a.75.75 0 0 0 0-1.1l-3.5-3.25a.75.75 0 1 0-1.02 1.1l2.1 1.95H6.75Z' clip-rule='evenodd'%3E%3C/path%3E%3C/svg%3E"); } .gutenberg__editor .list-style--checkmarks li::before { position: absolute; - left: -1.25rem; + left: -1.125rem; + line-height: 1.25rem; + height: 1.25rem; vertical-align: middle; - line-height: 1.5rem; - margin-top: 2px; - content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); + margin-top: 0.5px; + content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%236B7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20507C13.8259 6.33634 13.8996 6.51436 13.8996 6.69997C13.8996 6.88559 13.8259 7.0636 13.6947 7.19487L8.0947 12.7949C7.96343 12.9261 7.78541 12.9998 7.5998 12.9998C7.41418 12.9998 7.23617 12.9261 7.1049 12.7949L4.3049 9.99487C4.17739 9.86285 4.10683 9.68603 4.10842 9.50249C4.11002 9.31895 4.18364 9.14339 4.31342 9.0136C4.44321 8.88381 4.61878 8.8102 4.80232 8.8086C4.98585 8.80701 5.16268 8.87756 5.2947 9.00507L7.5998 11.3102L12.7049 6.20507C12.8362 6.07384 13.0142 6.00012 13.1998 6.00012C13.3854 6.00012 13.5634 6.07384 13.6947 6.20507Z' fill='%23fff'/%3E%3C/svg%3E%0A"); } .gutenberg__editor .list-style--question-marks li::before { position: absolute; - left: -1.25rem; + left: -1.125rem; vertical-align: middle; - line-height: 1.5rem; - margin-top: 2px; - content: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='question-mark-circle'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M18 10C18 12.1217 17.1571 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18C7.87827 18 5.84344 17.1571 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C12.1217 2 14.1566 2.84285 15.6569 4.34315C17.1571 5.84344 18 7.87827 18 10ZM10 7C9.8243 6.99983 9.65165 7.04595 9.49945 7.13373C9.34724 7.22151 9.22085 7.34784 9.133 7.5C9.06957 7.61788 8.98311 7.72182 8.87876 7.80566C8.77441 7.8895 8.65429 7.95154 8.52552 7.9881C8.39675 8.02466 8.26194 8.03499 8.12911 8.01849C7.99627 8.00198 7.86809 7.95897 7.75218 7.89201C7.63628 7.82505 7.53499 7.7355 7.45433 7.62867C7.37367 7.52184 7.31529 7.3999 7.28263 7.27008C7.24997 7.14027 7.24371 7.00522 7.26421 6.87294C7.28472 6.74065 7.33157 6.61384 7.402 6.5C7.73222 5.92811 8.24191 5.48116 8.85203 5.22846C9.46214 4.97576 10.1386 4.93144 10.7765 5.10236C11.4143 5.27328 11.978 5.64989 12.38 6.1738C12.782 6.6977 13 7.33962 13 8C13.0002 8.62062 12.8079 9.22603 12.4498 9.73285C12.0916 10.2397 11.5851 10.623 11 10.83V11C11 11.2652 10.8946 11.5196 10.7071 11.7071C10.5196 11.8946 10.2652 12 10 12C9.73478 12 9.48043 11.8946 9.29289 11.7071C9.10536 11.5196 9 11.2652 9 11V10C9 9.73478 9.10536 9.48043 9.29289 9.29289C9.48043 9.10536 9.73478 9 10 9C10.2652 9 10.5196 8.89464 10.7071 8.70711C10.8946 8.51957 11 8.26522 11 8C11 7.73478 10.8946 7.48043 10.7071 7.29289C10.5196 7.10536 10.2652 7 10 7ZM10 15C10.2652 15 10.5196 14.8946 10.7071 14.7071C10.8946 14.5196 11 14.2652 11 14C11 13.7348 10.8946 13.4804 10.7071 13.2929C10.5196 13.1054 10.2652 13 10 13C9.73478 13 9.48043 13.1054 9.29289 13.2929C9.10536 13.4804 9 13.7348 9 14C9 14.2652 9.10536 14.5196 9.29289 14.7071C9.48043 14.8946 9.73478 15 10 15Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); + line-height: 1.25rem; + height: 1.25rem; + content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%236B7280' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm9.008-3.018a1.502 1.502 0 0 1 2.522 1.159v.024a1.44 1.44 0 0 1-1.493 1.418 1 1 0 0 0-1.037.999V14a1 1 0 1 0 2 0v-.539a3.44 3.44 0 0 0 2.529-3.256 3.502 3.502 0 0 0-7-.255 1 1 0 0 0 2 .076c.014-.398.187-.774.48-1.044Zm.982 7.026a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2h-.01Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); } diff --git a/packages/schema/src/fragments/PageContent/BlockImageWithText.gql b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql index 6fda48e0d..4745412db 100644 --- a/packages/schema/src/fragments/PageContent/BlockImageWithText.gql +++ b/packages/schema/src/fragments/PageContent/BlockImageWithText.gql @@ -1,6 +1,6 @@ fragment BlockImageWithText on BlockImageWithText { image { - source(width: 1536, sizes: [[768, 768], [1536, 1536]]) + source(width: 1536, height: 1536) alt } textContent { diff --git a/packages/ui/src/components/Atoms/List.css b/packages/ui/src/components/Atoms/List.css index 636916653..8ba56b35d 100644 --- a/packages/ui/src/components/Atoms/List.css +++ b/packages/ui/src/components/Atoms/List.css @@ -16,15 +16,17 @@ ul { line-height: 1.25rem !important; } +ul li::marker { + @apply !text-gray-500; +} + .list-style--arrows li::before { position: absolute; - left: -1rem; - line-height: 1rem; - height: 1rem; - margin-top: 2px; + left: -1.25rem; + line-height: 1.25rem; + height: 1.25rem; vertical-align: middle; - content: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='arrow-narrow-right'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M9.83401 4.23435C9.98403 4.08437 10.1875 4.00012 10.3996 4.00012C10.6117 4.00012 10.8152 4.08437 10.9652 4.23435L14.1652 7.43435C14.3152 7.58437 14.3994 7.78782 14.3994 7.99995C14.3994 8.21208 14.3152 8.41553 14.1652 8.56555L10.9652 11.7656C10.8143 11.9113 10.6122 11.9919 10.4025 11.9901C10.1927 11.9883 9.99208 11.9041 9.84375 11.7558C9.69543 11.6075 9.61129 11.4068 9.60947 11.1971C9.60765 10.9873 9.68828 10.7852 9.83401 10.6344L11.6684 8.79995H2.39961C2.18744 8.79995 1.98395 8.71567 1.83392 8.56564C1.68389 8.41561 1.59961 8.21212 1.59961 7.99995C1.59961 7.78778 1.68389 7.5843 1.83392 7.43427C1.98395 7.28424 2.18744 7.19995 2.39961 7.19995H11.6684L9.83401 5.36555C9.68403 5.21553 9.59978 5.01208 9.59978 4.79995C9.59978 4.58782 9.68403 4.38437 9.83401 4.23435Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); - list-style-position: inside; + content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%236B7280' %3E%3Cpath fill-rule='evenodd' d='M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM6.75 9.25a.75.75 0 0 0 0 1.5h4.59l-2.1 1.95a.75.75 0 0 0 1.02 1.1l3.5-3.25a.75.75 0 0 0 0-1.1l-3.5-3.25a.75.75 0 1 0-1.02 1.1l2.1 1.95H6.75Z' clip-rule='evenodd'%3E%3C/path%3E%3C/svg%3E"); } .list-style--checkmarks li::before { @@ -34,7 +36,7 @@ ul { height: 1.25rem; vertical-align: middle; margin-top: 0.5px; - content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); + content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%236B7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20507C13.8259 6.33634 13.8996 6.51436 13.8996 6.69997C13.8996 6.88559 13.8259 7.0636 13.6947 7.19487L8.0947 12.7949C7.96343 12.9261 7.78541 12.9998 7.5998 12.9998C7.41418 12.9998 7.23617 12.9261 7.1049 12.7949L4.3049 9.99487C4.17739 9.86285 4.10683 9.68603 4.10842 9.50249C4.11002 9.31895 4.18364 9.14339 4.31342 9.0136C4.44321 8.88381 4.61878 8.8102 4.80232 8.8086C4.98585 8.80701 5.16268 8.87756 5.2947 9.00507L7.5998 11.3102L12.7049 6.20507C12.8362 6.07384 13.0142 6.00012 13.1998 6.00012C13.3854 6.00012 13.5634 6.07384 13.6947 6.20507Z' fill='%23fff'/%3E%3C/svg%3E%0A"); } .list-style--question-marks li::before { @@ -43,5 +45,5 @@ ul { vertical-align: middle; line-height: 1.25rem; height: 1.25rem; - content: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='question-mark-circle'%3E%3Cpath id='Vector' fill-rule='evenodd' clip-rule='evenodd' d='M18 10C18 12.1217 17.1571 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18C7.87827 18 5.84344 17.1571 4.34315 15.6569C2.84285 14.1566 2 12.1217 2 10C2 7.87827 2.84285 5.84344 4.34315 4.34315C5.84344 2.84285 7.87827 2 10 2C12.1217 2 14.1566 2.84285 15.6569 4.34315C17.1571 5.84344 18 7.87827 18 10ZM10 7C9.8243 6.99983 9.65165 7.04595 9.49945 7.13373C9.34724 7.22151 9.22085 7.34784 9.133 7.5C9.06957 7.61788 8.98311 7.72182 8.87876 7.80566C8.77441 7.8895 8.65429 7.95154 8.52552 7.9881C8.39675 8.02466 8.26194 8.03499 8.12911 8.01849C7.99627 8.00198 7.86809 7.95897 7.75218 7.89201C7.63628 7.82505 7.53499 7.7355 7.45433 7.62867C7.37367 7.52184 7.31529 7.3999 7.28263 7.27008C7.24997 7.14027 7.24371 7.00522 7.26421 6.87294C7.28472 6.74065 7.33157 6.61384 7.402 6.5C7.73222 5.92811 8.24191 5.48116 8.85203 5.22846C9.46214 4.97576 10.1386 4.93144 10.7765 5.10236C11.4143 5.27328 11.978 5.64989 12.38 6.1738C12.782 6.6977 13 7.33962 13 8C13.0002 8.62062 12.8079 9.22603 12.4498 9.73285C12.0916 10.2397 11.5851 10.623 11 10.83V11C11 11.2652 10.8946 11.5196 10.7071 11.7071C10.5196 11.8946 10.2652 12 10 12C9.73478 12 9.48043 11.8946 9.29289 11.7071C9.10536 11.5196 9 11.2652 9 11V10C9 9.73478 9.10536 9.48043 9.29289 9.29289C9.48043 9.10536 9.73478 9 10 9C10.2652 9 10.5196 8.89464 10.7071 8.70711C10.8946 8.51957 11 8.26522 11 8C11 7.73478 10.8946 7.48043 10.7071 7.29289C10.5196 7.10536 10.2652 7 10 7ZM10 15C10.2652 15 10.5196 14.8946 10.7071 14.7071C10.8946 14.5196 11 14.2652 11 14C11 13.7348 10.8946 13.4804 10.7071 13.2929C10.5196 13.1054 10.2652 13 10 13C9.73478 13 9.48043 13.1054 9.29289 13.2929C9.10536 13.4804 9 13.7348 9 14C9 14.2652 9.10536 14.5196 9.29289 14.7071C9.48043 14.8946 9.73478 15 10 15Z' fill='%236B7280'/%3E%3C/g%3E%3C/svg%3E "); + content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%236B7280' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm9.008-3.018a1.502 1.502 0 0 1 2.522 1.159v.024a1.44 1.44 0 0 1-1.493 1.418 1 1 0 0 0-1.037.999V14a1 1 0 1 0 2 0v-.539a3.44 3.44 0 0 0 2.529-3.256 3.502 3.502 0 0 0-7-.255 1 1 0 0 0 2 .076c.014-.398.187-.774.48-1.044Zm.982 7.026a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2h-.01Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); } From c75569c4b97d486e3a17a28f3c175dcd028e1590 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Tue, 14 May 2024 23:44:28 +0300 Subject: [PATCH 135/221] fix(SLB-308): update style --- .../Organisms/PageContent/BlockCta.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx index cdf574951..620f567f1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx @@ -10,18 +10,20 @@ import React from 'react'; export function BlockCta(props: BlockCtaFragment) { return ( - - {props.text} - {!!props.icon && props.icon === CtaIconType.Arrow && } - +
    + + {props.text} + {!!props.icon && props.icon === CtaIconType.Arrow && } + +
    ); } From 0eef255f4a78112289c110cae0d8ac89acb14baa Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Tue, 14 May 2024 23:55:24 +0300 Subject: [PATCH 136/221] chore(SLB-288): fix questionmark icon --- packages/drupal/gutenberg_blocks/css/edit.css | 2 +- packages/ui/src/components/Atoms/List.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 13520cab5..27b2807c7 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -80,5 +80,5 @@ vertical-align: middle; line-height: 1.25rem; height: 1.25rem; - content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%236B7280' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm9.008-3.018a1.502 1.502 0 0 1 2.522 1.159v.024a1.44 1.44 0 0 1-1.493 1.418 1 1 0 0 0-1.037.999V14a1 1 0 1 0 2 0v-.539a3.44 3.44 0 0 0 2.529-3.256 3.502 3.502 0 0 0-7-.255 1 1 0 0 0 2 .076c.014-.398.187-.774.48-1.044Zm.982 7.026a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2h-.01Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); + content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%236B7280' viewBox='0 0 20 24'%3E%3Cpath fill-rule='evenodd' d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm9.008-3.018a1.502 1.502 0 0 1 2.522 1.159v.024a1.44 1.44 0 0 1-1.493 1.418 1 1 0 0 0-1.037.999V14a1 1 0 1 0 2 0v-.539a3.44 3.44 0 0 0 2.529-3.256 3.502 3.502 0 0 0-7-.255 1 1 0 0 0 2 .076c.014-.398.187-.774.48-1.044Zm.982 7.026a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2h-.01Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); } diff --git a/packages/ui/src/components/Atoms/List.css b/packages/ui/src/components/Atoms/List.css index 8ba56b35d..712c8e272 100644 --- a/packages/ui/src/components/Atoms/List.css +++ b/packages/ui/src/components/Atoms/List.css @@ -45,5 +45,5 @@ ul li::marker { vertical-align: middle; line-height: 1.25rem; height: 1.25rem; - content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%236B7280' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm9.008-3.018a1.502 1.502 0 0 1 2.522 1.159v.024a1.44 1.44 0 0 1-1.493 1.418 1 1 0 0 0-1.037.999V14a1 1 0 1 0 2 0v-.539a3.44 3.44 0 0 0 2.529-3.256 3.502 3.502 0 0 0-7-.255 1 1 0 0 0 2 .076c.014-.398.187-.774.48-1.044Zm.982 7.026a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2h-.01Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); + content: url("data:image/svg+xml,%3Csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='%236B7280' viewBox='0 0 20 24'%3E%3Cpath fill-rule='evenodd' d='M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm9.008-3.018a1.502 1.502 0 0 1 2.522 1.159v.024a1.44 1.44 0 0 1-1.493 1.418 1 1 0 0 0-1.037.999V14a1 1 0 1 0 2 0v-.539a3.44 3.44 0 0 0 2.529-3.256 3.502 3.502 0 0 0-7-.255 1 1 0 0 0 2 .076c.014-.398.187-.774.48-1.044Zm.982 7.026a1 1 0 1 0 0 2H12a1 1 0 1 0 0-2h-.01Z' clip-rule='evenodd'/%3E%3C/svg%3E%0A"); } From 5ec05295d4867e5233b6a1e17d5742ac56b27474 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Wed, 15 May 2024 09:24:35 +0300 Subject: [PATCH 137/221] chore(SLB-288): roll back checkmarks icon change --- packages/drupal/gutenberg_blocks/css/edit.css | 2 +- packages/ui/src/components/Atoms/List.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/css/edit.css b/packages/drupal/gutenberg_blocks/css/edit.css index 27b2807c7..252a81b73 100644 --- a/packages/drupal/gutenberg_blocks/css/edit.css +++ b/packages/drupal/gutenberg_blocks/css/edit.css @@ -71,7 +71,7 @@ height: 1.25rem; vertical-align: middle; margin-top: 0.5px; - content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%236B7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20507C13.8259 6.33634 13.8996 6.51436 13.8996 6.69997C13.8996 6.88559 13.8259 7.0636 13.6947 7.19487L8.0947 12.7949C7.96343 12.9261 7.78541 12.9998 7.5998 12.9998C7.41418 12.9998 7.23617 12.9261 7.1049 12.7949L4.3049 9.99487C4.17739 9.86285 4.10683 9.68603 4.10842 9.50249C4.11002 9.31895 4.18364 9.14339 4.31342 9.0136C4.44321 8.88381 4.61878 8.8102 4.80232 8.8086C4.98585 8.80701 5.16268 8.87756 5.2947 9.00507L7.5998 11.3102L12.7049 6.20507C12.8362 6.07384 13.0142 6.00012 13.1998 6.00012C13.3854 6.00012 13.5634 6.07384 13.6947 6.20507Z' fill='%23fff'/%3E%3C/svg%3E%0A"); + content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); } .gutenberg__editor .list-style--question-marks li::before { diff --git a/packages/ui/src/components/Atoms/List.css b/packages/ui/src/components/Atoms/List.css index 712c8e272..f185321e6 100644 --- a/packages/ui/src/components/Atoms/List.css +++ b/packages/ui/src/components/Atoms/List.css @@ -36,7 +36,7 @@ ul li::marker { height: 1.25rem; vertical-align: middle; margin-top: 0.5px; - content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%236B7280'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20507C13.8259 6.33634 13.8996 6.51436 13.8996 6.69997C13.8996 6.88559 13.8259 7.0636 13.6947 7.19487L8.0947 12.7949C7.96343 12.9261 7.78541 12.9998 7.5998 12.9998C7.41418 12.9998 7.23617 12.9261 7.1049 12.7949L4.3049 9.99487C4.17739 9.86285 4.10683 9.68603 4.10842 9.50249C4.11002 9.31895 4.18364 9.14339 4.31342 9.0136C4.44321 8.88381 4.61878 8.8102 4.80232 8.8086C4.98585 8.80701 5.16268 8.87756 5.2947 9.00507L7.5998 11.3102L12.7049 6.20507C12.8362 6.07384 13.0142 6.00012 13.1998 6.00012C13.3854 6.00012 13.5634 6.07384 13.6947 6.20507Z' fill='%23fff'/%3E%3C/svg%3E%0A"); + content: url("data:image/svg+xml,%3Csvg width='18' height='19' viewBox='0 0 18 19' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='0.5' width='18' height='18' rx='9' fill='%23DEF7EC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M13.6947 6.20519C13.8259 6.33646 13.8996 6.51448 13.8996 6.70009C13.8996 6.88571 13.8259 7.06372 13.6947 7.19499L8.0947 12.795C7.96343 12.9262 7.78541 12.9999 7.5998 12.9999C7.41418 12.9999 7.23617 12.9262 7.1049 12.795L4.3049 9.99499C4.17739 9.86297 4.10683 9.68615 4.10842 9.50261C4.11002 9.31908 4.18364 9.14351 4.31342 9.01372C4.44321 8.88394 4.61878 8.81032 4.80232 8.80872C4.98585 8.80713 5.16268 8.87768 5.2947 9.00519L7.5998 11.3103L12.7049 6.20519C12.8362 6.07397 13.0142 6.00024 13.1998 6.00024C13.3854 6.00024 13.5634 6.07397 13.6947 6.20519Z' fill='%230E9F6E'/%3E%3C/svg%3E "); } .list-style--question-marks li::before { From 9adcce22cf81a645c93668cae2e5f86c9f6a3149 Mon Sep 17 00:00:00 2001 From: Vasile Chindris Date: Mon, 29 Apr 2024 17:25:04 +0300 Subject: [PATCH 138/221] feat(SLB-292): horizontal separator gutenberg block --- .../src/blocks/horizontal-separator.tsx | 21 +++++++++++++++++++ packages/drupal/gutenberg_blocks/src/index.ts | 1 + .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 2 ++ packages/schema/src/fragments/Page.gql | 1 + .../PageContent/BlockHorizontalSeparator.gql | 5 +++++ packages/schema/src/schema.graphql | 5 +++++ .../BlockHorizontalSeparator.stories.ts | 11 ++++++++++ .../PageContent/BlockHorizontalSeparator.tsx | 5 +++++ .../src/components/Organisms/PageDisplay.tsx | 3 +++ tests/e2e/specs/drupal/blocks.spec.ts | 3 +++ tests/schema/specs/blocks.spec.ts | 6 ++++++ 11 files changed, 63 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx create mode 100644 packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx diff --git a/packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx b/packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx new file mode 100644 index 000000000..720094b1f --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/horizontal-separator.tsx @@ -0,0 +1,21 @@ +import { registerBlockType } from 'wordpress__blocks'; +import { compose, withState } from 'wordpress__compose'; + +declare const Drupal: { t: (s: string) => string }; + +const { t: __ } = Drupal; + +// @ts-ignore +registerBlockType(`custom/horizontal-separator`, { + title: __('Horizontal separator'), + icon: 'minus', + category: 'text', + attributes: {}, + // @ts-ignore + edit: compose(withState())(() => { + return
    ; + }), + save() { + return null; + }, +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 2763bdad8..2c86b28df 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -9,3 +9,4 @@ import './blocks/image-with-text'; import './filters/list'; import './blocks/cta'; import './blocks/quote'; +import './blocks/horizontal-separator'; diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index dd286b895..25830e9fb 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -55,6 +55,8 @@ default:

    A standalone paragraph with markup and link

    + + diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index cbf2227eb..3ecddb3c9 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -35,6 +35,7 @@ fragment Page on Page { ...BlockCta ...BlockImageWithText ...BlockQuote + ...BlockHorizontalSeparator } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql b/packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql new file mode 100644 index 000000000..ef992a139 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockHorizontalSeparator.gql @@ -0,0 +1,5 @@ +fragment BlockHorizontalSeparator on BlockHorizontalSeparator { + # This is not really used, but until we have other real setting fields, we + # have to provide a dummy one. + markup +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index e2034acb5..179240b49 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -206,6 +206,7 @@ union PageContent @resolveEditorBlockType = | BlockCta | BlockImageWithText | BlockQuote + | BlockHorizontalSeparator type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -283,6 +284,10 @@ type BlockQuote @type(id: "custom/quote") { image: MediaImage @resolveEditorBlockMedia } +type BlockHorizontalSeparator @type(id: "custom/horizontal-separator") { + markup: Markup! @resolveEditorBlockMarkup +} + input PaginationInput { limit: Int! offset: Int! diff --git a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts new file mode 100644 index 000000000..006c3158f --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.stories.ts @@ -0,0 +1,11 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { BlockHorizontalSeparator } from './BlockHorizontalSeparator'; + +export default { + component: BlockHorizontalSeparator, +} satisfies Meta; + +export const HorizontalSeparator = { + args: {}, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx new file mode 100644 index 000000000..4d5308927 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export function BlockHorizontalSeparator() { + return
    ; +} diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..f5b15c390 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -6,6 +6,7 @@ import { UnreachableCaseError } from '../../utils/unreachable-case-error'; import { PageTransition } from '../Molecules/PageTransition'; import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; +import { BlockHorizontalSeparator } from './PageContent/BlockHorizontalSeparator'; import { BlockMarkup } from './PageContent/BlockMarkup'; import { BlockMedia } from './PageContent/BlockMedia'; import { BlockQuote } from './PageContent/BlockQuote'; @@ -60,6 +61,8 @@ export function PageDisplay(page: PageFragment) { ); case 'BlockQuote': return
    ; + case 'BlockHorizontalSeparator': + return ; default: throw new UnreachableCaseError(block); } diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 5124a81b2..732cddb7f 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -22,6 +22,9 @@ test('All blocks are rendered', async ({ page }) => { page.locator('a:text("link")[href="/en/architecture"]'), ).toHaveCount(1); + // Horizontal separator. + await expect(page.locator('hr')).toHaveCount(1); + // Image await expect( page.locator( diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index ce85d8164..6b1b0f714 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -73,6 +73,9 @@ test('Blocks', async () => { __typename } } + ... on BlockHorizontalSeparator { + __typename + } } } { @@ -108,6 +111,9 @@ test('Blocks', async () => {

    A standalone paragraph with markup and link

    ", }, + { + "__typename": "BlockHorizontalSeparator", + }, { "__typename": "BlockMedia", "caption": "Media image", From ea7d53953310ce84f7c9c821fd46e817c731679b Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 2 May 2024 15:53:24 +0200 Subject: [PATCH 139/221] style: add hr style --- .../Organisms/PageContent/BlockHorizontalSeparator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx index 4d5308927..6a3922e8a 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx @@ -1,5 +1,5 @@ import React from 'react'; export function BlockHorizontalSeparator() { - return
    ; + return
    ; } From 8afab945a5b487db01bfe3d530809878f2ff0dcf Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 15 May 2024 08:35:34 +0200 Subject: [PATCH 140/221] style: adjust content width --- .../Organisms/PageContent/BlockHorizontalSeparator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx index 6a3922e8a..a4bc3311b 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockHorizontalSeparator.tsx @@ -1,5 +1,5 @@ import React from 'react'; export function BlockHorizontalSeparator() { - return
    ; + return
    ; } From a81793c8da534aa36d505f0c20440aa06fd98209 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Wed, 15 May 2024 13:27:32 +0300 Subject: [PATCH 141/221] chore(SLB-288): move content container to blocks --- .../ui/src/components/Atoms/Container.css | 2 +- .../Organisms/PageContent/BlockCta.tsx | 26 ++++--- .../Organisms/PageContent/BlockForm.tsx | 2 +- .../Organisms/PageContent/BlockMedia.tsx | 2 +- .../Organisms/PageContent/BlockQuote.tsx | 76 ++++++++++--------- .../src/components/Organisms/PageDisplay.tsx | 2 +- 6 files changed, 57 insertions(+), 53 deletions(-) diff --git a/packages/ui/src/components/Atoms/Container.css b/packages/ui/src/components/Atoms/Container.css index 5bd018f50..b30f83468 100644 --- a/packages/ui/src/components/Atoms/Container.css +++ b/packages/ui/src/components/Atoms/Container.css @@ -3,7 +3,7 @@ } .container-content { - @apply mx-auto max-w-7xl; + @apply mx-auto max-w-3xl; } .container-text { diff --git a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx index cdf574951..9a43189f9 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockCta.tsx @@ -10,18 +10,20 @@ import React from 'react'; export function BlockCta(props: BlockCtaFragment) { return ( - - {props.text} - {!!props.icon && props.icon === CtaIconType.Arrow && } - +
    + + {props.text} + {!!props.icon && props.icon === CtaIconType.Arrow && } + +
    ); } diff --git a/packages/ui/src/components/Organisms/PageContent/BlockForm.tsx b/packages/ui/src/components/Organisms/PageContent/BlockForm.tsx index 8ee6770a1..d1a20a28e 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockForm.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockForm.tsx @@ -19,7 +19,7 @@ export function BlockForm( } return ( -
    +
    -
    +
    {props.caption ? (
    diff --git a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx index c99955e06..6cea70774 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockQuote.tsx @@ -3,45 +3,47 @@ import React from 'react'; export function BlockQuote(props: BlockQuoteFragment) { return ( -
    -
    - - Quote Symbol - - -

    {props.quote && }

    -
    - {props.image && ( - {props.image.alt +
    +
    + + Quote Symbol + - )} -
    - {props.author &&

    {props.author}

    } + +

    {props.quote && }

    +
    + {props.image && ( + {props.image.alt + )} +
    + {props.author &&

    {props.author}

    } +
    + {props.role && ( +

    + / + + {props.role} + +

    + )}
    - {props.role && ( -

    - / - - {props.role} - -

    - )} -
    -
    +
    +
    ); } diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 86813de03..cc6a4cff1 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -17,7 +17,7 @@ export function PageDisplay(page: PageFragment) {
    {page.hero ? : null}
    -
    +
    {page?.content?.filter(isTruthy).map((block, index) => { switch (block.__typename) { case 'BlockMedia': From cedf26536877f6adb05619604852d195a90603dd Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Tue, 7 May 2024 16:54:05 +0200 Subject: [PATCH 142/221] feat: breadcrumbs implementation --- .../Molecules/Breadcrumbs.stories.tsx | 24 +++++ .../src/components/Molecules/Breadcrumbs.tsx | 66 ++++++++++++++ packages/ui/src/components/Routes/Menu.tsx | 90 +++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx create mode 100644 packages/ui/src/components/Molecules/Breadcrumbs.tsx create mode 100644 packages/ui/src/components/Routes/Menu.tsx diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx new file mode 100644 index 000000000..a0ca464d7 --- /dev/null +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -0,0 +1,24 @@ +import { FrameQuery, OperationExecutor } from '@custom/schema'; +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { Default as FrameStory } from '../Routes/Frame.stories'; +import BreadCrumbs from './Breadcrumbs'; + +export default { + component: BreadCrumbs, + parameters: { + layout: 'fullscreen', + location: { pathname: '/gatsby-turbo' }, + }, +} satisfies StoryObj; + +export const Default = { + render: () => { + return ( + + + + ); + }, +}; diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx new file mode 100644 index 000000000..7f0811eb4 --- /dev/null +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -0,0 +1,66 @@ +import { Link } from '@custom/schema'; +import { ChevronRightIcon } from '@heroicons/react/24/outline'; +import clsx from 'clsx'; +import React from 'react'; + +import { isTruthy } from '../../utils/isTruthy'; +import { useBreadcrumbs } from '../Routes/Menu'; + +export default function BreadCrumbs({ + bgCol = 'gray-50', + className, +}: { + bgCol?: string; + className?: string; +}) { + const breadcrumbs = useBreadcrumbs(); + + console.log('breadcrumbs:', breadcrumbs); + + if (!breadcrumbs.length) { + console.log('breadcrumbs null:'); + return null; + } + + return ( + + ); +} diff --git a/packages/ui/src/components/Routes/Menu.tsx b/packages/ui/src/components/Routes/Menu.tsx new file mode 100644 index 000000000..2721f1a90 --- /dev/null +++ b/packages/ui/src/components/Routes/Menu.tsx @@ -0,0 +1,90 @@ +import { FrameQuery, NavigationItem, Url, useLocation } from '@custom/schema'; +import { useIntl } from 'react-intl'; + +import { useOperation } from '../../utils/operation'; + +export type MenuNameType = 'main' | 'footer'; + +export function useMenus() { + const intl = useIntl(); + const locale = intl.locale; + const settings = useOperation(FrameQuery).data; + return { + main: settings?.mainNavigation + ?.filter((nav) => nav?.locale === locale) + .pop(), + footer: settings?.footerNavigation + ?.filter((nav) => nav?.locale === locale) + .pop(), + }; +} + +export function useCurrentPath() { + const [loc] = useLocation(); + return loc.pathname; +} + +export function useMenuItem(path: string, menuName: MenuNameType) { + const menus = useMenus(); + return ( + menus && + menus[menuName]?.items.find((menuItem) => menuItem?.target === path) + ); +} + +export function useMenuChildren(path: string, menuName: MenuNameType) { + const menus = useMenus(); + const menuItemFromPath = useMenuItem(path, menuName); + return ( + menus && + menus[menuName]?.items.filter( + (menuItem) => menuItem?.parent === menuItemFromPath?.id, + ) + ); +} + +export function useCurrentMenuItem(menuName: MenuNameType) { + const currentPath = useCurrentPath(); + return useMenuItem(currentPath || '', menuName); +} + +export function useCurrentMenuChildren(menuName: MenuNameType) { + const currentPath = useCurrentPath(); + return useMenuChildren(currentPath || '', menuName); +} + +export function useMenuAncestors(path: string, menuName: MenuNameType) { + const menus = useMenus(); + const menuItemFromPath = useMenuItem(path, menuName); + let processingMenuItem = menuItemFromPath; + const ancestors: Array = []; + + // Set current page breadcrumb + if (typeof processingMenuItem !== 'undefined') { + ancestors.push(processingMenuItem); + } + + while ( + typeof processingMenuItem !== 'undefined' && + processingMenuItem?.parent + ) { + processingMenuItem = + menus && + menus[menuName]?.items.find( + (menuItem) => menuItem?.id === processingMenuItem?.parent, + ); + if (typeof processingMenuItem !== 'undefined') { + ancestors.push(processingMenuItem); + } + } + if (ancestors.length > 0) { + ancestors.push({ id: '_', target: '/' as Url, title: 'Home' }); + } + + return ancestors.reverse(); +} + +export const useBreadcrumbs = (menuName?: MenuNameType, path?: string) => { + const currentPath = useCurrentPath(); + return useMenuAncestors(path || currentPath || '', menuName || 'main'); +}; From 957d435edf6517f40468d37b0bd0125171dc32cb Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Tue, 7 May 2024 20:20:23 +0200 Subject: [PATCH 143/221] feat: wip --- .../components/Molecules/Breadcrumbs.stories.tsx | 6 +++--- .../ui/src/components/Molecules/Breadcrumbs.tsx | 13 ++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx index a0ca464d7..81dd002ba 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -1,5 +1,5 @@ import { FrameQuery, OperationExecutor } from '@custom/schema'; -import { Meta, StoryObj } from '@storybook/react'; +import { Meta } from '@storybook/react'; import React from 'react'; import { Default as FrameStory } from '../Routes/Frame.stories'; @@ -9,9 +9,9 @@ export default { component: BreadCrumbs, parameters: { layout: 'fullscreen', - location: { pathname: '/gatsby-turbo' }, + location: new URL('local:/gatsby-turbo'), }, -} satisfies StoryObj; +} satisfies Meta; export const Default = { render: () => { diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx index 7f0811eb4..f057661ce 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -6,19 +6,10 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { useBreadcrumbs } from '../Routes/Menu'; -export default function BreadCrumbs({ - bgCol = 'gray-50', - className, -}: { - bgCol?: string; - className?: string; -}) { +export default function BreadCrumbs({ className }: { className?: string }) { const breadcrumbs = useBreadcrumbs(); - console.log('breadcrumbs:', breadcrumbs); - if (!breadcrumbs.length) { - console.log('breadcrumbs null:'); return null; } @@ -27,7 +18,7 @@ export default function BreadCrumbs({ className={clsx('pt-5 max-w-screen-xl mx-auto', className)} aria-label="Breadcrumb" > -
      +
        {breadcrumbs?.filter(isTruthy).map(({ title, target, id }, index) => (
      1. {index > 0 ? ( From 755ccd76166fe3125d64efa95f6cca10473cf36d Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 8 May 2024 09:39:42 +0200 Subject: [PATCH 144/221] feat: wip --- .../Molecules/Breadcrumbs.stories.tsx | 2 +- .../src/components/Molecules/Breadcrumbs.tsx | 9 +++---- .../src/components/Organisms/PageDisplay.tsx | 10 +++++++- packages/ui/src/components/Routes/Menu.tsx | 4 ++- .../ui/src/components/Routes/Page.stories.tsx | 25 ++++++++++++++----- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx index 81dd002ba..9a7c6d0e5 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -3,7 +3,7 @@ import { Meta } from '@storybook/react'; import React from 'react'; import { Default as FrameStory } from '../Routes/Frame.stories'; -import BreadCrumbs from './Breadcrumbs'; +import { BreadCrumbs } from './Breadcrumbs'; export default { component: BreadCrumbs, diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx index f057661ce..0efa3eb61 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { useBreadcrumbs } from '../Routes/Menu'; -export default function BreadCrumbs({ className }: { className?: string }) { +export function BreadCrumbs({ className }: { className?: string }) { const breadcrumbs = useBreadcrumbs(); if (!breadcrumbs.length) { @@ -14,11 +14,8 @@ export default function BreadCrumbs({ className }: { className?: string }) { } return ( -
    + ); } @@ -120,6 +128,7 @@ function NoImageHero(props: NonNullable) { return (
    +

    {props.headline}

    From 7ccb740be7871f9052c1808c486bf3728f4fc216 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Wed, 15 May 2024 08:43:01 +0200 Subject: [PATCH 154/221] style(SLB-295): adjust positioning more --- .../ui/src/components/Organisms/PageHero.tsx | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index 998f322c9..bb7103693 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -126,36 +126,40 @@ function FormHero(props: NonNullable) { function NoImageHero(props: NonNullable) { return ( -
    -
    + <> +
    -

    - {props.headline} -

    - {props.lead ? ( -

    {props.lead}

    - ) : null} - {props.ctaText && props.ctaUrl ? ( - - - - - {props.ctaText} - - ) : null}
    -
    +
    +
    +

    + {props.headline} +

    + {props.lead ? ( +

    {props.lead}

    + ) : null} + {props.ctaText && props.ctaUrl ? ( + + + + + {props.ctaText} + + ) : null} +
    +
    + ); } From e2f60f601d4559fbda5d7fff2786955164a0ab66 Mon Sep 17 00:00:00 2001 From: HagerDakroury Date: Wed, 15 May 2024 18:15:59 +0300 Subject: [PATCH 155/221] refactor: refactor containment Co-authored-by: Mattia Simonato --- .../ui/src/components/Atoms/Container.css | 6 +- .../src/components/Molecules/Breadcrumbs.tsx | 76 +++--- .../ui/src/components/Organisms/Footer.tsx | 8 +- .../ui/src/components/Organisms/Header.tsx | 256 +++++++++--------- .../Organisms/PageContent/BlockCta.tsx | 35 ++- .../Organisms/PageContent/BlockForm.tsx | 38 +-- .../PageContent/BlockHorizontalSeparator.tsx | 8 +- .../Organisms/PageContent/BlockMarkup.tsx | 114 ++++---- .../Organisms/PageContent/BlockMedia.tsx | 18 +- .../Organisms/PageContent/BlockQuote.tsx | 82 +++--- .../src/components/Organisms/PageDisplay.tsx | 104 ++++--- .../ui/src/components/Organisms/PageHero.tsx | 10 +- 12 files changed, 396 insertions(+), 359 deletions(-) diff --git a/packages/ui/src/components/Atoms/Container.css b/packages/ui/src/components/Atoms/Container.css index b30f83468..566f31114 100644 --- a/packages/ui/src/components/Atoms/Container.css +++ b/packages/ui/src/components/Atoms/Container.css @@ -1,13 +1,13 @@ .container-page { - @apply max-w-full px-[1.25rem] md:px-[3.75rem]; + @apply max-w-full px-[1.25rem] md:px-[3.75rem] lg:px-[5rem]; } .container-content { - @apply mx-auto max-w-3xl; + @apply mx-auto max-w-7xl; } .container-text { - @apply max-w-[40.75rem] xl:ml-[11rem] lg:ml-[7rem]; + @apply max-w-3xl mx-auto; } .nested-container .container-page { diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.tsx index 27232e13b..431291137 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { isTruthy } from '../../utils/isTruthy'; import { useBreadcrumbs } from '../Routes/Menu'; -export function BreadCrumbs({ className }: { className?: string }) { +export function BreadCrumbs() { const breadcrumbs = useBreadcrumbs(); if (!breadcrumbs.length) { @@ -14,41 +14,43 @@ export function BreadCrumbs({ className }: { className?: string }) { } return ( - +
    + +
    ); } diff --git a/packages/ui/src/components/Organisms/Footer.tsx b/packages/ui/src/components/Organisms/Footer.tsx index c41d751be..73dc34bdf 100644 --- a/packages/ui/src/components/Organisms/Footer.tsx +++ b/packages/ui/src/components/Organisms/Footer.tsx @@ -21,11 +21,13 @@ export function Footer() { return (
    -
    - -
    + ); } @@ -115,7 +113,7 @@ function FormHero(props: NonNullable) {
    {props.formUrl ? (
    -
    +
    @@ -127,9 +125,7 @@ function FormHero(props: NonNullable) { function NoImageHero(props: NonNullable) { return ( <> -
    - -
    +

    From 13908d2b01c1e062041d45fa5330f29700991313 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 11:11:10 +0200 Subject: [PATCH 156/221] feat(SLB-300): add accordion block --- .../src/blocks/accordion-item-text.tsx | 90 +++++++++++++++++++ .../gutenberg_blocks/src/blocks/accordion.tsx | 25 ++++++ packages/drupal/gutenberg_blocks/src/index.ts | 2 + 3 files changed, 117 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx create mode 100644 packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx diff --git a/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx new file mode 100644 index 000000000..774c0a96c --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx @@ -0,0 +1,90 @@ +import React, { Fragment } from 'react'; +import { InspectorControls, RichText } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; +import { PanelBody, SelectControl } from 'wordpress__components'; +import { compose, withState } from 'wordpress__compose'; + +// @ts-ignore +const { t: __ } = Drupal; +// @ts-ignore +registerBlockType('custom/accordion-item-text', { + title: 'Accordion Item Text', + icon: 'text', + category: 'layout', + parent: ['custom/accordion'], + attributes: { + title: { + type: 'string', + }, + text: { + type: 'string', + }, + icon: { + type: 'string', + }, + }, + // @ts-ignore + edit: compose(withState())((props) => { + const { attributes, setAttributes } = props; + const icons = [ + { label: __('- Select an optional icon -'), value: '' }, + { label: __('Checkmark'), value: 'checkmark' }, + { label: __('Questionmark'), value: 'questionmark' }, + { label: __('Arrow'), value: 'arrow' }, + ]; + setAttributes({ + icon: attributes.icon === undefined ? '' : attributes.icon, + }); + + return ( + + + + { + setAttributes({ + icon: newValue, + }); + }} + /> + + +
    +
    {__('Accordion Item Text')}
    +
    + { + setAttributes({ title: newValue }); + }} + /> + { + setAttributes({ text: newValue }); + }} + /> +
    +
    +
    + ); + }), + + save() { + return null; + }, +}); diff --git a/packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx b/packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx new file mode 100644 index 000000000..924746dd8 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/blocks/accordion.tsx @@ -0,0 +1,25 @@ +import { InnerBlocks } from 'wordpress__block-editor'; +import { registerBlockType } from 'wordpress__blocks'; + +// @ts-ignore +const { t: __ } = Drupal; + +registerBlockType('custom/accordion', { + title: __('Accordion'), + icon: 'menu', + category: 'layout', + attributes: {}, + edit: () => { + return ( +
    +
    {__('Accordion')}
    + +
    + ); + }, + save: () => , +}); diff --git a/packages/drupal/gutenberg_blocks/src/index.ts b/packages/drupal/gutenberg_blocks/src/index.ts index 2c86b28df..e2c0159f7 100644 --- a/packages/drupal/gutenberg_blocks/src/index.ts +++ b/packages/drupal/gutenberg_blocks/src/index.ts @@ -10,3 +10,5 @@ import './filters/list'; import './blocks/cta'; import './blocks/quote'; import './blocks/horizontal-separator'; +import './blocks/accordion'; +import './blocks/accordion-item-text'; From c1a631770c1ed873d08230eb2bd4afe057fcfbd9 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 11:30:28 +0200 Subject: [PATCH 157/221] feat(SLB-300): accordion validators --- .../AccordionItemTextValidator.php | 37 ++++++++++++++++++ .../GutenbergValidator/AccordionValidator.php | 39 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php create mode 100644 packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php new file mode 100644 index 000000000..6f9a6dcee --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php @@ -0,0 +1,37 @@ + [ + 'field_label' => $this->t('Title'), + 'rules' => ['required'], + ], + // @todo check if we want text as rich text or inner blocks. + ]; + } + +} diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php new file mode 100644 index 000000000..6e7443e53 --- /dev/null +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php @@ -0,0 +1,39 @@ + 'custom/accordion-item-text', + 'blockLabel' => $this->t('Accordion'), + 'min' => 1, + 'max' => GutenbergCardinalityValidatorInterface::CARDINALITY_UNLIMITED, + ], + ]; + return $this->validateCardinality($block, $expectedChildren); + } + +} From 67eafa1b48cae4ddd4c19636523694c6fc1e7e67 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 11:43:19 +0200 Subject: [PATCH 158/221] feat(SLB-300): accordion schema --- packages/schema/src/schema.graphql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 179240b49..efeefbd72 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -207,6 +207,7 @@ union PageContent @resolveEditorBlockType = | BlockImageWithText | BlockQuote | BlockHorizontalSeparator + | BlockAccordion type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -246,6 +247,24 @@ type BlockImageTeaser @default @value { ctaUrl: Url @resolveEditorBlockAttribute(key: "ctaUrl") } +type BlockAccordion @type(id: "custom/accordion") { + items: [BlockAccordionItem]! @resolveEditorBlockChildren +} + +interface BlockAccordionItemInterface { + title: String! +} + +union BlockAccordionItem @resolveEditorBlockType = BlockAccordionItemText + +type BlockAccordionItemText implements BlockAccordionItemInterface + @type(id: "custom/accordion-item-text") { + title: String! @resolveEditorBlockAttribute(key: "title") + icon: String! @resolveEditorBlockAttribute(key: "icon") + # @todo check if we want inner blocks here. + text: String! @resolveEditorBlockAttribute(key: "text") +} + type BlockCta @type(id: "custom/cta") { url: Url @resolveEditorBlockAttribute(key: "url") text: String @resolveEditorBlockAttribute(key: "text") From aac5114db18334c7ccd62b3f041b74354575518d Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 12:38:36 +0200 Subject: [PATCH 159/221] chore(SLB-300): add placeholder for storybook integration --- packages/ui/src/components/Organisms/PageDisplay.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 57df3a91c..14f308190 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -67,6 +67,10 @@ export function PageDisplay(page: PageFragment) { return
    ; case 'BlockHorizontalSeparator': return ; + case 'BlockAccordion': + // @todo implement. + // eslint-disable-next-line react/jsx-no-literals + return
    BlockAccordion goes here
    ; default: throw new UnreachableCaseError(block); } From 42263b0b8689bed94b4890d7fe1c653d348798f7 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 13:09:06 +0200 Subject: [PATCH 160/221] feat(SLB-300): add accordion fragments --- packages/schema/src/fragments/Page.gql | 1 + .../src/fragments/PageContent/BlockAccordion.gql | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 packages/schema/src/fragments/PageContent/BlockAccordion.gql diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 3ecddb3c9..59285ccc4 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -36,6 +36,7 @@ fragment Page on Page { ...BlockImageWithText ...BlockQuote ...BlockHorizontalSeparator + ...BlockAccordion } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockAccordion.gql b/packages/schema/src/fragments/PageContent/BlockAccordion.gql new file mode 100644 index 000000000..d98beaa51 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockAccordion.gql @@ -0,0 +1,11 @@ +fragment BlockAccordion on BlockAccordion { + items { + ...BlockAccordionItemText + } +} + +fragment BlockAccordionItemText on BlockAccordionItemText { + title + icon + text +} From 3eec141dd30076a371745eca3b47fdcfd83c35ab Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 14:16:18 +0200 Subject: [PATCH 161/221] fix(SLB-300): don't use union for now Types implementing queryable interfaces must also implement the Node interface. --- packages/schema/src/schema.graphql | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index efeefbd72..816b8cac2 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -248,17 +248,10 @@ type BlockImageTeaser @default @value { } type BlockAccordion @type(id: "custom/accordion") { - items: [BlockAccordionItem]! @resolveEditorBlockChildren + items: [BlockAccordionItemText]! @resolveEditorBlockChildren } -interface BlockAccordionItemInterface { - title: String! -} - -union BlockAccordionItem @resolveEditorBlockType = BlockAccordionItemText - -type BlockAccordionItemText implements BlockAccordionItemInterface - @type(id: "custom/accordion-item-text") { +type BlockAccordionItemText @default @value { title: String! @resolveEditorBlockAttribute(key: "title") icon: String! @resolveEditorBlockAttribute(key: "icon") # @todo check if we want inner blocks here. From a3e2a0f9829b719cede641d47589f9d93700ce5f Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 15:31:00 +0200 Subject: [PATCH 162/221] refactor(SLB-300): use InnerBlocks instead of RichText --- .../AccordionItemTextValidator.php | 16 ++++++++++- .../GutenbergValidator/AccordionValidator.php | 3 +++ .../src/blocks/accordion-item-text.tsx | 27 ++++++++----------- .../fragments/PageContent/BlockAccordion.gql | 4 ++- packages/schema/src/schema.graphql | 3 +-- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php index 6f9a6dcee..4ab6b31ab 100644 --- a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionItemTextValidator.php @@ -3,6 +3,8 @@ namespace Drupal\gutenberg_blocks\Plugin\Validation\GutenbergValidator; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorInterface; +use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergCardinalityValidatorTrait; use Drupal\silverback_gutenberg\GutenbergValidation\GutenbergValidatorBase; /** @@ -12,6 +14,7 @@ * ) */ class AccordionItemTextValidator extends GutenbergValidatorBase { + use GutenbergCardinalityValidatorTrait; use StringTranslationTrait; /** @@ -30,8 +33,19 @@ public function validatedFields($block = []): array { 'field_label' => $this->t('Title'), 'rules' => ['required'], ], - // @todo check if we want text as rich text or inner blocks. ]; } + /** + * {@inheritDoc} + */ + public function validateContent($block = []): array { + $expectedChildren = [ + 'validationType' => GutenbergCardinalityValidatorInterface::CARDINALITY_ANY, + 'min' => 1, + 'max' => GutenbergCardinalityValidatorInterface::CARDINALITY_UNLIMITED, + ]; + return $this->validateCardinality($block, $expectedChildren); + } + } diff --git a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php index 6e7443e53..d0bae38b0 100644 --- a/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php +++ b/packages/drupal/gutenberg_blocks/src/Plugin/Validation/GutenbergValidator/AccordionValidator.php @@ -24,6 +24,9 @@ public function applies(array $block): bool { return $block['blockName'] === 'custom/accordion'; } + /** + * {@inheritDoc} + */ public function validateContent($block = []): array { $expectedChildren = [ [ diff --git a/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx index 774c0a96c..dd50cd99e 100644 --- a/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx +++ b/packages/drupal/gutenberg_blocks/src/blocks/accordion-item-text.tsx @@ -1,5 +1,9 @@ import React, { Fragment } from 'react'; -import { InspectorControls, RichText } from 'wordpress__block-editor'; +import { + InnerBlocks, + InspectorControls, + RichText, +} from 'wordpress__block-editor'; import { registerBlockType } from 'wordpress__blocks'; import { PanelBody, SelectControl } from 'wordpress__components'; import { compose, withState } from 'wordpress__compose'; @@ -16,9 +20,6 @@ registerBlockType('custom/accordion-item-text', { title: { type: 'string', }, - text: { - type: 'string', - }, icon: { type: 'string', }, @@ -67,16 +68,10 @@ registerBlockType('custom/accordion-item-text', { setAttributes({ title: newValue }); }} /> - { - setAttributes({ text: newValue }); - }} +

    @@ -84,7 +79,7 @@ registerBlockType('custom/accordion-item-text', { ); }), - save() { - return null; + save: () => { + return ; }, }); diff --git a/packages/schema/src/fragments/PageContent/BlockAccordion.gql b/packages/schema/src/fragments/PageContent/BlockAccordion.gql index d98beaa51..0f77b257d 100644 --- a/packages/schema/src/fragments/PageContent/BlockAccordion.gql +++ b/packages/schema/src/fragments/PageContent/BlockAccordion.gql @@ -7,5 +7,7 @@ fragment BlockAccordion on BlockAccordion { fragment BlockAccordionItemText on BlockAccordionItemText { title icon - text + textContent { + markup + } } diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 816b8cac2..73458a6eb 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -254,8 +254,7 @@ type BlockAccordion @type(id: "custom/accordion") { type BlockAccordionItemText @default @value { title: String! @resolveEditorBlockAttribute(key: "title") icon: String! @resolveEditorBlockAttribute(key: "icon") - # @todo check if we want inner blocks here. - text: String! @resolveEditorBlockAttribute(key: "text") + textContent: BlockMarkup @resolveEditorBlockChildren @seek(pos: 0) } type BlockCta @type(id: "custom/cta") { From d25a4137b955818ccb963397e1ba8b1f397108a7 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 15:44:40 +0200 Subject: [PATCH 163/221] test(SLB-300): accordion block --- .../a397ca48-8fad-411e-8901-0eba2feb989c.yml | 16 +++++++- tests/schema/specs/blocks.spec.ts | 40 +++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index 25830e9fb..be659117f 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -120,9 +120,23 @@ default: + + -

    +

    Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

    + + + + + +
    • Moitié-moitié
    • Fribourgeoise
    + + + +

    Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

    + + format: gutenberg summary: '' diff --git a/tests/schema/specs/blocks.spec.ts b/tests/schema/specs/blocks.spec.ts index 6b1b0f714..1786fc59b 100644 --- a/tests/schema/specs/blocks.spec.ts +++ b/tests/schema/specs/blocks.spec.ts @@ -76,6 +76,19 @@ test('Blocks', async () => { ... on BlockHorizontalSeparator { __typename } + ... on BlockAccordion { + items { + __typename + ... on BlockAccordionItemText { + __typename + title + icon + textContent { + markup + } + } + } + } } } { @@ -224,10 +237,31 @@ test('Blocks', async () => { "role": "Project manager", }, { - "__typename": "BlockMarkup", - "markup": " -

    + "__typename": "BlockAccordion", + "items": [ + { + "__typename": "BlockAccordionItemText", + "icon": "", + "textContent": { + "markup": " +

    Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

    + ", + }, + "title": "With a single paragraph and no icon", + }, + { + "__typename": "BlockAccordionItemText", + "icon": "arrow", + "textContent": { + "markup": " +
    • Moitié-moitié
    • Fribourgeoise
    + +

    Incididunt laborum velit non proident nostrud velit. Minim excepteur ut aliqua nisi. Culpa laboris consectetur proident. Tempor esse ullamco et dolor proident id officia laborum voluptate nostrud elit dolore qui amet. Ex Lorem irure eu anim ipsum officia.

    ", + }, + "title": "With a list and a paragraph and arrow icon", + }, + ], }, ], "hero": { From 795646d9926a219c7ca6912ef0464033d54774f8 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 18:03:29 +0200 Subject: [PATCH 164/221] feat(SLB-301): accordion storybook --- packages/schema/src/schema.graphql | 2 +- .../PageContent/BlockAccordion.stories.tsx | 56 +++++++++++++++ .../Organisms/PageContent/BlockAccordion.tsx | 68 +++++++++++++++++++ .../src/components/Organisms/PageDisplay.tsx | 5 +- 4 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx create mode 100644 packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 73458a6eb..fbeafa4e3 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -248,7 +248,7 @@ type BlockImageTeaser @default @value { } type BlockAccordion @type(id: "custom/accordion") { - items: [BlockAccordionItemText]! @resolveEditorBlockChildren + items: [BlockAccordionItemText!]! @resolveEditorBlockChildren } type BlockAccordionItemText @default @value { diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx new file mode 100644 index 000000000..867eaef1b --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx @@ -0,0 +1,56 @@ +import { Markup } from '@custom/schema'; +import { Meta, StoryObj } from '@storybook/react'; + +import { BlockAccordion } from './BlockAccordion'; + +export default { + component: BlockAccordion, +} satisfies Meta; + +export const AccordionItemText = { + args: { + items: [ + { + title: 'Cheese Fondue', + icon: '', + textContent: { + markup: ` +

    The earliest known recipe for the modern form of cheese fondue comes from a 1699 book published in Zürich, under the name "Käss mit Wein zu kochen" 'to cook cheese with wine'. It calls for grated or cut-up cheese to be melted with wine, and for bread to be dipped in it.

    +
      +
    • Fribourgeoise
    • +
    • Moitié-Moitié
    • +
    + ` as Markup, + }, + }, + { + title: 'Rösti', + icon: 'questionmark', + textContent: { + markup: ` +

    Rösti is a kind of fried potato cake served as a main course or side dish.

    +

    As a main dish, rösti is usually accompanied with cheese, onions and cold meat or eggs. This dish, originally from Zürich, was first simply made by frying grated raw potatoes in a pan. It has then spread towards Bern where it is made with boiled potatoes instead. This is where it took the name Rösti.[20] There are many variants in Switzerland and outside the borders.[21] This culinary specialty gives its name to the röstigraben, which designates the cultural differences between the German- and French-speaking parts of the country.

    + ` as Markup, + }, + }, + { + title: 'Älplermagronen', + icon: 'checkmark', + textContent: { + markup: ` +

    Älplermagronen are now regarded as a traditional dish of the Swiss Alps and a classic of Swiss comfort foods. According to a popular theory, pasta became widespread in northern Switzerland in the late 19th century, when the Gotthard Tunnel was built, partly by Italian workers who brought dry pasta with them.

    + ` as Markup, + }, + }, + { + title: 'Meringue with double cream', + icon: 'arrow', + textContent: { + markup: ` +

    The Oxford English Dictionary states that the French word is of unknown origin. The name meringue for this confection first appeared in print in François Massialot's cookbook of 1692.

    + ` as Markup, + }, + }, + ], + }, +} satisfies StoryObj; diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx new file mode 100644 index 000000000..4fa234038 --- /dev/null +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -0,0 +1,68 @@ +import { + BlockAccordionFragment, + BlockAccordionItemTextFragment, +} from '@custom/schema'; +import { + ArrowRightCircleIcon, + CheckCircleIcon, + QuestionMarkCircleIcon, +} from '@heroicons/react/20/solid'; +import React from 'react'; + +import { BlockMarkup } from './BlockMarkup'; + +export function BlockAccordion(props: BlockAccordionFragment) { + return ( +
    + {props.items.map((item, index) => ( + + ))} +
    + ); +} + +function AccordionItemText( + props: BlockAccordionItemTextFragment & { + id: number; + }, +) { + return ( + <> +

    + +

    +
    +
    + {props.textContent?.markup && } +
    +
    + + ); +} + +function AccordionIcon({ icon }: { icon: string }) { + switch (icon) { + case 'questionmark': + return ; + case 'checkmark': + return ; + case 'arrow': + return ; + default: + return null; + } +} diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 14f308190..27424c280 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -5,6 +5,7 @@ import { isTruthy } from '../../utils/isTruthy'; import { UnreachableCaseError } from '../../utils/unreachable-case-error'; import { BreadCrumbs } from '../Molecules/Breadcrumbs'; import { PageTransition } from '../Molecules/PageTransition'; +import { BlockAccordion } from './PageContent/BlockAccordion'; import { BlockCta } from './PageContent/BlockCta'; import { BlockForm } from './PageContent/BlockForm'; import { BlockHorizontalSeparator } from './PageContent/BlockHorizontalSeparator'; @@ -68,9 +69,7 @@ export function PageDisplay(page: PageFragment) { case 'BlockHorizontalSeparator': return ; case 'BlockAccordion': - // @todo implement. - // eslint-disable-next-line react/jsx-no-literals - return
    BlockAccordion goes here
    ; + return ; default: throw new UnreachableCaseError(block); } From 77b9c24bb066e1d185b3beb7143bf136d1ffde27 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 19:05:16 +0200 Subject: [PATCH 165/221] refactor(SLB-301): use custom theme --- apps/website/package.json | 1 + .../Organisms/PageContent/BlockAccordion.tsx | 108 ++++++++++-------- pnpm-lock.yaml | 96 ++++++++++++++-- 3 files changed, 148 insertions(+), 57 deletions(-) diff --git a/apps/website/package.json b/apps/website/package.json index 2954fe07e..870eea6af 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -15,6 +15,7 @@ "@custom/decap": "workspace:*", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", + "flowbite-react": "^0.9.0", "gatsby": "^5.13.1", "gatsby-plugin-layout": "^4.13.0", "gatsby-plugin-manifest": "^5.13.0", diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 4fa234038..d9838dce7 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -1,67 +1,85 @@ -import { - BlockAccordionFragment, - BlockAccordionItemTextFragment, -} from '@custom/schema'; +import { BlockAccordionFragment } from '@custom/schema'; import { ArrowRightCircleIcon, CheckCircleIcon, QuestionMarkCircleIcon, } from '@heroicons/react/20/solid'; +import { Accordion, CustomFlowbiteTheme, Flowbite } from 'flowbite-react'; import React from 'react'; import { BlockMarkup } from './BlockMarkup'; -export function BlockAccordion(props: BlockAccordionFragment) { - return ( -
    - {props.items.map((item, index) => ( - - ))} -
    - ); -} - -function AccordionItemText( - props: BlockAccordionItemTextFragment & { - id: number; +const accordionTheme: CustomFlowbiteTheme['accordion'] = { + root: { + base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', + flush: { + off: 'border-b', + on: 'border-b', + }, }, -) { + content: { + base: 'p-2 m-0 text-gray-200 dark:bg-gray-900', + }, + title: { + arrow: { + base: 'h-0 w-0', + }, + base: 'flex w-full items-center justify-between p-5 text-left font-medium text-gray-500 dark:text-gray-400', + flush: { + off: 'hover:bg-gray-100 dark:hover:bg-gray-800 dark:focus:ring-gray-800', + on: 'bg-transparent dark:bg-transparent', + }, + heading: '', + open: { + off: '', + on: 'text-gray-900 dark:text-gray-100', + }, + }, +}; + +// Applying the custom theme to the Accordion component +// doesn't work out, wrapping it in a Flowbite component. +const theme: CustomFlowbiteTheme = { + accordion: accordionTheme, +}; + +export function BlockAccordion(props: BlockAccordionFragment) { return ( - <> -

    - -

    -
    -
    - {props.textContent?.markup && } -
    -
    - + + + {props.items.map((item, index) => ( + + + + {item.icon && } {item.title} + + + + {item.textContent?.markup && ( + + )} + + + ))} + + ); } function AccordionIcon({ icon }: { icon: string }) { switch (icon) { case 'questionmark': - return ; + return ( + + ); case 'checkmark': - return ; + return ( + + ); case 'arrow': - return ; + return ( + + ); default: return null; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf1905123..11ead8bff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -301,6 +301,9 @@ importers: '@custom/ui': specifier: workspace:* version: link:../../packages/ui + flowbite-react: + specifier: ^0.9.0 + version: 0.9.0(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.3) gatsby: specifier: ^5.13.1 version: 5.13.1(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) @@ -4158,14 +4161,12 @@ packages: resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} dependencies: '@floating-ui/utils': 0.2.1 - dev: true /@floating-ui/dom@1.6.3: resolution: {integrity: sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==} dependencies: '@floating-ui/core': 1.6.0 '@floating-ui/utils': 0.2.1 - dev: true /@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==} @@ -4176,11 +4177,22 @@ packages: '@floating-ui/dom': 1.6.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true + + /@floating-ui/react@0.26.10(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sh6f9gVvWQdEzLObrWbJ97c0clJObiALsFe0LiR/kb3tDRKwEhObASEH2QyfdoO/ZBPzwxa9j+nYFo+sqgbioA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/utils': 0.2.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tabbable: 6.2.0 + dev: false /@floating-ui/utils@0.2.1: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} - dev: true /@formatjs/cli@6.2.4: resolution: {integrity: sha512-g1o9O143F5TGB55skib3fKbyjifPa9YoDcX9L07hVJocRKngCcu4JhKViyUSN55KGcN2ttfBomM+wihN6wtBSQ==} @@ -7273,6 +7285,10 @@ packages: resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} dev: true + /@popperjs/core@2.11.8: + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + dev: false + /@radix-ui/number@1.0.1: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} dependencies: @@ -10374,6 +10390,7 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/parser@6.17.0(eslint@7.0.0)(typescript@5.3.3): resolution: {integrity: sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==} @@ -10687,6 +10704,7 @@ packages: typescript: 5.4.4 transitivePeerDependencies: - supports-color + dev: false /@typescript-eslint/typescript-estree@6.17.0(typescript@5.3.3): resolution: {integrity: sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==} @@ -12001,6 +12019,7 @@ packages: /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} + requiresBuild: true dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 @@ -13793,6 +13812,10 @@ packages: static-extend: 0.1.2 dev: false + /classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + dev: false + /clean-deep@3.4.0: resolution: {integrity: sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==} engines: {node: '>=4'} @@ -13805,6 +13828,7 @@ packages: /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + requiresBuild: true /clean-stack@4.2.0: resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} @@ -14981,6 +15005,11 @@ packages: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} dev: true + /debounce@2.0.0: + resolution: {integrity: sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==} + engines: {node: '>=18'} + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -16438,6 +16467,7 @@ packages: /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + requiresBuild: true dependencies: iconv-lite: 0.6.3 @@ -17095,7 +17125,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@4.9.5) debug: 3.2.7 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 @@ -17202,7 +17232,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.4.4) + '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@4.9.5) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -18577,6 +18607,32 @@ packages: engines: {node: '>=0.4.0'} dev: true + /flowbite-react@0.9.0(react-dom@18.2.0)(react@18.2.0)(tailwindcss@3.4.3): + resolution: {integrity: sha512-wRGzTPHaEuRSXiAFhdTuksezABE/AjI/iyOOBGZpsFAz/sq7zuorAqjRud9FWgy3TlFPtldl7kL93wNY2nOnKQ==} + peerDependencies: + react: '>=18' + react-dom: '>=18' + tailwindcss: ^3 + dependencies: + '@floating-ui/core': 1.6.0 + '@floating-ui/react': 0.26.10(react-dom@18.2.0)(react@18.2.0) + classnames: 2.5.1 + debounce: 2.0.0 + flowbite: 2.3.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-icons: 5.0.1(react@18.2.0) + tailwind-merge: 2.2.2 + tailwindcss: 3.4.3 + dev: false + + /flowbite@2.3.0: + resolution: {integrity: sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==} + dependencies: + '@popperjs/core': 2.11.8 + mini-svg-data-uri: 1.4.4 + dev: false + /flush-write-stream@2.0.0: resolution: {integrity: sha512-uXClqPxT4xW0lcdSBheb2ObVU+kuqUk3Jk64EwieirEXZx9XUrVwp/JuBfKAWaM4T5Td/VL7QLDWPXp/MvGm/g==} dependencies: @@ -24707,7 +24763,6 @@ packages: /mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - dev: true /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -25993,6 +26048,7 @@ packages: /p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} + requiresBuild: true dependencies: aggregate-error: 3.1.0 @@ -26713,7 +26769,6 @@ packages: read-cache: 1.0.0 resolve: 1.22.8 dev: false - optional: true /postcss-import@16.0.0(postcss@8.4.32): resolution: {integrity: sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==} @@ -26746,7 +26801,6 @@ packages: camelcase-css: 2.0.1 postcss: 8.4.38 dev: false - optional: true /postcss-load-config@4.0.2(postcss@8.4.32): resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} @@ -26779,7 +26833,7 @@ packages: dependencies: lilconfig: 3.1.1 postcss: 8.4.38 - yaml: 2.3.4 + yaml: 2.4.1 /postcss-load-config@5.0.3(postcss@8.4.32): resolution: {integrity: sha512-90pBBI5apUVruIEdCxZic93Wm+i9fTrp7TXbgdUCH+/L+2WnfpITSpq5dFU/IPvbv7aNiMlQISpUkAm3fEcvgQ==} @@ -26997,7 +27051,6 @@ packages: postcss: 8.4.38 postcss-selector-parser: 6.0.16 dev: false - optional: true /postcss-normalize-charset@5.1.0(postcss@8.4.38): resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} @@ -28144,6 +28197,14 @@ packages: source-map: 0.7.4 dev: false + /react-icons@5.0.1(react@18.2.0): + resolution: {integrity: sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==} + peerDependencies: + react: '*' + dependencies: + react: 18.2.0 + dev: false + /react-immutable-proptypes@2.2.0(immutable@3.8.2): resolution: {integrity: sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==} peerDependencies: @@ -30331,6 +30392,7 @@ packages: /sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + requiresBuild: true /sqlite3@5.1.7: resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} @@ -30903,6 +30965,10 @@ packages: resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} engines: {node: '>=18'} + /tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + dev: false + /table@5.4.6: resolution: {integrity: sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==} engines: {node: '>=6.0.0'} @@ -30936,6 +31002,12 @@ packages: - supports-color dev: false + /tailwind-merge@2.2.2: + resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==} + dependencies: + '@babel/runtime': 7.24.4 + dev: false + /tailwindcss@3.4.0: resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} engines: {node: '>=14.0.0'} @@ -30997,7 +31069,6 @@ packages: transitivePeerDependencies: - ts-node dev: false - optional: true /tannin@1.2.0: resolution: {integrity: sha512-U7GgX/RcSeUETbV7gYgoz8PD7Ni4y95pgIP/Z6ayI3CfhSujwKEBlGFTCRN+Aqnuyf4AN2yHL+L8x+TCGjb9uA==} @@ -31657,6 +31728,7 @@ packages: dependencies: tslib: 1.14.1 typescript: 5.4.4 + dev: false /tsx@4.7.1: resolution: {integrity: sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==} From 37ea34fc276169e5b5cc707817a422b8dff2cbb2 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 20:00:05 +0200 Subject: [PATCH 166/221] refactor(SLB-301): styling --- .../Organisms/PageContent/BlockAccordion.tsx | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index d9838dce7..da90274c1 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -1,13 +1,21 @@ -import { BlockAccordionFragment } from '@custom/schema'; +import { BlockAccordionFragment, Html } from '@custom/schema'; import { ArrowRightCircleIcon, CheckCircleIcon, QuestionMarkCircleIcon, } from '@heroicons/react/20/solid'; +import clsx from 'clsx'; import { Accordion, CustomFlowbiteTheme, Flowbite } from 'flowbite-react'; -import React from 'react'; +import type { Element } from 'hast'; +import { selectAll } from 'hast-util-select'; +import React, { PropsWithChildren } from 'react'; +import { Plugin } from 'unified'; -import { BlockMarkup } from './BlockMarkup'; +const unorderedItems: Plugin<[], Element> = () => (tree) => { + selectAll('ul > li', tree).forEach((node) => { + node.properties!.unordered = true; + }); +}; const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { @@ -18,7 +26,7 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { }, }, content: { - base: 'p-2 m-0 text-gray-200 dark:bg-gray-900', + base: 'pb-5 pt-5 text-base font-normal text-gray-500 dark:bg-gray-900 dark:text-gray-100', }, title: { arrow: { @@ -56,7 +64,32 @@ export function BlockAccordion(props: BlockAccordionFragment) { {item.textContent?.markup && ( - + ) => { + return ( +
  • + {children} +
  • + ); + }, + }} + markup={item.textContent.markup} + /> )}
    From 5f20f773411758dfe012b4864e8d6728abfc1e65 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 20:52:51 +0200 Subject: [PATCH 167/221] refactor(SLB-301): styling --- .../ui/src/components/Organisms/PageContent/BlockAccordion.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index da90274c1..0d357e500 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -26,7 +26,7 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { }, }, content: { - base: 'pb-5 pt-5 text-base font-normal text-gray-500 dark:bg-gray-900 dark:text-gray-100', + base: 'pb-5 pt-5 text-base font-light text-gray-500 dark:bg-gray-900 dark:text-gray-100', }, title: { arrow: { From 3122dfdca9cffe5be15b554ff48dc0c20f541e50 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 3 May 2024 21:13:27 +0200 Subject: [PATCH 168/221] refactor(SLB-301): styling --- .../Organisms/PageContent/BlockAccordion.stories.tsx | 2 +- .../components/Organisms/PageContent/BlockAccordion.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx index 867eaef1b..6f7be4e00 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx @@ -29,7 +29,7 @@ export const AccordionItemText = { textContent: { markup: `

    Rösti is a kind of fried potato cake served as a main course or side dish.

    -

    As a main dish, rösti is usually accompanied with cheese, onions and cold meat or eggs. This dish, originally from Zürich, was first simply made by frying grated raw potatoes in a pan. It has then spread towards Bern where it is made with boiled potatoes instead. This is where it took the name Rösti.[20] There are many variants in Switzerland and outside the borders.[21] This culinary specialty gives its name to the röstigraben, which designates the cultural differences between the German- and French-speaking parts of the country.

    +

    As a main dish, rösti is usually accompanied with cheese, onions and cold meat or eggs. This dish, originally from Zürich, was first simply made by frying grated raw potatoes in a pan. It has then spread towards Bern where it is made with boiled potatoes instead. This is where it took the name Rösti. There are many variants in Switzerland and outside the borders. This culinary specialty gives its name to the röstigraben, which designates the cultural differences between the German- and French-speaking parts of the country.

    ` as Markup, }, }, diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 0d357e500..c53495d9a 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -21,8 +21,8 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', flush: { - off: 'border-b', - on: 'border-b', + off: 'border-b last:border-0', + on: 'border-b last:border-0', }, }, content: { @@ -32,7 +32,7 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { arrow: { base: 'h-0 w-0', }, - base: 'flex w-full items-center justify-between p-5 text-left font-medium text-gray-500 dark:text-gray-400', + base: 'flex w-full items-center justify-between p-4 pl-1 text-left font-normal text-lg text-gray-500 dark:text-gray-400', flush: { off: 'hover:bg-gray-100 dark:hover:bg-gray-800 dark:focus:ring-gray-800', on: 'bg-transparent dark:bg-transparent', @@ -62,7 +62,7 @@ export function BlockAccordion(props: BlockAccordionFragment) { {item.icon && } {item.title} - + {item.textContent?.markup && ( Date: Fri, 3 May 2024 22:11:41 +0200 Subject: [PATCH 169/221] fix(SLB-301): adjust width --- .../Organisms/PageContent/BlockAccordion.tsx | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index c53495d9a..b1434696f 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -63,34 +63,36 @@ export function BlockAccordion(props: BlockAccordionFragment) { - {item.textContent?.markup && ( - ) => { - return ( -
  • - {children} -
  • - ); - }, - }} - markup={item.textContent.markup} - /> - )} +
    + {item.textContent?.markup && ( + ) => { + return ( +
  • + {children} +
  • + ); + }, + }} + markup={item.textContent.markup} + /> + )} +
    ))} From cb1455c037f2b6ae4c50dc5f6ea3d80e30591f15 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Mon, 6 May 2024 10:03:08 +0200 Subject: [PATCH 170/221] feat(SLB-299): adjust accordion style, add to page story --- .../src/components/Organisms/PageContent/BlockAccordion.tsx | 4 ++-- packages/ui/src/components/Routes/Page.stories.tsx | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index b1434696f..5f4e19453 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -19,7 +19,7 @@ const unorderedItems: Plugin<[], Element> = () => (tree) => { const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { - base: 'divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', + base: 'mt-10 divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', flush: { off: 'border-b last:border-0', on: 'border-b last:border-0', @@ -63,7 +63,7 @@ export function BlockAccordion(props: BlockAccordionFragment) { -
    +
    {item.textContent?.markup && ( ['content'], }, }, From 804a02b5b6cdb57670c954f4ca5c9c87eeccb813 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Tue, 7 May 2024 21:45:22 +0200 Subject: [PATCH 171/221] fix(SLB-299): last border --- .../src/components/Organisms/PageContent/BlockAccordion.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 5f4e19453..9838e91a3 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -21,8 +21,8 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { base: 'mt-10 divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', flush: { - off: 'border-b last:border-0', - on: 'border-b last:border-0', + off: 'last:border-0', + on: 'last:border-0', }, }, content: { From 742714286fa7f5ac35fe8b68f2009626b5e9bd30 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Mon, 13 May 2024 17:09:37 +0200 Subject: [PATCH 172/221] fix(SLB-364): remove dark mode --- .../Organisms/PageContent/BlockAccordion.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index 9838e91a3..eb485c935 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -19,28 +19,28 @@ const unorderedItems: Plugin<[], Element> = () => (tree) => { const accordionTheme: CustomFlowbiteTheme['accordion'] = { root: { - base: 'mt-10 divide-y divide-gray-200 border-gray-200 dark:divide-gray-700 dark:border-gray-700', + base: 'mt-10 divide-y divide-gray-200 border-gray-200', flush: { off: 'last:border-0', on: 'last:border-0', }, }, content: { - base: 'pb-5 pt-5 text-base font-light text-gray-500 dark:bg-gray-900 dark:text-gray-100', + base: 'pb-5 pt-5 text-base font-light text-gray-500', }, title: { arrow: { base: 'h-0 w-0', }, - base: 'flex w-full items-center justify-between p-4 pl-1 text-left font-normal text-lg text-gray-500 dark:text-gray-400', + base: 'flex w-full items-center justify-between p-4 pl-1 text-left font-normal text-lg text-gray-500', flush: { - off: 'hover:bg-gray-100 dark:hover:bg-gray-800 dark:focus:ring-gray-800', - on: 'bg-transparent dark:bg-transparent', + off: 'hover:bg-gray-100', + on: 'bg-transparent', }, heading: '', open: { off: '', - on: 'text-gray-900 dark:text-gray-100', + on: 'text-gray-900', }, }, }; From a543def08ce91aa05270d5a007d6cf5b2c8a011c Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Mon, 13 May 2024 17:11:53 +0200 Subject: [PATCH 173/221] refactor(SLB-365): add arrows back --- .../ui/src/components/Organisms/PageContent/BlockAccordion.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx index eb485c935..b6282c10d 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.tsx @@ -29,9 +29,6 @@ const accordionTheme: CustomFlowbiteTheme['accordion'] = { base: 'pb-5 pt-5 text-base font-light text-gray-500', }, title: { - arrow: { - base: 'h-0 w-0', - }, base: 'flex w-full items-center justify-between p-4 pl-1 text-left font-normal text-lg text-gray-500', flush: { off: 'hover:bg-gray-100', From e87e3544eefda0d7fd6798977a5c02f5d3928b8d Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Mon, 13 May 2024 17:32:05 +0200 Subject: [PATCH 174/221] test(SLB-299): use more content --- .../Organisms/PageContent/BlockAccordion.stories.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx index 6f7be4e00..d5c5b8b34 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockAccordion.stories.tsx @@ -15,7 +15,8 @@ export const AccordionItemText = { icon: '', textContent: { markup: ` -

    The earliest known recipe for the modern form of cheese fondue comes from a 1699 book published in Zürich, under the name "Käss mit Wein zu kochen" 'to cook cheese with wine'. It calls for grated or cut-up cheese to be melted with wine, and for bread to be dipped in it.

    +

    The earliest known recipe for the modern form of cheese fondue comes from a 1699 book published in Zürich, under the name "Käss mit Wein zu kochen" 'to cook cheese with wine'.

    +

    It calls for grated or cut-up cheese to be melted with wine, and for bread to be dipped in it. However, the name "cheese fondue", until the late 19th century, referred to a dish composed of eggs and cheese, as in la Chapelle's 1735 Fonduë de Fromage, aux Truffes Fraiches; it was something between scrambled eggs with cheese and a cheese soufflé. Brillat-Savarin wrote in 1834 that it is "nothing other than scrambled eggs with cheese". Variations included cream ("à la genevoise") and truffles ("à la piémontaise") in addition to eggs, as well as what is now called "raclette" ("fondue valaisanne").