+ {intl.formatMessage({
+ defaultMessage:
+ 'The moderation state transition form cannot be displayed because the content failed to load.',
+ id: 'mFbroE',
+ })}
+
+
+ );
+ }
+
+ return (
+
+
+
+ {showForm && (
+
+
+
+ )}
+
+
+ );
+}
diff --git a/packages/drupal/custom/custom.info.yml b/packages/drupal/custom/custom.info.yml
index e74400ef9..d1a126fe3 100644
--- a/packages/drupal/custom/custom.info.yml
+++ b/packages/drupal/custom/custom.info.yml
@@ -7,6 +7,8 @@ dependencies:
- simple_oauth:simple_oauth
- consumers:consumers
- drupal:serialization
+ - drupal:content_moderation
+ - silverback_preview_link:silverback_preview_link
'interface translation project': custom
'interface translation server pattern': modules/custom/custom/translations/%language.po
diff --git a/packages/drupal/custom/custom.services.yml b/packages/drupal/custom/custom.services.yml
index 74980bc9f..42ad20137 100644
--- a/packages/drupal/custom/custom.services.yml
+++ b/packages/drupal/custom/custom.services.yml
@@ -11,6 +11,16 @@ services:
custom.menus:
class: Drupal\custom\Menus
+ custom.content_moderation:
+ class: Drupal\custom\ContentModeration
+ arguments:
+ - '@content_moderation.moderation_information'
+ - '@content_moderation.state_transition_validation'
+ - '@current_user'
+ - '@access_check.silverback_preview_link'
+ - '@account_switcher'
+ - '@datetime.time'
+
custom.entity_language_redirect_subscriber:
class: Drupal\custom\EventSubscriber\EntityLanguageRedirectSubscriber
arguments: ['@language_manager', '@current_route_match']
diff --git a/packages/drupal/custom/src/ContentModeration.php b/packages/drupal/custom/src/ContentModeration.php
new file mode 100644
index 000000000..c30bb49ab
--- /dev/null
+++ b/packages/drupal/custom/src/ContentModeration.php
@@ -0,0 +1,272 @@
+moderationInformation = $moderation_information;
+ $this->stateValidator = $state_validator;
+ $this->currentUser = $current_user;
+ $this->previewLinkAccessCheck = $preview_link_access_check;
+ $this->accountSwitcher = $account_switcher;
+ $this->time = $time;
+ }
+
+ /**
+ * Directive to moderate content.
+ */
+ public function moderateContent(DirectiveArguments $args) : array {
+ $userSessionChanged = FALSE;
+ try {
+ // For the moderateContent action, the access credentials should be inside
+ // the directive arguments.
+ // @todo: this might need a bigger refactoring, so that the request gets
+ // authenticated based on the preview_user_id and preview_access_token
+ // globally, so we do not need to perform the authorisation in this
+ // method.
+ $previewUserId = $args->args['accessCredentials']['previewUserId'];
+ $previewAccessToken = $args->args['accessCredentials']['previewAccessToken'];
+ /* @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+ $storage = \Drupal::entityTypeManager()
+ ->getStorage($args->args['entityType']);
+ if (!$storage instanceof RevisionableStorageInterface) {
+ throw new \Exception("The " . $args->args['entityType'] . ' entity storage is not revisionable.');
+ }
+ $content = $storage->loadRevision($args->args['revisionId']);
+ if (!empty($previewAccessToken) && !empty($previewUserId) && $this->currentUser->id() != $previewUserId) {
+ $accessResult = $this->previewLinkAccessCheck->access($content, $previewAccessToken);
+ // If the access token is valid, then we should switch to preview user
+ // as well.
+ if ($accessResult->isAllowed()) {
+ $this->accountSwitcher->switchTo(new UserSession(['uid' => $previewUserId]));
+ $userSessionChanged = TRUE;
+ }
+ }
+ $submittedData = json_decode($args->args['submittedData'], true);
+ // Check again if the transition is allowed.
+ $workflow = $this->moderationInformation->getWorkflowForEntity($content);
+ $currentState = $this->moderationInformation->getOriginalState($content);
+ $newState = $workflow->getTypePlugin()->getState($submittedData['new_moderation_state']);
+ if (!$this->stateValidator->isTransitionValid($workflow, $currentState, $newState, $this->currentUser, $content)) {
+ throw new \Exception("The moderation state could not be changed. Transition from " .$currentState->id() . ' to ' . $newState->id() . ' is not allowed (' . $args->args['entityType'] . '; ' . $args->args['contentId'] . ') for user id: ' .$this->currentUser->id());
+ }
+
+ // If we get here, then we are allowed to change the moderation state, so
+ // we try to do it.
+ $this->changeModerationStateForContent($content, $submittedData['new_moderation_state'], $submittedData['comment'], $submittedData['name']);
+ return [
+ 'result' => 'success',
+ 'errors' => NULL,
+ ];
+ }
+ catch (\Exception $e) {
+ Error::logException(\Drupal::logger('error'), $e);
+ return [
+ 'result' => 'error',
+ 'errors' => [
+ [
+ 'message' => 'The moderation state could not be changed. Check the application logs for more details.',
+ 'key' => 'invalid_input'
+ ]
+ ],
+ ];
+ }
+ finally {
+ // Make sure we switch back to the original session.
+ if ($userSessionChanged) {
+ $this->accountSwitcher->switchBack();
+ }
+ }
+ }
+
+ /**
+ * Directive to get the moderation information for content.
+ * @param \Drupal\graphql_directives\DirectiveArguments $args
+ *
+ * @return array
+ */
+ public function moderationInfo(DirectiveArguments $args): array | null{
+ $content = $args->value;
+ if (!$content instanceof ContentEntityInterface) {
+ return null;
+ }
+ if (!$this->moderationInformation->isModeratedEntity($content)) {
+ return null;
+ }
+ $userSessionChanged = FALSE;
+ try {
+ // @todo: maybe the preview_user_id and preview_access_token should be
+ // used as an authorisation method, globally, instead of needing to check
+ // them on each individual feature. In that case we would not need to
+ // use the account switcher service and to temporarily switch the current
+ // user session to the one specified by preview_user_id.
+ $accessCredentials = $this->getAccessCredentialsFromRequest();
+ $previewUserId = $accessCredentials['preview_user_id'];
+ $previewAccessToken = $accessCredentials['preview_access_token'];
+ if (!empty($previewAccessToken) && !empty($previewUserId) && $this->currentUser->id() != $previewUserId) {
+ $accessResult = $this->previewLinkAccessCheck->access($content, $previewAccessToken);
+ // If the access token is valid, then we should switch to preview user
+ // as well.
+ if ($accessResult->isAllowed()) {
+ $this->accountSwitcher->switchTo(new UserSession(['uid' => $previewUserId]));
+ $userSessionChanged = TRUE;
+ }
+ }
+
+ $availableStates = $this->getAvailableStatesForContent($content);
+ $currentState = $this->moderationInformation->getOriginalState($content);
+ } finally {
+ // Make sure we switch back to the original session.
+ if ($userSessionChanged) {
+ $this->accountSwitcher->switchBack();
+ }
+ }
+ return [
+ 'currentState' => [
+ 'id' => $currentState->id(),
+ 'label' => $currentState->label(),
+ ],
+ 'availableStates' => $availableStates,
+ ];
+ }
+
+ /**
+ * Helper method to return an array with all the available states that a
+ * content can transition to.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $content
+ *
+ * @return array
+ */
+ protected function getAvailableStatesForContent(ContentEntityInterface $content): array {
+ $transitions = $this->stateValidator->getValidTransitions($content, $this->currentUser);
+ $availableStates = [];
+ foreach ($transitions as $transition) {
+ $transitionTo = $transition->to();
+ $availableStates[] = [
+ 'id' => $transitionTo->id(),
+ 'label' => $transitionTo->label(),
+ ];
+ }
+
+ return $availableStates;
+ }
+
+ /**
+ * Helper method to return the preview access credentials, if they are part of
+ * a request.
+ *
+ * @return array
+ */
+ protected function getAccessCredentialsFromRequest(): array {
+ // If we have a preview_access_token and a preview_user_id, then we need to
+ // get the available transitions in the context of that user.
+ $request = \Drupal::request();
+ $previewAccessToken = $request->get('preview_access_token');
+ $previewUserId = $request->get('preview_user_id');
+ // When running in the context of a graphql request, the preview_user_id and
+ // the preview_access_token should be available in the variables of the
+ // query.
+ if ($request->get('queryId') && $request->get('variables')) {
+ $variablesValues = json_decode($request->get('variables'), true);
+ $previewAccessToken = !empty($previewAccessToken) ? $previewAccessToken : $variablesValues['preview_access_token'];
+ $previewUserId = !empty($previewUserId) ? $previewUserId : $variablesValues['preview_user_id'];
+ }
+ return [
+ 'preview_access_token' => $previewAccessToken,
+ 'preview_user_id' => $previewUserId,
+ ];
+ }
+
+ /**
+ * Helper method to change the moderation state of a cnotent.
+ *
+ * @param \Drupal\Core\Entity\RevisionableInterface $content
+ * @param string $new_state
+ * @param string $comment
+ * @param string $author_name
+ *
+ * @return void
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ protected function changeModerationStateForContent(RevisionableInterface $content, string $new_state, string $comment, string $author_name) {
+ /* @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+ $storage = \Drupal::entityTypeManager()->getStorage($content->getEntityTypeId());
+ $content = $storage->createRevision($content, $content->isDefaultRevision());
+ $content->set('moderation_state', $new_state);
+ if ($content instanceof RevisionLogInterface) {
+ $content->setRevisionCreationTime($this->time->getRequestTime());
+ $content->setRevisionLogMessage($comment . ' (' . $author_name . ')');
+ $content->setRevisionUserId($this->currentUser->id());
+ }
+ $content->save();
+ }
+}
diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql
index ddaeed564..e41654309 100644
--- a/packages/schema/src/fragments/Page.gql
+++ b/packages/schema/src/fragments/Page.gql
@@ -57,4 +57,14 @@ fragment Page on Page {
href
}
}
+ moderationInfo {
+ currentState {
+ id
+ label
+ }
+ availableStates {
+ id
+ label
+ }
+ }
}
diff --git a/packages/schema/src/operations/ModerateContent.gql b/packages/schema/src/operations/ModerateContent.gql
new file mode 100644
index 000000000..934f2f103
--- /dev/null
+++ b/packages/schema/src/operations/ModerateContent.gql
@@ -0,0 +1,22 @@
+mutation ModerateContent(
+ $contentId: String!
+ $entityType: String!
+ $submittedData: JSONString!
+ $revisionId: String!
+ $accessCredentials: AccessCredentialsInput
+) {
+ moderateContent(
+ contentId: $contentId
+ entityType: $entityType
+ revisionId: $revisionId
+ submittedData: $submittedData
+ accessCredentials: $accessCredentials
+ ) {
+ errors {
+ key
+ field
+ message
+ }
+ result
+ }
+}
diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql
index e28a7e697..bfd44a88e 100644
--- a/packages/schema/src/schema.graphql
+++ b/packages/schema/src/schema.graphql
@@ -21,6 +21,22 @@ directive @createWebformSubmission(
submittedData: JSONString!
) on FIELD_DEFINITION
+"""
+implementation(drupal): custom.content_moderation::moderateContent
+"""
+directive @moderateContent(
+ contentId: String!
+ entityType: String!
+ revisionId: String!
+ submittedData: JSONString!
+ accessCredentials: AccessCredentialsInput
+) on FIELD_DEFINITION
+
+"""
+implementation(drupal): custom.content_moderation::moderationInfo
+"""
+directive @resolveModerationInfo on FIELD_DEFINITION
+
"""
Retrieve the properties of an image.
TODO: Move to a shared "image" package.
@@ -152,6 +168,7 @@ interface Page implements CardItem & Editable @resolveEntityBundle {
hero: Hero
content: [PageContent]
metaTags: [MetaTag]
+ moderationInfo: ModerationInformation
}
type DecapPage implements Page & Editable & CardItem
@@ -166,6 +183,7 @@ type DecapPage implements Page & Editable & CardItem
hero: Hero
content: [PageContent]
metaTags: [MetaTag]
+ moderationInfo: ModerationInformation
}
"""
@@ -201,6 +219,7 @@ type DrupalPage implements Page & Editable & CardItem
@seek(pos: 1)
@resolveEditorBlockChildren
metaTags: [MetaTag] @resolveProperty(path: "metatag")
+ moderationInfo: ModerationInformation @resolveModerationInfo
}
type Hero {
@@ -462,6 +481,26 @@ type Mutation {
webformId: "$webformId"
submittedData: "$submittedData"
)
+
+ moderateContent(
+ contentId: String!
+ entityType: String!
+ revisionId: String!
+ submittedData: JSONString!
+ accessCredentials: AccessCredentialsInput
+ ): ModerateContentMutationResponse
+ @moderateContent(
+ contentId: "$contentId"
+ entityType: "$entityType"
+ revisionId: "$revisionId"
+ submittedData: "$submittedData"
+ accessCredentials: "$accessCredentials"
+ )
+}
+
+input AccessCredentialsInput {
+ previewUserId: String!
+ previewAccessToken: String!
}
type WebformSubmissionCreateResponse {
@@ -469,6 +508,11 @@ type WebformSubmissionCreateResponse {
submission: JSONString
}
+type ModerateContentMutationResponse {
+ errors: [MutationError]
+ result: JSONString
+}
+
type MutationError {
message: String!
key: String!
@@ -488,6 +532,16 @@ type MetaTagAttributes @default @value(json: "{}") {
href: String
}
+type ModerationInformation {
+ currentState: ModerationState
+ availableStates: [ModerationState]
+}
+
+type ModerationState {
+ id: ID
+ label: String
+}
+
type DemoBlock {
heading: String!
description: Markup
diff --git a/packages/ui/package.json b/packages/ui/package.json
index add3f6d0d..0cf1eb264 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -10,6 +10,9 @@
"./routes/*": [
"./build/components/Routes/*.js"
],
+ "./operations": [
+ "./build/utils/operation.js"
+ ],
"./styles.css": [
"./build/styles.css"
]
@@ -39,7 +42,7 @@
"report": "mkdir -p coverage/storybook && nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook"
},
"dependencies": {
- "@amazeelabs/react-intl": "^1.1.3",
+ "@amazeelabs/react-intl": "^1.1.4",
"@amazeelabs/silverback-iframe": "^1.3.8",
"@custom/schema": "workspace:*",
"@headlessui/react": "^2.2.0",
diff --git a/packages/ui/src/components/Routes/Preview.tsx b/packages/ui/src/components/Routes/Preview.tsx
index 1eb29dda4..6a7d89871 100644
--- a/packages/ui/src/components/Routes/Preview.tsx
+++ b/packages/ui/src/components/Routes/Preview.tsx
@@ -9,7 +9,7 @@ import { Loading } from '../Molecules/Loading';
import { Messages } from '../Molecules/Messages';
import { PageDisplay } from '../Organisms/PageDisplay';
-function usePreviewParameters(): OperationVariables<
+export function usePreviewParameters(): OperationVariables<
typeof PreviewDrupalPageQuery
> {
const [location] = useLocation();
@@ -53,7 +53,6 @@ export function Preview() {
PreviewDrupalPageQuery,
usePreviewParameters(),
);
-
const intl = useIntl();
// @todo forward error from the backend.
// @todo 403 status code.
diff --git a/packages/ui/src/tailwind.config.cjs b/packages/ui/src/tailwind.config.cjs
index 7dae08d4c..df59d65b3 100644
--- a/packages/ui/src/tailwind.config.cjs
+++ b/packages/ui/src/tailwind.config.cjs
@@ -4,7 +4,10 @@
const stylingAssets = require('./stylingAssets.json');
module.exports = {
- content: ['./src/**/*.{tsx, mdx}'],
+ content: [
+ './src/**/*.{js,jsx,ts,tsx}',
+ '../../apps/preview/src/**/*.{js,jsx,ts,tsx}',
+ ],
theme: {
extend: {
typography: ({ theme }) => ({
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 41981eda7..ff5fce78f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -155,6 +155,9 @@ importers:
apps/preview:
dependencies:
+ '@amazeelabs/react-intl':
+ specifier: ^1.1.4
+ version: 1.1.4(react-dom@18.2.0)(react@18.3.1)(typescript@5.7.3)(webpack@5.91.0)
'@custom/eslint-config':
specifier: workspace:*
version: link:../../packages/eslint-config
@@ -186,11 +189,14 @@ importers:
specifier: ^3.3.2
version: 3.3.2
react:
- specifier: ^18
+ specifier: ^18.2.0
version: 18.3.1
react-dom:
- specifier: ^18
+ specifier: ^18.2.0
version: 18.2.0(react@18.3.1)
+ react-hook-form:
+ specifier: ^7.49.2
+ version: 7.54.2(react@18.3.1)
rxjs:
specifier: ^7.8.1
version: 7.8.1
@@ -607,8 +613,8 @@ importers:
packages/ui:
dependencies:
'@amazeelabs/react-intl':
- specifier: ^1.1.3
- version: 1.1.3(react-dom@18.2.0)(react@18.3.1)(typescript@5.7.3)(webpack@5.91.0)
+ specifier: ^1.1.4
+ version: 1.1.4(react-dom@18.2.0)(react@18.3.1)(typescript@5.7.3)(webpack@5.91.0)
'@amazeelabs/silverback-iframe':
specifier: ^1.3.8
version: 1.3.8(@babel/core@7.26.7)(prop-types@15.8.1)(react-dom@18.2.0)(react@18.3.1)
@@ -1121,8 +1127,8 @@ packages:
- utf-8-validate
dev: false
- /@amazeelabs/react-intl@1.1.3(react-dom@18.2.0)(react@18.3.1)(typescript@5.7.3)(webpack@5.91.0):
- resolution: {integrity: sha512-J2QaPIEfzoc7AZFXUaHAE+dl/kFssYud/vu/Lp9Py3rBoeUvW3/5AeQaL5rN8MEEbCf+XtWI5Imooxjze9JSsA==}
+ /@amazeelabs/react-intl@1.1.4(react-dom@18.2.0)(react@18.3.1)(typescript@5.7.3)(webpack@5.91.0):
+ resolution: {integrity: sha512-3Q6MWnrFRARtV0FgUNMeK8eaW9eH+pTE8m+38MCY9lDiQUhuX9unJB8bwnWukb3GVY6BLojtBI+l8WzWumyVeA==}
peerDependencies:
react: '*'
react-dom: '*'
@@ -3063,6 +3069,7 @@ packages:
cpu: [ppc64]
os: [aix]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/android-arm64@0.19.11:
@@ -3098,6 +3105,7 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/android-arm@0.19.11:
@@ -3133,6 +3141,7 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/android-x64@0.19.11:
@@ -3168,6 +3177,7 @@ packages:
cpu: [x64]
os: [android]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/darwin-arm64@0.19.11:
@@ -3203,6 +3213,7 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/darwin-x64@0.19.11:
@@ -3238,6 +3249,7 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/freebsd-arm64@0.19.11:
@@ -3273,6 +3285,7 @@ packages:
cpu: [arm64]
os: [freebsd]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/freebsd-x64@0.19.11:
@@ -3308,6 +3321,7 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-arm64@0.19.11:
@@ -3343,6 +3357,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-arm@0.19.11:
@@ -3378,6 +3393,7 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-ia32@0.19.11:
@@ -3413,6 +3429,7 @@ packages:
cpu: [ia32]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-loong64@0.19.11:
@@ -3448,6 +3465,7 @@ packages:
cpu: [loong64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-mips64el@0.19.11:
@@ -3483,6 +3501,7 @@ packages:
cpu: [mips64el]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-ppc64@0.19.11:
@@ -3518,6 +3537,7 @@ packages:
cpu: [ppc64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-riscv64@0.19.11:
@@ -3553,6 +3573,7 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-s390x@0.19.11:
@@ -3588,6 +3609,7 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/linux-x64@0.19.11:
@@ -3623,6 +3645,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/netbsd-arm64@0.24.2:
@@ -3631,6 +3654,7 @@ packages:
cpu: [arm64]
os: [netbsd]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/netbsd-x64@0.19.11:
@@ -3666,6 +3690,7 @@ packages:
cpu: [x64]
os: [netbsd]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/openbsd-arm64@0.23.1:
@@ -3683,6 +3708,7 @@ packages:
cpu: [arm64]
os: [openbsd]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/openbsd-x64@0.19.11:
@@ -3718,6 +3744,7 @@ packages:
cpu: [x64]
os: [openbsd]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/sunos-x64@0.19.11:
@@ -3753,6 +3780,7 @@ packages:
cpu: [x64]
os: [sunos]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/win32-arm64@0.19.11:
@@ -3788,6 +3816,7 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/win32-ia32@0.19.11:
@@ -3823,6 +3852,7 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
+ dev: true
optional: true
/@esbuild/win32-x64@0.19.11:
@@ -3858,6 +3888,7 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
+ dev: true
optional: true
/@eslint-community/eslint-utils@4.4.1(eslint@7.32.0):
@@ -15121,6 +15152,7 @@ packages:
'@esbuild/win32-arm64': 0.24.2
'@esbuild/win32-ia32': 0.24.2
'@esbuild/win32-x64': 0.24.2
+ dev: true
/escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
@@ -25249,7 +25281,7 @@ packages:
neo-async: 2.6.2
react: 18.3.1
react-dom: 18.2.0(react@18.3.1)
- webpack: 5.91.0(@swc/core@1.10.11)(esbuild@0.24.2)
+ webpack: 5.91.0(@swc/core@1.10.11)
webpack-sources: 3.2.3
dev: false
@@ -27787,32 +27819,6 @@ packages:
ps-tree: 1.2.0
dev: false
- /terser-webpack-plugin@5.3.10(@swc/core@1.10.11)(esbuild@0.24.2)(webpack@5.91.0):
- resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
- engines: {node: '>= 10.13.0'}
- peerDependencies:
- '@swc/core': '*'
- esbuild: '*'
- uglify-js: '*'
- webpack: ^5.1.0
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- esbuild:
- optional: true
- uglify-js:
- optional: true
- dependencies:
- '@jridgewell/trace-mapping': 0.3.25
- '@swc/core': 1.10.11
- esbuild: 0.24.2
- jest-worker: 27.5.1
- schema-utils: 3.3.0
- serialize-javascript: 6.0.2
- terser: 5.30.3
- webpack: 5.91.0(@swc/core@1.10.11)(esbuild@0.24.2)
- dev: false
-
/terser-webpack-plugin@5.3.10(@swc/core@1.10.11)(webpack@5.91.0):
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
engines: {node: '>= 10.13.0'}
@@ -29872,46 +29878,6 @@ packages:
- esbuild
- uglify-js
- /webpack@5.91.0(@swc/core@1.10.11)(esbuild@0.24.2):
- resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==}
- engines: {node: '>=10.13.0'}
- hasBin: true
- peerDependencies:
- webpack-cli: '*'
- peerDependenciesMeta:
- webpack-cli:
- optional: true
- dependencies:
- '@types/eslint-scope': 3.7.7
- '@types/estree': 1.0.6
- '@webassemblyjs/ast': 1.12.1
- '@webassemblyjs/wasm-edit': 1.12.1
- '@webassemblyjs/wasm-parser': 1.12.1
- acorn: 8.14.0
- acorn-import-assertions: 1.9.0(acorn@8.14.0)
- browserslist: 4.24.4
- chrome-trace-event: 1.0.3
- enhanced-resolve: 5.16.0
- es-module-lexer: 1.6.0
- eslint-scope: 5.1.1
- events: 3.3.0
- glob-to-regexp: 0.4.1
- graceful-fs: 4.2.11
- json-parse-even-better-errors: 2.3.1
- loader-runner: 4.3.0
- mime-types: 2.1.35
- neo-async: 2.6.2
- schema-utils: 3.3.0
- tapable: 2.2.1
- terser-webpack-plugin: 5.3.10(@swc/core@1.10.11)(esbuild@0.24.2)(webpack@5.91.0)
- watchpack: 2.4.1
- webpack-sources: 3.2.3
- transitivePeerDependencies:
- - '@swc/core'
- - esbuild
- - uglify-js
- dev: false
-
/website-scraper@5.3.1:
resolution: {integrity: sha512-gogqPXD2gVsxoyd2yRiympw3rA5GuEpD1CaDEJ/J8zzanx7hkbTtneoO1SGs436PpLbWVcUge+6APGLhzsuZPA==}
engines: {node: '>=14.14'}