- ∑ns
+ ∑ns
x
an
diff --git a/contentcuration/contentcuration/tests/utils/test_markdown.py b/contentcuration/contentcuration/tests/utils/test_markdown.py
index c111146a85..0088d4a09e 100644
--- a/contentcuration/contentcuration/tests/utils/test_markdown.py
+++ b/contentcuration/contentcuration/tests/utils/test_markdown.py
@@ -120,8 +120,8 @@ def test_mixed_inline_and_block(self):
"\n"
"And this is block math:
\n"
'"
"Back to text with more inline: "
@@ -132,6 +132,62 @@ def test_mixed_inline_and_block(self):
self._assert_conversion(markdown_text, expected)
+ def test_block_sum_uses_munderover(self):
+ """Block-mode \\sum with sub+superscript must use , not """
+
+ markdown_text = "$$\\sum_{i=1}^{n} x_i$$"
+ expected = (
+ '"
+ )
+
+ self._assert_conversion(markdown_text, expected)
+
+ def test_inline_sum_uses_msubsup(self):
+ """Inline-mode \\sum with sub+superscript must use , not """
+
+ markdown_text = "The sum $$\\sum_{i=1}^{n} x_i$$ is finite."
+ expected = (
+ "The sum "
+ ''
+ " is finite.
\n"
+ )
+
+ self._assert_conversion(markdown_text, expected)
+
+ def test_block_prod_uses_munderover(self):
+ """Block-mode \\prod with sub+superscript must use """
+
+ markdown_text = "$$\\prod_{k=0}^{n} a_k$$"
+ expected = (
+ '"
+ )
+
+ self._assert_conversion(markdown_text, expected)
+
+ def test_block_int_unaffected(self):
+ """Block-mode \\int should still use (not affected by munderover fix)"""
+
+ markdown_text = "$$\\int_{a}^{b} f(x) dx$$"
+ expected = (
+ '"
+ )
+
+ self._assert_conversion(markdown_text, expected)
+
def test_no_math_content(self):
"""Test that regular markdown without math still works"""
diff --git a/contentcuration/contentcuration/tests/utils/test_publish.py b/contentcuration/contentcuration/tests/utils/test_publish.py
new file mode 100644
index 0000000000..922a90b1a1
--- /dev/null
+++ b/contentcuration/contentcuration/tests/utils/test_publish.py
@@ -0,0 +1,486 @@
+import os
+import tempfile
+import uuid
+from unittest import mock
+
+from django.conf import settings
+from kolibri_content import models as kolibrimodels
+from kolibri_content.router import using_content_database
+from le_utils.constants import licenses
+
+import contentcuration.models as ccmodels
+from contentcuration.tests import testdata
+from contentcuration.tests.base import StudioTestCase
+from contentcuration.tests.utils.restricted_filesystemstorage import (
+ RestrictedFileSystemStorage,
+)
+from contentcuration.utils.publish import create_content_database
+from contentcuration.utils.publish import create_draft_channel_version
+from contentcuration.utils.publish import ensure_versioned_database_exists
+from contentcuration.utils.publish import increment_channel_version
+from contentcuration.utils.publish import publish_channel
+
+
+class EnsureVersionedDatabaseTestCase(StudioTestCase):
+ def setUp(self):
+ super().setUp()
+
+ self._temp_directory_ctx = tempfile.TemporaryDirectory()
+ self.test_db_root_dir = self._temp_directory_ctx.__enter__()
+
+ storage = RestrictedFileSystemStorage(location=self.test_db_root_dir)
+
+ self._storage_patch_ctx = mock.patch(
+ "contentcuration.utils.publish.storage",
+ new=storage,
+ )
+ self._storage_patch_ctx.__enter__()
+
+ os.makedirs(
+ os.path.join(self.test_db_root_dir, settings.DB_ROOT), exist_ok=True
+ )
+
+ self.channel = testdata.channel()
+ self.channel.version = 2
+ self.channel.save()
+
+ self.versioned_db_path = os.path.join(
+ self.test_db_root_dir,
+ settings.DB_ROOT,
+ f"{self.channel.id}-{self.channel.version}.sqlite3",
+ )
+ self.unversioned_db_path = os.path.join(
+ self.test_db_root_dir, settings.DB_ROOT, f"{self.channel.id}.sqlite3"
+ )
+
+ def tearDown(self):
+ self._temp_directory_ctx.__exit__(None, None, None)
+ self._storage_patch_ctx.__exit__(None, None, None)
+
+ super().tearDown()
+
+ def test_versioned_database_exists(self):
+ # In reality, the versioned database for the current version
+ # and the unversioned database would have the same content,
+ # but here we provide different content so that we can test
+ # that the versioned database is not overwritten.
+ versioned_db_content = "Versioned content"
+ unversioned_db_content = "Unversioned content"
+
+ with open(self.versioned_db_path, "w") as f:
+ f.write(versioned_db_content)
+ with open(self.unversioned_db_path, "w") as f:
+ f.write(unversioned_db_content)
+
+ ensure_versioned_database_exists(self.channel.id, self.channel.version)
+
+ with open(self.versioned_db_path) as f:
+ read_versioned_content = f.read()
+ self.assertEqual(read_versioned_content, versioned_db_content)
+
+ def test_versioned_database_does_not_exist(self):
+ unversioned_db_content = "Unversioned content"
+
+ with open(self.unversioned_db_path, "w") as f:
+ f.write(unversioned_db_content)
+
+ ensure_versioned_database_exists(self.channel.id, self.channel.version)
+
+ with open(self.versioned_db_path) as f:
+ read_versioned_content = f.read()
+ self.assertEqual(read_versioned_content, unversioned_db_content)
+
+ def test_not_published(self):
+ self.channel.version = 0
+ self.channel.save()
+ self.versioned_db_path = os.path.join(
+ self.test_db_root_dir,
+ settings.DB_ROOT,
+ f"{self.channel.id}-{self.channel.version}.sqlite3",
+ )
+
+ with self.assertRaises(ValueError):
+ ensure_versioned_database_exists(self.channel.id, self.channel.version)
+
+
+class IncrementChannelVersionTestCase(StudioTestCase):
+ """Test increment_channel_version function with ChannelVersion integration."""
+
+ def setUp(self):
+ super().setUp()
+ self.channel = testdata.channel()
+ self.channel.version = 0
+ self.channel.save()
+
+ def test_increment_published_version(self):
+ """Test incrementing version for published channel."""
+ initial_version = self.channel.version
+
+ increment_channel_version(self.channel)
+
+ self.channel.refresh_from_db()
+
+ self.assertEqual(self.channel.version, initial_version + 1)
+
+ self.assertIsNotNone(self.channel.version_info)
+ self.assertEqual(self.channel.version_info.version, self.channel.version)
+ self.assertEqual(self.channel.version_info.channel, self.channel)
+
+ self.assertIsNone(self.channel.version_info.secret_token)
+
+ def test_increment_draft_version(self):
+ """Test incrementing version for draft channel."""
+ initial_version = self.channel.version
+
+ channel_version = create_draft_channel_version(self.channel)
+
+ self.channel.refresh_from_db()
+
+ self.assertEqual(self.channel.version, initial_version)
+
+ self.assertIsNotNone(channel_version)
+ self.assertIsNone(channel_version.version)
+ self.assertEqual(channel_version.channel, self.channel)
+
+ if self.channel.version_info:
+ self.assertNotEqual(self.channel.version_info, channel_version)
+
+ self.assertIsNotNone(channel_version.secret_token)
+ self.assertFalse(channel_version.secret_token.is_primary)
+
+ def test_multiple_published_versions(self):
+ """Test creating multiple published versions."""
+ increment_channel_version(self.channel)
+ increment_channel_version(self.channel)
+ increment_channel_version(self.channel)
+
+ self.channel.refresh_from_db()
+
+ self.assertEqual(self.channel.version, 3)
+
+ self.assertEqual(self.channel.channel_versions.count(), 3)
+
+ self.assertIsNotNone(self.channel.version_info)
+ self.assertEqual(self.channel.version_info.version, 3)
+
+ def test_mixed_draft_and_published_versions(self):
+ """Test creating mix of draft and published versions."""
+ increment_channel_version(self.channel)
+ draft1 = create_draft_channel_version(self.channel)
+ draft2 = create_draft_channel_version(self.channel)
+
+ self.channel.refresh_from_db()
+
+ self.assertEqual(self.channel.version, 1)
+
+ self.assertEqual(self.channel.channel_versions.count(), 2)
+ self.assertEqual(uuid.UUID(str(draft1.id)), uuid.UUID(str(draft2.id)))
+
+ self.assertIsNotNone(draft1.secret_token)
+ self.assertIsNotNone(draft2.secret_token)
+
+ self.assertIsNotNone(self.channel.version_info)
+ self.assertEqual(self.channel.version_info.version, 1)
+ self.assertIsNone(self.channel.version_info.secret_token)
+
+
+class DraftPublishChannelTestCase(StudioTestCase):
+ """
+ Tests for the draft publish flow using the full publish_channel function.
+
+ Verifies that draft publishes correctly populate the draft ChannelVersion
+ metadata while leaving channel-level fields untouched, and that
+ special_permissions_included is fully replaced (not accumulated) on each
+ draft publish.
+
+ save_export_database is mocked to avoid file-system operations; all other
+ publish_channel logic runs as normal.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls._patch_save_export = mock.patch(
+ "contentcuration.utils.publish.save_export_database"
+ )
+ cls._patch_save_export.start()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls._patch_save_export.stop()
+ super().tearDownClass()
+
+ def setUp(self):
+ super().setUp()
+ self.channel = testdata.channel()
+
+ # Fetch the Special Permissions license loaded by loadconstants.
+ # Do NOT call create() — loadconstants inserts licenses with explicit PKs
+ # so the PK sequence is still at 1, and create() would collide with id=1.
+ self.special_perms_license = ccmodels.License.objects.get(
+ license_name=licenses.SPECIAL_PERMISSIONS
+ )
+
+ # Give the channel an existing published version so version_info is set.
+ # increment_channel_version calls channel.save(), which triggers
+ # Channel.on_update() (models.py ~line 1392). on_update() runs
+ # ChannelVersion.objects.get_or_create(channel=self, version=self.version)
+ # and assigns the result to self.version_info — so after this call,
+ # channel.version_info is a real ChannelVersion, not None.
+ increment_channel_version(self.channel)
+ self.channel.refresh_from_db()
+
+ # Snapshot channel state before any draft publish.
+ self.original_total_resource_count = self.channel.total_resource_count
+ self.original_published_size = self.channel.published_size
+ self.original_published_data = dict(self.channel.published_data)
+ self.original_version_info_id = (
+ self.channel.version_info.id if self.channel.version_info else None
+ )
+
+ def _run_draft_publish(self, version_notes=""):
+ publish_channel(
+ self.admin_user.id,
+ self.channel.id,
+ version_notes=version_notes,
+ force=False,
+ force_exercises=False,
+ send_email=False,
+ progress_tracker=None,
+ is_draft_version=True,
+ use_staging_tree=False,
+ )
+
+ def _get_draft_version(self):
+ return ccmodels.ChannelVersion.objects.get(channel=self.channel, version=None)
+
+ # ------------------------------------------------------------------
+ # Test 1: draft ChannelVersion metadata fields are populated
+ # ------------------------------------------------------------------
+
+ def test_draft_channel_version_fields_are_populated(self):
+ """
+ After a draft publish, the draft ChannelVersion has its metadata fields set.
+
+ testdata.channel() has no *published* nodes, so counts are 0 and lists are [].
+ We assert specific values rather than assertIsNotNone to avoid vacuous passes
+ (e.g. assertIsNotNone(0) always passes even if the field was never written).
+ date_published is the only field we can only check for non-None, since its
+ exact value is non-deterministic.
+ """
+ self._run_draft_publish(version_notes="draft notes")
+ draft_version = self._get_draft_version()
+
+ self.assertEqual(draft_version.resource_count, 0)
+ self.assertEqual(draft_version.size, 0)
+ self.assertEqual(draft_version.kind_count, [])
+ self.assertIsNotNone(draft_version.date_published)
+ self.assertEqual(draft_version.version_notes, "draft notes")
+ self.assertEqual(draft_version.included_languages, [])
+ self.assertEqual(draft_version.included_licenses, [])
+ self.assertEqual(draft_version.included_categories, [])
+ self.assertEqual(draft_version.non_distributable_licenses_included, [])
+
+ # ------------------------------------------------------------------
+ # Test 2: channel-level fields are NOT touched
+ # ------------------------------------------------------------------
+
+ def test_channel_fields_not_modified_during_draft_publish(self):
+ """
+ A draft publish must not change channel.total_resource_count,
+ channel.published_size, channel.published_data, or channel.version_info.
+ """
+ self._run_draft_publish()
+ self.channel.refresh_from_db()
+
+ self.assertEqual(
+ self.channel.total_resource_count, self.original_total_resource_count
+ )
+ self.assertEqual(self.channel.published_size, self.original_published_size)
+ self.assertEqual(self.channel.published_data, self.original_published_data)
+
+ current_version_info_id = (
+ self.channel.version_info.id if self.channel.version_info else None
+ )
+ self.assertEqual(current_version_info_id, self.original_version_info_id)
+
+ # ------------------------------------------------------------------
+ # Test 3: second draft publish replaces special_permissions_included
+ # ------------------------------------------------------------------
+
+ def test_second_draft_publish_replaces_special_permissions_included(self):
+ """
+ On a second consecutive draft publish, special_permissions_included on the
+ draft ChannelVersion reflects only the current publish — licenses from the
+ previous draft publish that are no longer present are removed.
+
+ Two publish_channel calls are made with a different license_description on
+ the special-permissions node between them. After the second call the M2M
+ must contain only the description used in that second call.
+ """
+ # Get a video node from the channel's main tree to use as the content node.
+ sp_node = (
+ self.channel.main_tree.get_descendants().filter(kind_id="video").first()
+ )
+
+ # First draft publish: node has Special Permissions with "License A".
+ sp_node.license = self.special_perms_license
+ sp_node.license_description = "License A"
+ sp_node.published = True
+ sp_node.save()
+
+ self._run_draft_publish()
+ draft_version = self._get_draft_version()
+ self.assertEqual(draft_version.special_permissions_included.count(), 1)
+ self.assertEqual(
+ draft_version.special_permissions_included.first().description, "License A"
+ )
+
+ # Second draft publish: node's description changes to "License B".
+ sp_node.license_description = "License B"
+ sp_node.save()
+
+ self._run_draft_publish()
+ draft_version.refresh_from_db()
+ self.assertEqual(
+ draft_version.special_permissions_included.count(),
+ 1,
+ "special_permissions_included should be fully replaced on each draft publish",
+ )
+ self.assertEqual(
+ draft_version.special_permissions_included.first().description, "License B"
+ )
+
+ # ------------------------------------------------------------------
+ # Test 4: distributable stays False for draft publishes of public channels
+ # ------------------------------------------------------------------
+
+ def test_special_permissions_distributable_false_for_draft_publish(self):
+ """
+ Even when channel.public is True, a draft publish must not mark
+ AuditedSpecialPermissionsLicense records as distributable — the
+ distributable field must remain False for all licenses linked to the
+ draft ChannelVersion.
+ """
+ sp_node = (
+ self.channel.main_tree.get_descendants().filter(kind_id="video").first()
+ )
+ sp_node.license = self.special_perms_license
+ sp_node.license_description = "Custom License"
+ sp_node.published = True
+ sp_node.save()
+
+ self.channel.public = True
+ self.channel.save()
+
+ self._run_draft_publish()
+ draft_version = self._get_draft_version()
+
+ self.assertGreater(draft_version.special_permissions_included.count(), 0)
+ for license_obj in draft_version.special_permissions_included.all():
+ self.assertFalse(
+ license_obj.distributable,
+ "distributable must stay False for draft publishes",
+ )
+
+ def test_draft_publish_populates_channel_snapshot_fields(self):
+ """
+ After the first draft publish, channel_name, channel_description,
+ channel_thumbnail_encoding, and channel_language on the draft
+ ChannelVersion reflect the channel's state at publish time.
+ """
+ self.channel.name = "Snapshot Channel"
+ self.channel.description = "Snapshot description"
+ self.channel.thumbnail_encoding = {"base64": "abc123"}
+ self.channel.save()
+
+ self._run_draft_publish()
+ draft_version = self._get_draft_version()
+
+ self.assertEqual(draft_version.channel_name, "Snapshot Channel")
+ self.assertEqual(draft_version.channel_description, "Snapshot description")
+ self.assertEqual(draft_version.channel_thumbnail_encoding, {"base64": "abc123"})
+ self.assertEqual(draft_version.channel_language_id, self.channel.language_id)
+
+ def test_second_draft_publish_refreshes_channel_snapshot_fields(self):
+ """
+ A second draft publish after mutating channel metadata must overwrite
+ the stale snapshot values on the existing draft ChannelVersion row —
+ for all four snapshot fields.
+
+ channel.language is changed from "en" to "fr" to confirm the refresh
+ is not vacuous (the language snapshot must change to the new value).
+ """
+ self.channel.name = "Old Name"
+ self.channel.description = "Old description"
+ self.channel.thumbnail_encoding = {"base64": "old_thumb"}
+ # language is already "en" from testdata.channel(); no need to set it
+ self.channel.save()
+
+ self._run_draft_publish()
+ draft_version = self._get_draft_version()
+
+ self.assertEqual(draft_version.channel_name, "Old Name")
+ self.assertEqual(
+ draft_version.channel_thumbnail_encoding, {"base64": "old_thumb"}
+ )
+ self.assertEqual(draft_version.channel_language_id, "en")
+
+ # Mutate all four fields; language changes from "en" to "fr"
+ self.channel.name = "New Name"
+ self.channel.description = "New description"
+ self.channel.thumbnail_encoding = {"base64": "new_thumb"}
+ self.channel.language_id = "fr"
+ self.channel.save()
+
+ self._run_draft_publish()
+ draft_version.refresh_from_db()
+
+ self.assertEqual(draft_version.channel_name, "New Name")
+ self.assertEqual(draft_version.channel_description, "New description")
+ self.assertEqual(
+ draft_version.channel_thumbnail_encoding, {"base64": "new_thumb"}
+ )
+ self.assertEqual(draft_version.channel_language_id, "fr")
+
+
+class MapChannelToKolibriChannelTestCase(StudioTestCase):
+ def setUp(self):
+ super().setUp()
+ self.channel = testdata.channel()
+ # icon_encoding must not be None — the kolibri content DB thumbnail column is NOT NULL.
+ # publish_channel calls set_channel_icon_encoding before create_content_database;
+ # since we call create_content_database directly we set it here instead.
+ self.channel.icon_encoding = ""
+ self.channel.save()
+ # increment_channel_version + refresh_from_db gives channel.version > 0,
+ # so channel.version + 1 is distinguishable from the draft value of 0.
+ increment_channel_version(self.channel)
+ self.channel.refresh_from_db()
+
+ def _get_channel_metadata_version(self, is_draft_version):
+ with mock.patch("contentcuration.utils.publish.save_export_database"):
+ tempdb = create_content_database(
+ self.channel,
+ force=True,
+ user_id=self.admin_user.id,
+ force_exercises=False,
+ is_draft_version=is_draft_version,
+ )
+ try:
+ with using_content_database(tempdb):
+ return kolibrimodels.ChannelMetadata.objects.get(
+ id=self.channel.id
+ ).version
+ finally:
+ if os.path.exists(tempdb):
+ os.remove(tempdb)
+
+ def test_draft_publish_sets_version_zero(self):
+ self.assertEqual(self._get_channel_metadata_version(is_draft_version=True), 0)
+
+ def test_non_draft_publish_sets_version_plus_one(self):
+ self.assertEqual(
+ self._get_channel_metadata_version(is_draft_version=False),
+ self.channel.version + 1,
+ )
diff --git a/contentcuration/contentcuration/tests/utils/test_restricted_filesystemstorage.py b/contentcuration/contentcuration/tests/utils/test_restricted_filesystemstorage.py
new file mode 100644
index 0000000000..ca5ce7be71
--- /dev/null
+++ b/contentcuration/contentcuration/tests/utils/test_restricted_filesystemstorage.py
@@ -0,0 +1,44 @@
+import tempfile
+
+from django.core.files.base import ContentFile
+from django.test import TestCase
+
+from contentcuration.tests.utils.restricted_filesystemstorage import (
+ RestrictedFileSystemStorage,
+)
+
+
+class RestrictedFileSystemStorageTestCase(TestCase):
+ # Sanity-checks that the RestrictedFileSystemStorage wrapper used in tests
+ # works as expected, not actually testing application code
+
+ def setUp(self):
+ super().setUp()
+
+ self._temp_directory_ctx = tempfile.TemporaryDirectory()
+ self.temp_dir = self._temp_directory_ctx.__enter__()
+
+ self.storage = RestrictedFileSystemStorage(location=self.temp_dir)
+
+ def tearDown(self):
+ self._temp_directory_ctx.__exit__(None, None, None)
+ super().tearDown()
+
+ def test_opening_for_read_works(self):
+ test_content = "test content"
+
+ self.storage.save("filename", ContentFile(test_content))
+ with self.storage.open("filename", "r") as f:
+ content = f.read()
+ self.assertEqual(content, test_content)
+
+ def test_opening_for_write_does_not_work(self):
+ test_content = "test content"
+
+ with self.assertRaises(ValueError):
+ with self.storage.open("filename", "w") as f:
+ f.write(test_content)
+
+ def test_path_does_not_work(self):
+ with self.assertRaises(NotImplementedError):
+ self.storage.path("filename")
diff --git a/contentcuration/contentcuration/tests/views/test_subscription.py b/contentcuration/contentcuration/tests/views/test_subscription.py
new file mode 100644
index 0000000000..889d3f4f58
--- /dev/null
+++ b/contentcuration/contentcuration/tests/views/test_subscription.py
@@ -0,0 +1,378 @@
+from unittest import mock
+
+import stripe
+from django.test import override_settings
+from django.urls import reverse
+
+from contentcuration.models import UserSubscription
+from contentcuration.tests import testdata
+from contentcuration.tests.base import StudioAPITestCase
+
+
+@override_settings(
+ STRIPE_SECRET_KEY="sk_test_fake",
+ STRIPE_PRICE_ID="price_test_fake",
+)
+class CreateCheckoutSessionViewTest(StudioAPITestCase):
+ def setUp(self):
+ self.user = testdata.user(email="checkout@test.com")
+ self.url = reverse("stripe_create_checkout_session")
+
+ def test_requires_authentication(self):
+ """Unauthenticated users should be rejected."""
+ response = self.client.post(self.url)
+ self.assertEqual(response.status_code, 403)
+
+ @mock.patch("contentcuration.views.subscription.stripe.checkout.Session.create")
+ def test_creates_checkout_session(self, mock_create):
+ """Authenticated user can create checkout session."""
+ mock_create.return_value = mock.Mock(url="https://checkout.stripe.com/test")
+
+ self.client.force_authenticate(self.user)
+ response = self.client.post(
+ self.url,
+ data={"storage_gb": 10},
+ format="json",
+ )
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertEqual(data["checkout_url"], "https://checkout.stripe.com/test")
+
+ @mock.patch("contentcuration.views.subscription.stripe.checkout.Session.create")
+ def test_rejects_user_with_active_subscription(self, mock_create):
+ """User with active subscription cannot create new checkout."""
+ UserSubscription.objects.create(
+ user=self.user,
+ stripe_subscription_status="active",
+ )
+
+ self.client.force_authenticate(self.user)
+ response = self.client.post(
+ self.url,
+ data={"storage_gb": 10},
+ format="json",
+ )
+
+ self.assertEqual(response.status_code, 400)
+ mock_create.assert_not_called()
+
+ @mock.patch("contentcuration.views.subscription.stripe.checkout.Session.create")
+ def test_user_with_canceled_subscription_can_checkout_again(self, mock_create):
+ """User whose subscription was canceled can create a new checkout session."""
+ UserSubscription.objects.create(
+ user=self.user,
+ stripe_customer_id="cus_test123",
+ stripe_subscription_id="sub_old",
+ stripe_subscription_status="canceled",
+ subscription_disk_space=0,
+ )
+ mock_create.return_value = mock.Mock(url="https://checkout.stripe.com/new")
+
+ self.client.force_authenticate(self.user)
+ response = self.client.post(
+ self.url,
+ data={"storage_gb": 5},
+ format="json",
+ )
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertEqual(data["checkout_url"], "https://checkout.stripe.com/new")
+ # Should reuse the existing customer ID
+ mock_create.assert_called_once()
+ call_kwargs = mock_create.call_args[1]
+ self.assertEqual(call_kwargs["customer"], "cus_test123")
+
+
+@override_settings(
+ STRIPE_SECRET_KEY="sk_test_fake",
+)
+class CreatePortalSessionViewTest(StudioAPITestCase):
+ def setUp(self):
+ self.user = testdata.user(email="portal@test.com")
+ self.url = reverse("stripe_create_portal_session")
+
+ def test_requires_authentication(self):
+ """Unauthenticated users should be rejected."""
+ response = self.client.post(self.url)
+ self.assertEqual(response.status_code, 403)
+
+ def test_rejects_user_without_subscription(self):
+ """User without subscription cannot access portal."""
+ self.client.force_authenticate(self.user)
+ response = self.client.post(self.url)
+
+ self.assertEqual(response.status_code, 400)
+
+ @mock.patch(
+ "contentcuration.views.subscription.stripe.billing_portal.Session.create"
+ )
+ def test_creates_portal_session(self, mock_create):
+ """User with subscription can create portal session."""
+ UserSubscription.objects.create(
+ user=self.user,
+ stripe_customer_id="cus_test123",
+ stripe_subscription_status="active",
+ )
+ mock_create.return_value = mock.Mock(url="https://billing.stripe.com/test")
+
+ self.client.force_authenticate(self.user)
+ response = self.client.post(self.url)
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertEqual(data["portal_url"], "https://billing.stripe.com/test")
+
+
+@override_settings(
+ STRIPE_SECRET_KEY="sk_test_fake",
+)
+class GetSubscriptionStatusViewTest(StudioAPITestCase):
+ def setUp(self):
+ self.user = testdata.user(email="status@test.com")
+ self.url = reverse("stripe_subscription_status")
+
+ def test_requires_authentication(self):
+ """Unauthenticated users should be rejected."""
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, 403)
+
+ def test_returns_no_subscription(self):
+ """User without subscription gets appropriate response."""
+ self.client.force_authenticate(self.user)
+ response = self.client.get(self.url)
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertFalse(data["is_active"])
+
+ def test_returns_active_subscription(self):
+ """User with active subscription gets appropriate response."""
+ UserSubscription.objects.create(
+ user=self.user,
+ stripe_subscription_status="active",
+ subscription_disk_space=50 * 1024 * 1024 * 1024,
+ )
+
+ self.client.force_authenticate(self.user)
+ response = self.client.get(self.url)
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertTrue(data["is_active"])
+ self.assertEqual(data["status"], "active")
+
+
+@override_settings(
+ STRIPE_SECRET_KEY="sk_test_fake",
+ STRIPE_WEBHOOK_SECRET="whsec_test_fake",
+)
+class StripeWebhookViewTest(StudioAPITestCase):
+ def setUp(self):
+ self.user = testdata.user(email="webhook@test.com")
+ self.webhook_url = reverse("stripe_webhook")
+
+ @mock.patch("contentcuration.views.subscription.stripe.Subscription.retrieve")
+ @mock.patch("contentcuration.views.subscription.stripe.Webhook.construct_event")
+ def test_handles_checkout_completed(self, mock_construct, mock_retrieve):
+ """Webhook activates subscription on checkout.session.completed."""
+ mock_construct.return_value = {
+ "id": "evt_test",
+ "type": "checkout.session.completed",
+ "data": {
+ "object": {
+ "id": "cs_test",
+ "client_reference_id": str(self.user.id),
+ "customer": "cus_test123",
+ "subscription": "sub_test123",
+ }
+ },
+ }
+ mock_retrieve.return_value = {
+ "items": {"data": [{"quantity": 10}]},
+ }
+
+ response = self.client.post(
+ self.webhook_url,
+ data="{}",
+ format="json",
+ HTTP_STRIPE_SIGNATURE="test_sig",
+ )
+
+ self.assertEqual(response.status_code, 200)
+
+ # Verify subscription was created
+ self.user.refresh_from_db()
+ subscription = self.user.subscription
+ self.assertEqual(subscription.stripe_customer_id, "cus_test123")
+ self.assertEqual(subscription.stripe_subscription_id, "sub_test123")
+ self.assertEqual(subscription.stripe_subscription_status, "active")
+ self.assertEqual(subscription.subscription_disk_space, 10 * 10 ** 9)
+
+ @mock.patch("contentcuration.views.subscription.stripe.Webhook.construct_event")
+ def test_handles_subscription_deleted(self, mock_construct):
+ """Webhook revokes access on customer.subscription.deleted."""
+ subscription = UserSubscription.objects.create(
+ user=self.user,
+ stripe_customer_id="cus_test123",
+ stripe_subscription_id="sub_test123",
+ stripe_subscription_status="active",
+ subscription_disk_space=50 * 1024 * 1024 * 1024,
+ )
+
+ mock_construct.return_value = {
+ "id": "evt_test",
+ "type": "customer.subscription.deleted",
+ "data": {
+ "object": {
+ "id": "sub_test123",
+ "customer": "cus_test123",
+ }
+ },
+ }
+
+ response = self.client.post(
+ self.webhook_url,
+ data="{}",
+ format="json",
+ HTTP_STRIPE_SIGNATURE="test_sig",
+ )
+
+ self.assertEqual(response.status_code, 200)
+
+ subscription.refresh_from_db()
+ self.assertEqual(subscription.stripe_subscription_status, "canceled")
+ self.assertEqual(subscription.subscription_disk_space, 0)
+
+ @mock.patch("contentcuration.views.subscription.stripe.Webhook.construct_event")
+ def test_handles_subscription_updated(self, mock_construct):
+ """Webhook updates status on customer.subscription.updated."""
+ subscription = UserSubscription.objects.create(
+ user=self.user,
+ stripe_customer_id="cus_test123",
+ stripe_subscription_id="sub_test123",
+ stripe_subscription_status="active",
+ subscription_disk_space=50 * 1024 * 1024 * 1024,
+ )
+
+ mock_construct.return_value = {
+ "id": "evt_test",
+ "type": "customer.subscription.updated",
+ "data": {
+ "object": {
+ "id": "sub_test123",
+ "status": "past_due",
+ "cancel_at_period_end": False,
+ "items": {"data": [{"quantity": 50}]},
+ }
+ },
+ }
+
+ response = self.client.post(
+ self.webhook_url,
+ data="{}",
+ format="json",
+ HTTP_STRIPE_SIGNATURE="test_sig",
+ )
+
+ self.assertEqual(response.status_code, 200)
+
+ subscription.refresh_from_db()
+ self.assertEqual(subscription.stripe_subscription_status, "past_due")
+
+ @mock.patch("contentcuration.views.subscription.stripe.Webhook.construct_event")
+ def test_handles_cancel_at_period_end(self, mock_construct):
+ """Webhook sets cancel_at_period_end when user cancels via portal."""
+ subscription = UserSubscription.objects.create(
+ user=self.user,
+ stripe_customer_id="cus_test123",
+ stripe_subscription_id="sub_test123",
+ stripe_subscription_status="active",
+ subscription_disk_space=5 * 10 ** 9,
+ )
+
+ mock_construct.return_value = {
+ "id": "evt_test",
+ "type": "customer.subscription.updated",
+ "data": {
+ "object": {
+ "id": "sub_test123",
+ "status": "active",
+ "cancel_at_period_end": False,
+ "cancel_at": 1806883200,
+ "current_period_end": 1806883200,
+ "items": {"data": [{"quantity": 5}]},
+ }
+ },
+ }
+
+ response = self.client.post(
+ self.webhook_url,
+ data="{}",
+ format="json",
+ HTTP_STRIPE_SIGNATURE="test_sig",
+ )
+
+ self.assertEqual(response.status_code, 200)
+
+ subscription.refresh_from_db()
+ self.assertEqual(subscription.stripe_subscription_status, "active")
+ self.assertTrue(subscription.cancel_at_period_end)
+ self.assertIsNotNone(subscription.current_period_end)
+ # Storage preserved — still active until period end
+ self.assertEqual(subscription.subscription_disk_space, 5 * 10 ** 9)
+
+ @mock.patch("contentcuration.views.subscription.stripe.Webhook.construct_event")
+ def test_rejects_invalid_signature(self, mock_construct):
+ """Webhook rejects invalid signatures."""
+ mock_construct.side_effect = stripe.error.SignatureVerificationError(
+ "Invalid signature", "sig"
+ )
+
+ response = self.client.post(
+ self.webhook_url,
+ data="{}",
+ format="json",
+ HTTP_STRIPE_SIGNATURE="bad_sig",
+ )
+
+ self.assertEqual(response.status_code, 400)
+
+ @mock.patch("contentcuration.views.subscription.stripe.Subscription.retrieve")
+ @mock.patch("contentcuration.views.subscription.stripe.Webhook.construct_event")
+ def test_idempotent_checkout_processing(self, mock_construct, mock_retrieve):
+ """Same checkout event processed twice doesn't duplicate."""
+ mock_retrieve.return_value = {
+ "items": {"data": [{"quantity": 10}]},
+ }
+ UserSubscription.objects.create(
+ user=self.user,
+ stripe_customer_id="cus_test123",
+ stripe_subscription_id="sub_test123",
+ stripe_subscription_status="active",
+ )
+
+ mock_construct.return_value = {
+ "id": "evt_test",
+ "type": "checkout.session.completed",
+ "data": {
+ "object": {
+ "id": "cs_test",
+ "client_reference_id": str(self.user.id),
+ "customer": "cus_test123",
+ "subscription": "sub_test123",
+ }
+ },
+ }
+
+ response = self.client.post(
+ self.webhook_url,
+ data="{}",
+ format="json",
+ HTTP_STRIPE_SIGNATURE="test_sig",
+ )
+
+ self.assertEqual(response.status_code, 200)
+ # Should still only have one subscription
+ self.assertEqual(UserSubscription.objects.filter(user=self.user).count(), 1)
diff --git a/contentcuration/contentcuration/tests/viewsets/base.py b/contentcuration/contentcuration/tests/viewsets/base.py
index 617d23bb26..c75b7f9549 100644
--- a/contentcuration/contentcuration/tests/viewsets/base.py
+++ b/contentcuration/contentcuration/tests/viewsets/base.py
@@ -2,9 +2,8 @@
from django.urls import reverse
-from contentcuration.celery import app
from contentcuration.models import Change
-from contentcuration.tests.helpers import clear_tasks
+from contentcuration.tests.helpers import EagerTasksTestMixin
from contentcuration.viewsets.sync.constants import CHANNEL
from contentcuration.viewsets.sync.constants import SYNCED
from contentcuration.viewsets.sync.utils import _generate_event as base_generate_event
@@ -94,31 +93,15 @@ def generate_publish_channel_event(channel_id):
return event
-def generate_publish_next_event(channel_id):
- event = base_generate_publish_next_event(channel_id)
+def generate_publish_next_event(channel_id, use_staging_tree=False):
+ event = base_generate_publish_next_event(
+ channel_id, use_staging_tree=use_staging_tree
+ )
event["rev"] = random.randint(1, 10000000)
return event
-class SyncTestMixin(object):
- celery_task_always_eager = None
-
- @classmethod
- def setUpClass(cls):
- super(SyncTestMixin, cls).setUpClass()
- # update celery so tasks are always eager for this test, meaning they'll execute synchronously
- cls.celery_task_always_eager = app.conf.task_always_eager
- app.conf.update(task_always_eager=True)
-
- def setUp(self):
- super(SyncTestMixin, self).setUp()
- clear_tasks()
-
- @classmethod
- def tearDownClass(cls):
- super(SyncTestMixin, cls).tearDownClass()
- app.conf.update(task_always_eager=cls.celery_task_always_eager)
-
+class SyncTestMixin(EagerTasksTestMixin):
@property
def sync_url(self):
return reverse("sync")
diff --git a/contentcuration/contentcuration/tests/viewsets/test_channel.py b/contentcuration/contentcuration/tests/viewsets/test_channel.py
index 8309f47c8c..2c72cf2dcc 100644
--- a/contentcuration/contentcuration/tests/viewsets/test_channel.py
+++ b/contentcuration/contentcuration/tests/viewsets/test_channel.py
@@ -6,14 +6,24 @@
from django.urls import reverse
from kolibri_public.models import ContentNode as PublicContentNode
from le_utils.constants import content_kinds
+from le_utils.constants.labels import subjects
from mock import patch
from contentcuration import models
from contentcuration import models as cc
from contentcuration.constants import channel_history
+from contentcuration.constants import community_library_submission
+from contentcuration.models import AuditedSpecialPermissionsLicense
+from contentcuration.models import Change
+from contentcuration.models import Channel
+from contentcuration.models import ChannelVersion
+from contentcuration.models import CommunityLibrarySubmission
from contentcuration.models import ContentNode
+from contentcuration.models import Country
+from contentcuration.tasks import apply_channel_changes_task
from contentcuration.tests import testdata
from contentcuration.tests.base import StudioAPITestCase
+from contentcuration.tests.helpers import reverse_with_query
from contentcuration.tests.viewsets.base import generate_create_event
from contentcuration.tests.viewsets.base import generate_delete_event
from contentcuration.tests.viewsets.base import generate_deploy_channel_event
@@ -24,6 +34,9 @@
from contentcuration.tests.viewsets.base import SyncTestMixin
from contentcuration.viewsets.channel import _unpublished_changes_query
from contentcuration.viewsets.sync.constants import CHANNEL
+from contentcuration.viewsets.sync.utils import (
+ generate_added_to_community_library_event,
+)
class SyncTestCase(SyncTestMixin, StudioAPITestCase):
@@ -492,7 +505,7 @@ def test_publish_next(self):
self.assertEqual(response.status_code, 200)
modified_channel = models.Channel.objects.get(id=channel.id)
- self.assertEqual(modified_channel.staging_tree.published, True)
+ self.assertEqual(modified_channel.staging_tree.published, False)
def test_publish_next_with_incomplete_staging_tree(self):
channel = testdata.channel()
@@ -507,7 +520,9 @@ def test_publish_next_with_incomplete_staging_tree(self):
channel.save()
self.assertEqual(channel.staging_tree.published, False)
- response = self.sync_changes([generate_publish_next_event(channel.id)])
+ response = self.sync_changes(
+ [generate_publish_next_event(channel.id, use_staging_tree=True)]
+ )
self.assertEqual(response.status_code, 200)
self.assertTrue(
@@ -517,6 +532,115 @@ def test_publish_next_with_incomplete_staging_tree(self):
modified_channel = models.Channel.objects.get(id=channel.id)
self.assertEqual(modified_channel.staging_tree.published, False)
+ def test_sync_added_to_community_library_change(self):
+ # Syncing the change from the frontend should be disallowed
+ self.client.force_authenticate(self.admin_user)
+
+ channel = testdata.channel()
+ channel.version = 1
+ channel.public = True
+ channel.save()
+
+ added_to_community_library_change = generate_added_to_community_library_event(
+ key=channel.id,
+ channel_version=1,
+ )
+ response = self.sync_changes([added_to_community_library_change])
+
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(len(response.json()["allowed"]), 0, response.content)
+ self.assertEqual(len(response.json()["disallowed"]), 1, response.content)
+
+ @mock.patch("contentcuration.viewsets.channel.export_channel_to_kolibri_public")
+ def test_process_added_to_community_library_change(self, mock_export_func):
+ # Creating the change on the backend should be supported
+ self.client.force_authenticate(self.admin_user)
+
+ editor_user = testdata.user("channel@editor.com")
+
+ channel = testdata.channel()
+ channel.version = 2
+ channel.public = False
+ channel.editors.add(editor_user)
+ channel.save()
+
+ current_live_submission = CommunityLibrarySubmission.objects.create(
+ channel=channel,
+ channel_version=1,
+ author=editor_user,
+ status=community_library_submission.STATUS_LIVE,
+ )
+ new_submission = CommunityLibrarySubmission.objects.create(
+ channel=channel,
+ channel_version=2,
+ author=editor_user,
+ status=community_library_submission.STATUS_APPROVED,
+ )
+
+ categories = {
+ "category1": True,
+ "category2": True,
+ }
+
+ country1 = Country.objects.create(code="C1", name="Country 1")
+ country2 = Country.objects.create(code="C2", name="Country 2")
+ countries = [country1, country2]
+ country_codes = [country.code for country in countries]
+
+ added_to_community_library_change = generate_added_to_community_library_event(
+ key=channel.id,
+ channel_version=2,
+ categories=categories,
+ country_codes=country_codes,
+ )
+ Change.create_change(
+ added_to_community_library_change, created_by_id=self.admin_user.id
+ )
+
+ # This task will run immediately thanks to SyncTestMixin
+ apply_channel_changes_task.fetch_or_enqueue(
+ user=self.admin_user,
+ channel_id=channel.id,
+ )
+
+ # We cannot easily use the assert_called_once_with method here
+ # because we are not checking countries for strict equality,
+ # so we need to check the call arguments manually
+ mock_export_func.assert_called_once()
+
+ (call_args, call_kwargs) = mock_export_func.call_args
+ self.assertEqual(len(call_args), 0)
+ self.assertCountEqual(
+ call_kwargs.keys(),
+ [
+ "channel_id",
+ "channel_version",
+ "categories",
+ "countries",
+ "public",
+ ],
+ )
+ self.assertEqual(call_kwargs["channel_id"], channel.id)
+ self.assertEqual(call_kwargs["channel_version"], 2)
+ self.assertCountEqual(call_kwargs["categories"], categories.keys())
+
+ # The countries argument used when creating the mapper is in fact
+ # not a list, but a QuerySet, but it contains the same elements
+ self.assertCountEqual(call_kwargs["countries"], countries)
+ self.assertEqual(call_kwargs["public"], False)
+
+ # Check that the current submission became the live one
+ current_live_submission.refresh_from_db()
+ new_submission.refresh_from_db()
+ self.assertEqual(
+ current_live_submission.status,
+ community_library_submission.STATUS_APPROVED,
+ )
+ self.assertEqual(
+ new_submission.status,
+ community_library_submission.STATUS_LIVE,
+ )
+
class CRUDTestCase(StudioAPITestCase):
@property
@@ -554,6 +678,186 @@ def test_fetch_admin_channels_invalid_filter(self):
)
self.assertEqual(response.status_code, 200, response.content)
+ def test_admin_channel_detail__latest_community_library_submission__exists(self):
+ older_submission = testdata.community_library_submission()
+ older_submission.channel.version = 2
+ older_submission.channel_version = 1
+ older_submission.status = community_library_submission.STATUS_LIVE
+ older_submission.channel.save()
+ older_submission.save()
+
+ latest_submission = CommunityLibrarySubmission.objects.create(
+ channel=older_submission.channel,
+ channel_version=2,
+ author=older_submission.author,
+ )
+
+ self.client.force_authenticate(self.admin_user)
+ response = self.client.get(
+ reverse(
+ "admin-channels-detail", kwargs={"pk": older_submission.channel.id}
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(
+ response.data["latest_community_library_submission_id"],
+ latest_submission.id,
+ )
+ self.assertEqual(
+ response.data["latest_community_library_submission_status"],
+ community_library_submission.STATUS_PENDING,
+ )
+ self.assertTrue(response.data["has_any_live_community_library_submission"])
+
+ def test_admin_channel_detail__latest_community_library_submission__none_exist(
+ self,
+ ):
+ channel = testdata.channel()
+
+ self.client.force_authenticate(self.admin_user)
+ response = self.client.get(
+ reverse("admin-channels-detail", kwargs={"pk": channel.id}),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertIsNone(response.data["latest_community_library_submission_id"])
+ self.assertIsNone(response.data["latest_community_library_submission_status"])
+ self.assertFalse(response.data["has_any_live_community_library_submission"])
+
+ def test_admin_channel_filter__latest_community_library_submission_status__any(
+ self,
+ ):
+ self.client.force_authenticate(user=self.admin_user)
+
+ submission = testdata.community_library_submission()
+
+ response = self.client.get(
+ reverse_with_query(
+ "admin-channels-list",
+ query={
+ "id__in": submission.channel.id,
+ },
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(len(response.data), 1)
+
+ def test_admin_channel_filter__latest_community_library_submission_status__multiple(
+ self,
+ ):
+ self.client.force_authenticate(user=self.admin_user)
+
+ submission1 = testdata.community_library_submission()
+ submission1.status = community_library_submission.STATUS_LIVE
+ submission1.save()
+
+ submission2 = testdata.community_library_submission()
+ submission2.status = community_library_submission.STATUS_PENDING
+ submission2.save()
+
+ submission3 = testdata.community_library_submission()
+ submission3.status = community_library_submission.STATUS_APPROVED
+ submission3.save()
+
+ response = self.client.get(
+ reverse_with_query(
+ "admin-channels-list",
+ query=[
+ (
+ "latest_community_library_submission_status",
+ community_library_submission.STATUS_LIVE,
+ ),
+ (
+ "latest_community_library_submission_status",
+ community_library_submission.STATUS_PENDING,
+ ),
+ ],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertCountEqual(
+ [ch["id"] for ch in response.data],
+ [
+ submission1.channel.id,
+ submission2.channel.id,
+ ],
+ )
+
+ def test_admin_channel_filter__community_library_live(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ submission1 = testdata.community_library_submission()
+ submission1.channel.version = 2
+ submission1.channel.save()
+ submission1.status = community_library_submission.STATUS_LIVE
+ submission1.channel_version = 1
+ submission1.save()
+
+ CommunityLibrarySubmission.objects.create(
+ channel=submission1.channel,
+ channel_version=2,
+ author=submission1.author,
+ status=community_library_submission.STATUS_PENDING,
+ )
+
+ other_channel_submission = testdata.community_library_submission()
+ other_channel_submission.status = community_library_submission.STATUS_PENDING
+ other_channel_submission.save()
+
+ response = self.client.get(
+ reverse_with_query(
+ "admin-channels-list",
+ query={"community_library_live": True},
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertCountEqual(
+ [ch["id"] for ch in response.data],
+ [submission1.channel.id],
+ )
+
+ def test_has_community_library_submission_endpoint(self):
+ """Test the on-demand has_community_library_submission endpoint"""
+ user = testdata.user()
+ channel_with_submission = testdata.channel()
+ channel_with_submission.editors.add(user)
+ channel_with_submission.version = 1
+ channel_with_submission.save()
+ submission = testdata.community_library_submission()
+ submission.channel = channel_with_submission
+ submission.author = user
+ submission.channel_version = channel_with_submission.version
+ submission.save()
+
+ channel_without_submission = testdata.channel()
+ channel_without_submission.editors.add(user)
+
+ self.client.force_authenticate(user=user)
+
+ response = self.client.get(
+ reverse(
+ "channel-has-community-library-submission",
+ kwargs={"pk": channel_with_submission.id},
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertTrue(response.data["has_community_library_submission"])
+
+ response = self.client.get(
+ reverse(
+ "channel-has-community-library-submission",
+ kwargs={"pk": channel_without_submission.id},
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertFalse(response.data["has_community_library_submission"])
+
def test_create_channel(self):
user = testdata.user()
self.client.force_authenticate(user=user)
@@ -627,6 +931,45 @@ def test_admin_restore_channel(self):
channel.history.filter(actor=user, action=channel_history.RECOVERY).count(),
)
+ def test_channel_detail_includes_draft_token_when_draft_version_exists(self):
+ """Test that the channel API response includes draft_token when a draft ChannelVersion exists."""
+ user = testdata.user()
+ channel = models.Channel.objects.create(
+ actor_id=user.id, **self.channel_metadata
+ )
+ channel.editors.add(user)
+
+ # Create a draft ChannelVersion (version=None) with a token
+ draft_version = ChannelVersion.objects.create(
+ channel=channel,
+ version=None,
+ )
+ token = draft_version.new_token()
+
+ self.client.force_authenticate(user=user)
+ response = self.client.get(
+ reverse("channel-detail", kwargs={"pk": channel.id}),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(response.data["draft_token"], token.token)
+
+ def test_channel_detail_draft_token_is_none_when_no_draft_version(self):
+ """Test that the channel API response has draft_token=None when no draft version exists."""
+ user = testdata.user()
+ channel = models.Channel.objects.create(
+ actor_id=user.id, **self.channel_metadata
+ )
+ channel.editors.add(user)
+
+ self.client.force_authenticate(user=user)
+ response = self.client.get(
+ reverse("channel-detail", kwargs={"pk": channel.id}),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertIsNone(response.data["draft_token"])
+
class UnpublishedChangesQueryTestCase(StudioAPITestCase):
def test_unpublished_changes_query_with_channel_object(self):
@@ -940,3 +1283,402 @@ def _perform_action(self, url_path, channel_id):
reverse(url_path, kwargs={"pk": channel_id}), format="json"
)
return response
+
+
+class GetPublishedDataTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+
+ self.editor_user = testdata.user(email="editor@user.com")
+ self.admin_user = testdata.user(email="admin@user.com")
+ self.forbidden_user = testdata.user(email="forbidden@user.com")
+
+ self.channel = testdata.channel()
+ self.channel.editors.add(self.editor_user)
+ self.channel.editors.add(self.admin_user)
+
+ self.channel.published_data = {
+ "key1": "value1",
+ "key2": "value2",
+ }
+ self.channel.save()
+
+ def test_get_version_detail__is_editor(self):
+ """Test that editors can get version detail with populated versionInfo."""
+ from contentcuration.models import ChannelVersion
+
+ self.client.force_authenticate(user=self.editor_user)
+
+ self.channel.version = 1
+ self.channel.save()
+
+ channel_version, created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=1,
+ defaults={
+ "resource_count": 100,
+ "size": 1024000,
+ },
+ )
+ if not created:
+ channel_version.resource_count = 100
+ channel_version.size = 1024000
+ channel_version.save()
+
+ self.channel.version_info = channel_version
+ self.channel.save()
+
+ response = self.client.get(
+ reverse("channel-version-detail", kwargs={"pk": self.channel.id}),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ data = response.json()
+
+ self.assertEqual(data["version"], 1)
+ self.assertEqual(data["resource_count"], 100)
+ self.assertEqual(data["size"], 1024000)
+
+ def test_get_version_detail__is_admin(self):
+ """Test that admins can get version detail with populated versionInfo."""
+ from contentcuration.models import ChannelVersion
+
+ self.client.force_authenticate(user=self.admin_user)
+
+ self.channel.version = 2
+ self.channel.save()
+
+ channel_version, created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=2,
+ defaults={
+ "resource_count": 200,
+ "size": 2048000,
+ },
+ )
+ if not created:
+ channel_version.resource_count = 200
+ channel_version.size = 2048000
+ channel_version.save()
+
+ self.channel.version_info = channel_version
+ self.channel.save()
+
+ response = self.client.get(
+ reverse("channel-version-detail", kwargs={"pk": self.channel.id}),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ data = response.json()
+
+ # Assert against the most recent ChannelVersion
+ self.assertEqual(data["version"], 2)
+ self.assertEqual(data["resource_count"], 200)
+ self.assertEqual(data["size"], 2048000)
+
+ def test_get_version_detail__is_forbidden_user(self):
+ """Test that forbidden users cannot access version detail."""
+ self.client.force_authenticate(user=self.forbidden_user)
+
+ response = self.client.get(
+ reverse("channel-version-detail", kwargs={"pk": self.channel.id}),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 404, response.content)
+
+
+class GetVersionDetailEndpointTestCase(StudioAPITestCase):
+ """Test get_version_detail API endpoint."""
+
+ def setUp(self):
+ super(GetVersionDetailEndpointTestCase, self).setUp()
+ self.user = testdata.user()
+ self.client.force_authenticate(user=self.user)
+
+ self.channel = testdata.channel()
+ self.channel.version = 3
+ self.channel.published = True
+ self.channel.editors.add(self.user)
+ self.channel.save()
+
+ self.channel_version, _ = ChannelVersion.objects.update_or_create(
+ channel=self.channel,
+ version=3,
+ defaults={
+ "version_notes": "Test version",
+ "size": 5000,
+ "resource_count": 25,
+ "kind_count": [
+ {"count": 10, "kind_id": "video"},
+ {"count": 15, "kind_id": "document"},
+ ],
+ "included_languages": ["en", "es"],
+ "included_licenses": [1, 2],
+ "included_categories": [
+ subjects.SUBJECTSLIST[0],
+ subjects.SUBJECTSLIST[1],
+ ],
+ },
+ )
+
+ self.channel.version_info = self.channel_version
+ self.channel.save()
+
+ def test_get_version_detail_success(self):
+ """Test successfully retrieving version detail."""
+ url = reverse("channel-version-detail", kwargs={"pk": self.channel.id})
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+
+ data = response.json()
+ self.assertEqual(data["version"], 3)
+ self.assertEqual(data["version_notes"], "Test version")
+ self.assertEqual(data["size"], 5000)
+ self.assertEqual(data["resource_count"], 25)
+
+ def test_get_version_detail_no_version_info(self):
+ """Test endpoint when channel has no version_info."""
+ channel2 = testdata.channel()
+ channel2.version = 1
+ channel2.editors.add(self.user)
+ channel2.save()
+
+ ChannelVersion.objects.filter(channel=channel2).delete()
+
+ Channel.objects.filter(pk=channel2.pk).update(version_info=None)
+
+ url = reverse("channel-version-detail", kwargs={"pk": channel2.id})
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), {})
+
+ def test_get_version_detail_returns_all_fields(self):
+ """Test that get_version_detail returns all expected fields."""
+ url = reverse("channel-version-detail", kwargs={"pk": self.channel.id})
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+
+ expected_fields = [
+ "id",
+ "version",
+ "resource_count",
+ "kind_count",
+ "size",
+ "date_published",
+ "version_notes",
+ "included_languages",
+ "included_licenses",
+ "included_categories",
+ "non_distributable_licenses_included",
+ ]
+ for field in expected_fields:
+ self.assertIn(field, data, f"Field '{field}' should be in response")
+
+ def test_get_version_detail_excludes_special_permissions_included(self):
+ """Test that special_permissions_included is not in the response."""
+ special_license = AuditedSpecialPermissionsLicense.objects.create(
+ description="Test special permissions license"
+ )
+ self.channel_version.special_permissions_included.add(special_license)
+
+ url = reverse("channel-version-detail", kwargs={"pk": self.channel.id})
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+
+ self.assertNotIn(
+ "special_permissions_included",
+ data,
+ "special_permissions_included should not be in the response. "
+ "Use /api/audited_special_permissions_license/?channel_version= instead.",
+ )
+
+ def test_get_version_detail_with_special_permissions_licenses(self):
+ """Test that get_version_detail works correctly even when special permissions licenses exist."""
+ license1 = AuditedSpecialPermissionsLicense.objects.create(
+ description="First special permissions license"
+ )
+ license2 = AuditedSpecialPermissionsLicense.objects.create(
+ description="Second special permissions license"
+ )
+
+ self.channel_version.special_permissions_included.add(license1, license2)
+
+ url = reverse("channel-version-detail", kwargs={"pk": self.channel.id})
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+
+ self.assertNotIn("special_permissions_included", data)
+ self.assertEqual(data["version"], 3)
+ self.assertEqual(data["resource_count"], 25)
+
+
+class ChannelVersionViewsetTestCase(StudioAPITestCase):
+ """Test channel_version Viewset."""
+
+ def setUpChannelWithVersions(self, name, version_count):
+ channel = testdata.channel(name=name)
+ channel.version = version_count
+ channel.published = True
+ channel.editors.add(self.user)
+ channel.save()
+
+ for i in range(version_count):
+ ChannelVersion.objects.update_or_create(
+ channel=channel,
+ version=i + 1,
+ defaults={
+ "version_notes": f"Test version {i + 1}",
+ },
+ )
+ return channel
+
+ def setUp(self):
+ super().setUp()
+ self.user = testdata.user()
+ self.client.force_authenticate(user=self.user)
+
+ self.channel = self.setUpChannelWithVersions(
+ name="Test Channel with Versions", version_count=3
+ )
+
+ self.channel_2 = self.setUpChannelWithVersions(
+ name="Test Channel with Versions 2", version_count=5
+ )
+
+ def test_get_channel_version_without_channel_filter_should_fail(self):
+ """Test retrieving channel versions without channel filter."""
+ url = reverse("channelversion-list")
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 412)
+
+ def test_get_channel_version_with_invalid_channel_filter_should_fail(self):
+ """Test retrieving channel versions with invalid channel filter."""
+ url = reverse("channelversion-list") + "?channel=invalid-channel-id"
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 400)
+
+ def test_get_channel_versions_with_valid_channel_filter(self):
+ """Test retrieving channel versions with valid channel filter."""
+ url = reverse("channelversion-list") + f"?channel={self.channel.id}"
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ results = data["results"] if "results" in data else data
+ self.assertEqual(len(results), 3)
+ for version_data in results:
+ self.assertEqual(version_data["channel"], self.channel.id)
+
+ def test_get_specific_channel_version(self):
+ """Test retrieving a specific channel version."""
+ channel_version = ChannelVersion.objects.filter(channel=self.channel).first()
+ url = reverse("channelversion-detail", kwargs={"pk": channel_version.id})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertEqual(data["id"], channel_version.id)
+ self.assertEqual(data["channel"], self.channel.id)
+ self.assertEqual(data["version"], channel_version.version)
+
+ def test_get_channel_versions_ordering(self):
+ """Test ordering of channel versions."""
+ url = (
+ reverse("channelversion-list")
+ + f"?channel={self.channel_2.id}&ordering=version"
+ )
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ results = response.json()
+ versions = [version_data["version"] for version_data in results]
+ self.assertEqual(versions, sorted(versions))
+
+ url_desc = (
+ reverse("channelversion-list")
+ + f"?channel={self.channel_2.id}&ordering=-version"
+ )
+ response_desc = self.client.get(url_desc)
+ self.assertEqual(response_desc.status_code, 200)
+ results_desc = response_desc.json()
+ versions_desc = [version_data["version"] for version_data in results_desc]
+ self.assertEqual(versions_desc, sorted(versions_desc, reverse=True))
+
+ def test_get_channel_versions_filter_by_version(self):
+ """Test filtering channel versions by specific version number."""
+ target_version = 2
+ url = (
+ reverse("channelversion-list")
+ + f"?channel={self.channel.id}&version={target_version}"
+ )
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ results = response.json()
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]["version"], target_version)
+
+ def test_get_channel_versions_filter_by_version_gte(self):
+ """Test filtering channel versions by specific version number."""
+ target_version = 2
+ url = (
+ reverse("channelversion-list")
+ + f"?channel={self.channel.id}&version__gte={target_version}"
+ )
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ results = response.json()
+ self.assertEqual(len(results), 2)
+ for version_data in results:
+ self.assertGreaterEqual(version_data["version"], target_version)
+
+ def test_channel_version_cannot_be_created_via_api(self):
+ """Test that channel versions cannot be created via the API."""
+ url = reverse("channelversion-list")
+ payload = {
+ "channel": self.channel.id,
+ "version": 4,
+ "version_notes": "New version via API",
+ }
+ response = self.client.post(url, payload, format="json")
+ self.assertEqual(response.status_code, 405)
+
+ def test_channel_version_cannot_be_updated_via_api(self):
+ """Test that channel versions cannot be updated via the API."""
+ channel_version = ChannelVersion.objects.filter(channel=self.channel).first()
+ url = reverse("channelversion-detail", kwargs={"pk": channel_version.id})
+ payload = {
+ "version_notes": "Updated version notes via API",
+ }
+ response = self.client.patch(url, payload, format="json")
+ self.assertEqual(response.status_code, 405)
+
+ def test_channel_version_cannot_be_deleted_via_api(self):
+ """Test that channel versions cannot be deleted via the API."""
+ channel_version = ChannelVersion.objects.filter(channel=self.channel).first()
+ url = reverse("channelversion-detail", kwargs={"pk": channel_version.id})
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, 405)
+
+ def test_channel_viewer_can_access_channel_versions(self):
+ """Test that a user with channel viewer permissions can access channel versions."""
+ viewer_user = testdata.user(email="vieweruser@example.com")
+ self.channel.viewers.add(viewer_user)
+ self.client.force_authenticate(user=viewer_user)
+ url = reverse("channelversion-list") + f"?channel={self.channel.id}"
+ response = self.client.get(url)
+ results = response.json()
+ self.assertEqual(len(results), 3)
+
+ def test_non_channel_viewer_cannot_access_channel_versions(self):
+ """Test that a user without channel viewer permissions cannot access channel versions."""
+ other_user = testdata.user(email="otheruser@example.com")
+ self.client.force_authenticate(user=other_user)
+ url = reverse("channelversion-list") + f"?channel={self.channel.id}"
+ response = self.client.get(url)
+ results = response.json()
+ self.assertEqual(len(results), 0)
diff --git a/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py b/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py
new file mode 100644
index 0000000000..4cd51fb2a1
--- /dev/null
+++ b/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py
@@ -0,0 +1,1336 @@
+import datetime
+from unittest import mock
+
+import pytz
+from django.urls import reverse
+
+from contentcuration.constants import (
+ community_library_submission as community_library_submission_constants,
+)
+from contentcuration.models import AuditedSpecialPermissionsLicense
+from contentcuration.models import Change
+from contentcuration.models import Channel
+from contentcuration.models import ChannelVersion
+from contentcuration.models import CommunityLibrarySubmission
+from contentcuration.models import User
+from contentcuration.tests import testdata
+from contentcuration.tests.base import StudioAPITestCase
+from contentcuration.tests.helpers import reverse_with_query
+from contentcuration.viewsets.sync.constants import ADDED_TO_COMMUNITY_LIBRARY
+
+
+class CRUDTestCase(StudioAPITestCase):
+ @property
+ def new_submission_metadata(self):
+ return {
+ "channel": self.channel_without_submission.id,
+ "countries": [self.country1.code],
+ }
+
+ @property
+ def updated_submission_metadata(self):
+ return {
+ "countries": [
+ "C2",
+ ],
+ "channel": self.channel_with_submission1.id,
+ }
+
+ def setUp(self):
+ super().setUp()
+ self.author_user = testdata.user(email="author@user.com")
+ self.editor_user = testdata.user(email="editor@user.com")
+ self.forbidden_user = testdata.user(email="forbidden@user.com")
+ self.admin_user = testdata.user(email="admin@user.com")
+ self.admin_user.is_admin = True
+ self.admin_user.save()
+
+ self.country1 = testdata.country(name="Country 1", code="C1")
+ self.country2 = testdata.country(name="Country 2", code="C2")
+
+ self.channel_with_submission1 = testdata.channel()
+ self.channel_with_submission1.public = False
+ self.channel_with_submission1.version = 1
+ self.channel_with_submission1.editors.add(self.author_user)
+ self.channel_with_submission1.editors.add(self.editor_user)
+ self.channel_with_submission1.save()
+
+ self.channel_with_submission2 = testdata.channel()
+ self.channel_with_submission2.public = False
+ self.channel_with_submission2.version = 1
+ self.channel_with_submission2.editors.add(self.author_user)
+ self.channel_with_submission2.editors.add(self.editor_user)
+ self.channel_with_submission2.save()
+
+ self.channel_without_submission = testdata.channel()
+ self.channel_without_submission.public = False
+ self.channel_without_submission.version = 1
+ self.channel_without_submission.editors.add(self.author_user)
+ self.channel_without_submission.editors.add(self.editor_user)
+ self.channel_without_submission.save()
+
+ self.unpublished_channel = testdata.channel()
+ self.unpublished_channel.public = False
+ self.unpublished_channel.version = 0
+ self.unpublished_channel.editors.add(self.author_user)
+ self.unpublished_channel.editors.add(self.editor_user)
+ self.unpublished_channel.save()
+
+ self.public_channel = testdata.channel()
+ self.public_channel.public = True
+ self.public_channel.version = 1
+ self.public_channel.editors.add(self.author_user)
+ self.public_channel.editors.add(self.editor_user)
+ self.public_channel.save()
+
+ self.existing_submission1 = testdata.community_library_submission()
+ self.existing_submission1.channel = self.channel_with_submission1
+ self.existing_submission1.author = self.author_user
+ self.existing_submission1.save()
+ self.existing_submission1.countries.add(self.country1)
+ self.existing_submission1.save()
+
+ self.existing_submission2 = testdata.community_library_submission()
+ self.existing_submission2.channel = self.channel_with_submission2
+ self.existing_submission2.author = self.author_user
+ self.existing_submission2.save()
+ self.existing_submission2.countries.add(self.country1)
+ self.existing_submission2.save()
+
+ def tearDown(self):
+ super().tearDown()
+
+ def test_create_submission__is_editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+ submission = self.new_submission_metadata
+ response = self.client.post(
+ reverse("community-library-submission-list"),
+ submission,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 201, response.content)
+
+ def test_create_submission__is_forbidden(self):
+ self.client.force_authenticate(user=self.forbidden_user)
+ submission = self.new_submission_metadata
+ response = self.client.post(
+ reverse("community-library-submission-list"),
+ submission,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_create_submission__unpublished_channel(self):
+ self.client.force_authenticate(user=self.editor_user)
+ submission = self.new_submission_metadata
+ submission["channel"] = self.unpublished_channel.id
+
+ response = self.client.post(
+ reverse("community-library-submission-list"),
+ submission,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_create_submission__public_channel(self):
+ self.client.force_authenticate(user=self.editor_user)
+ submission = self.new_submission_metadata
+ submission["channel"] = self.public_channel.id
+
+ response = self.client.post(
+ reverse("community-library-submission-list"),
+ submission,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_create_submission__explicit_channel_version(self):
+ self.client.force_authenticate(user=self.editor_user)
+ submission_metadata = self.new_submission_metadata
+ submission_metadata["channel_version"] = 2
+ response = self.client.post(
+ reverse("community-library-submission-list"),
+ submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 201, response.content)
+
+ created_submission_id = response.data["id"]
+ created_submission = CommunityLibrarySubmission.objects.get(
+ id=created_submission_id
+ )
+
+ # The explicitly set channel version should be ignored by the serializer
+ self.assertEqual(created_submission.channel_version, 1)
+
+ def test_create_submission__publishing_channel(self):
+ self.client.force_authenticate(user=self.editor_user)
+
+ submission_metadata = self.new_submission_metadata
+
+ # Mark the channel referenced by the metadata as publishing
+ from contentcuration.models import Channel
+ from contentcuration.models import ContentNode
+
+ channel = Channel.objects.get(id=submission_metadata["channel"])
+ # Ensure main_tree exists; mark its publishing flag
+ main_tree = channel.main_tree or ContentNode.objects.get(
+ id=channel.main_tree_id
+ )
+ main_tree.publishing = True
+ main_tree.save()
+
+ response = self.client.post(
+ reverse("community-library-submission-list"),
+ submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_list_submissions__is_editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-list",
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 2)
+
+ def test_list_submissions__is_admin(self):
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-list",
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 2)
+
+ def test_list_submissions__is_forbidden(self):
+ self.client.force_authenticate(user=self.forbidden_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-list",
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 0)
+
+ def test_list_submissions__filter_by_channel(self):
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={"channel": self.channel_with_submission1.id},
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]["channel_id"], self.channel_with_submission1.id)
+
+ def test_list_submissions__pagination(self):
+ self.client.force_authenticate(user=self.author_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={"max_results": 1},
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data["results"]
+ more = response.data["more"]
+
+ self.assertEqual(len(results), 1)
+ self.assertIsNotNone(more)
+
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query=more,
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data["results"]
+ more = response.data["more"]
+
+ self.assertEqual(len(results), 1)
+ self.assertIsNone(more)
+
+ def test_get_single_submission__is_editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ result = response.data
+ self.assertEqual(result["id"], self.existing_submission1.id)
+
+ def test_get_single_submission__is_admin(self):
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ result = response.data
+ self.assertEqual(result["id"], self.existing_submission1.id)
+
+ def test_get_single_submission__is_forbidden(self):
+ self.client.force_authenticate(user=self.forbidden_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 404, response.content)
+
+ def test_get_single_submission__author_name(self):
+ self.client.force_authenticate(user=self.author_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ result = response.data
+ self.assertEqual(
+ result["author_name"],
+ f"{self.author_user.first_name} {self.author_user.last_name}",
+ )
+
+ def test_update_submission__is_author(self):
+ self.client.force_authenticate(user=self.author_user)
+ response = self.client.put(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ self.updated_submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ updated_submission = CommunityLibrarySubmission.objects.get(
+ id=self.existing_submission1.id
+ )
+ self.assertEqual(updated_submission.countries.count(), 1)
+ self.assertEqual(updated_submission.countries.first().code, "C2")
+
+ def test_update_submission__is_editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.put(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ self.updated_submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 404, response.content)
+
+ def test_update_submission__is_admin__change_countries(self):
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.put(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ self.updated_submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ updated_submission = CommunityLibrarySubmission.objects.get(
+ id=self.existing_submission1.id
+ )
+ self.assertEqual(updated_submission.countries.count(), 1)
+ self.assertEqual(updated_submission.countries.first().code, "C2")
+
+ def test_update_submission__is_admin__keep_countries(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ updated_submission_metadata = self.updated_submission_metadata.copy()
+ updated_submission_metadata.pop("countries")
+
+ response = self.client.put(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ updated_submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ updated_submission = CommunityLibrarySubmission.objects.get(
+ id=self.existing_submission1.id
+ )
+ self.assertEqual(updated_submission.countries.count(), 1)
+ self.assertEqual(updated_submission.countries.first().code, "C1")
+
+ def test_update_submission__change_channel(self):
+ self.client.force_authenticate(user=self.admin_user)
+ submission_metadata = self.updated_submission_metadata
+ submission_metadata["channel"] = self.channel_without_submission.id
+ response = self.client.put(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_partial_update_submission__missing_channel(self):
+ self.client.force_authenticate(user=self.admin_user)
+ submission_metadata = self.updated_submission_metadata
+ del submission_metadata["channel"]
+ response = self.client.patch(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ submission_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ updated_submission = CommunityLibrarySubmission.objects.get(
+ id=self.existing_submission1.id
+ )
+ self.assertEqual(updated_submission.channel, self.channel_with_submission1)
+
+ def test_delete_submission__is_author(self):
+ self.client.force_authenticate(user=self.author_user)
+ response = self.client.delete(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 204, response.content)
+ self.assertFalse(
+ CommunityLibrarySubmission.objects.filter(
+ id=self.existing_submission1.id
+ ).exists()
+ )
+
+ def test_delete_submission__is_editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.delete(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 404, response.content)
+ self.assertTrue(
+ CommunityLibrarySubmission.objects.filter(
+ id=self.existing_submission1.id
+ ).exists()
+ )
+
+ def test_delete_submission__is_admin(self):
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.delete(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 204, response.content)
+ self.assertFalse(
+ CommunityLibrarySubmission.objects.filter(
+ id=self.existing_submission1.id
+ ).exists()
+ )
+
+ def test_delete_submission__is_forbidden(self):
+ self.client.force_authenticate(user=self.forbidden_user)
+ response = self.client.delete(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.existing_submission1.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 404, response.content)
+ self.assertTrue(
+ CommunityLibrarySubmission.objects.filter(
+ id=self.existing_submission1.id
+ ).exists()
+ )
+
+
+class AdminViewSetTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+
+ self.resolved_time = datetime.datetime(2023, 10, 1, tzinfo=pytz.utc)
+
+ # Mock django.utils.timezone.now for auto_now field updates
+ self.django_timezone_patcher = mock.patch(
+ "django.utils.timezone.now",
+ return_value=self.resolved_time,
+ )
+ self.django_timezone_patcher.start()
+
+ self.submission = testdata.community_library_submission()
+ self.submission.channel.version = 3
+ self.submission.channel.save()
+ self.submission.channel_version = 2
+ self.submission.save()
+
+ # Create the ChannelVersion for this submission (needed when approving)
+ self.channel_version = ChannelVersion.objects.create(
+ channel=self.submission.channel,
+ version=self.submission.channel_version,
+ )
+
+ self.editor_user = self.submission.channel.editors.first()
+
+ self.superseded_submission = CommunityLibrarySubmission.objects.create(
+ channel=self.submission.channel,
+ author=self.editor_user,
+ status=community_library_submission_constants.STATUS_PENDING,
+ date_created=datetime.datetime(2023, 1, 1, tzinfo=pytz.utc),
+ channel_version=1,
+ )
+ self.not_superseded_submission = CommunityLibrarySubmission.objects.create(
+ channel=self.submission.channel,
+ author=self.editor_user,
+ status=community_library_submission_constants.STATUS_PENDING,
+ date_created=datetime.datetime(2024, 1, 1, tzinfo=pytz.utc),
+ channel_version=3,
+ )
+ self.submission_for_other_channel = testdata.community_library_submission()
+ self.submission_for_other_channel.channel_version = 1
+ self.submission_for_other_channel.save()
+
+ self.feedback_notes = "Feedback"
+ self.internal_notes = "Internal notes"
+
+ self.resolve_approve_metadata = {
+ "status": community_library_submission_constants.STATUS_APPROVED,
+ "feedback_notes": self.feedback_notes,
+ "internal_notes": self.internal_notes,
+ }
+ self.resolve_reject_metadata = {
+ "status": community_library_submission_constants.STATUS_REJECTED,
+ "resolution_reason": community_library_submission_constants.REASON_INVALID_METADATA,
+ "feedback_notes": self.feedback_notes,
+ "internal_notes": self.internal_notes,
+ }
+
+ def tearDown(self):
+ self.django_timezone_patcher.stop()
+ super().tearDown()
+
+ def _manually_reject_submission(self):
+ self.submission.status = community_library_submission_constants.STATUS_REJECTED
+ self.submission.resolved_by = self.admin_user
+ self.submission.resolution_reason = (
+ community_library_submission_constants.REASON_INVALID_METADATA
+ )
+ self.submission.feedback_notes = self.feedback_notes
+ self.submission.internal_notes = self.internal_notes
+ self.submission.save()
+
+ def _refresh_submissions_from_db(self):
+ self.submission.refresh_from_db()
+ self.superseded_submission.refresh_from_db()
+ self.not_superseded_submission.refresh_from_db()
+ self.submission_for_other_channel.refresh_from_db()
+
+ def test_list_submissions__admin(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ self._manually_reject_submission()
+
+ response = self.client.get(
+ reverse("admin-community-library-submission-list"),
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 4)
+ rejected_results = [
+ result
+ for result in results
+ if result["status"]
+ == community_library_submission_constants.STATUS_REJECTED
+ ]
+ self.assertEqual(len(rejected_results), 1)
+ result = rejected_results[0]
+
+ self.assertEqual(result["resolved_by_id"], self.admin_user.id)
+ self.assertEqual(
+ result["resolved_by_name"],
+ f"{self.admin_user.first_name} {self.admin_user.last_name}",
+ )
+ self.assertEqual(result["internal_notes"], self.internal_notes)
+
+ def test_list_submissions__editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+
+ self._manually_reject_submission()
+
+ response = self.client.get(
+ reverse("admin-community-library-submission-list"),
+ )
+ self.assertEqual(response.status_code, 403, response.content)
+
+ def test_submission_detail__admin(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ self._manually_reject_submission()
+
+ response = self.client.get(
+ reverse(
+ "admin-community-library-submission-detail",
+ args=[self.submission.id],
+ ),
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ result = response.data
+ self.assertEqual(result["id"], self.submission.id)
+ self.assertEqual(result["resolved_by_id"], self.admin_user.id)
+ self.assertEqual(
+ result["resolved_by_name"],
+ f"{self.admin_user.first_name} {self.admin_user.last_name}",
+ )
+ self.assertEqual(result["internal_notes"], self.internal_notes)
+
+ def test_submission_detail__editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+
+ self._manually_reject_submission()
+
+ response = self.client.get(
+ reverse(
+ "admin-community-library-submission-detail",
+ args=[self.submission.id],
+ ),
+ )
+ self.assertEqual(response.status_code, 403, response.content)
+
+ def test_update_submission(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ response = self.client.put(
+ reverse(
+ "admin-community-library-submission-detail",
+ args=[self.submission.id],
+ ),
+ {},
+ format="json",
+ )
+ self.assertEqual(response.status_code, 405, response.content)
+
+ def test_partial_update_submission(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ response = self.client.patch(
+ reverse(
+ "admin-community-library-submission-detail",
+ args=[self.submission.id],
+ ),
+ {},
+ format="json",
+ )
+ self.assertEqual(response.status_code, 405, response.content)
+
+ def test_destroy_submission(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ response = self.client.delete(
+ reverse(
+ "admin-community-library-submission-detail",
+ args=[self.submission.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 405, response.content)
+
+ def test_resolve_submission__editor(self):
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_approve_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 403, response.content)
+
+ @mock.patch(
+ "contentcuration.viewsets.community_library_submission.apply_channel_changes_task"
+ )
+ def test_resolve_submission__accept_correct(self, apply_task_mock):
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_approve_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ resolved_submission = CommunityLibrarySubmission.objects.get(
+ id=self.submission.id
+ )
+ self.assertEqual(
+ resolved_submission.status,
+ community_library_submission_constants.STATUS_APPROVED,
+ )
+ self.assertEqual(resolved_submission.feedback_notes, self.feedback_notes)
+ self.assertEqual(resolved_submission.internal_notes, self.internal_notes)
+ self.assertEqual(resolved_submission.resolved_by, self.admin_user)
+ self.assertEqual(resolved_submission.date_updated, self.resolved_time)
+
+ change = Change.objects.get(
+ channel=self.submission.channel,
+ change_type=ADDED_TO_COMMUNITY_LIBRARY,
+ )
+ self.assertEqual(change.created_by_id, self.admin_user.id)
+ self.assertTrue(change.unpublishable)
+ apply_task_mock.fetch_or_enqueue.assert_called_once_with(
+ self.admin_user,
+ channel_id=self.submission.channel.id,
+ )
+
+ @mock.patch(
+ "contentcuration.viewsets.community_library_submission.apply_channel_changes_task"
+ )
+ def test_resolve_submission__reject_correct(self, apply_task_mock):
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_reject_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ resolved_submission = CommunityLibrarySubmission.objects.get(
+ id=self.submission.id
+ )
+ self.assertEqual(
+ resolved_submission.status,
+ community_library_submission_constants.STATUS_REJECTED,
+ )
+ self.assertEqual(
+ resolved_submission.resolution_reason,
+ community_library_submission_constants.REASON_INVALID_METADATA,
+ )
+ self.assertEqual(resolved_submission.feedback_notes, self.feedback_notes)
+ self.assertEqual(resolved_submission.internal_notes, self.internal_notes)
+ self.assertEqual(resolved_submission.resolved_by, self.admin_user)
+ self.assertEqual(resolved_submission.date_updated, self.resolved_time)
+
+ self.assertFalse(
+ Change.objects.filter(
+ channel=self.submission.channel,
+ change_type=ADDED_TO_COMMUNITY_LIBRARY,
+ ).exists()
+ )
+ apply_task_mock.fetch_or_enqueue.assert_not_called()
+
+ def test_resolve_submission__reject_missing_resolution_reason(self):
+ self.client.force_authenticate(user=self.admin_user)
+ metadata = self.resolve_reject_metadata.copy()
+ del metadata["resolution_reason"]
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_resolve_submission__reject_missing_feedback_notes(self):
+ self.client.force_authenticate(user=self.admin_user)
+ metadata = self.resolve_reject_metadata.copy()
+ del metadata["feedback_notes"]
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_resolve_submission__invalid_status(self):
+ self.client.force_authenticate(user=self.admin_user)
+ metadata = self.resolve_approve_metadata.copy()
+ metadata["status"] = (community_library_submission_constants.STATUS_PENDING,)
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_resolve_submission__not_pending(self):
+ self.client.force_authenticate(user=self.admin_user)
+ self.submission.status = community_library_submission_constants.STATUS_APPROVED
+ self.submission.save()
+
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_approve_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ @mock.patch(
+ "contentcuration.viewsets.community_library_submission.apply_channel_changes_task"
+ )
+ def test_resolve_submission__overrite_categories(self, apply_task_mock):
+ self.client.force_authenticate(user=self.admin_user)
+ categories = ["Category 1"]
+ self.resolve_approve_metadata["categories"] = categories
+
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_approve_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ resolved_submission = CommunityLibrarySubmission.objects.get(
+ id=self.submission.id
+ )
+ self.assertListEqual(resolved_submission.categories, categories)
+
+ @mock.patch(
+ "contentcuration.viewsets.community_library_submission.apply_channel_changes_task"
+ )
+ def test_resolve_submission__accept_mark_superseded(self, apply_task_mock):
+ self.client.force_authenticate(user=self.admin_user)
+
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_approve_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ self._refresh_submissions_from_db()
+
+ self.assertEqual(
+ self.superseded_submission.status,
+ community_library_submission_constants.STATUS_SUPERSEDED,
+ )
+ self.assertEqual(
+ self.not_superseded_submission.status,
+ community_library_submission_constants.STATUS_PENDING,
+ )
+ self.assertEqual(
+ self.submission_for_other_channel.status,
+ community_library_submission_constants.STATUS_PENDING,
+ )
+
+ def test_resolve_submission__reject_do_not_mark_superseded(self):
+ self.client.force_authenticate(user=self.admin_user)
+
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_reject_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ self._refresh_submissions_from_db()
+
+ self.assertEqual(
+ self.superseded_submission.status,
+ community_library_submission_constants.STATUS_PENDING,
+ )
+ self.assertEqual(
+ self.not_superseded_submission.status,
+ community_library_submission_constants.STATUS_PENDING,
+ )
+ self.assertEqual(
+ self.submission_for_other_channel.status,
+ community_library_submission_constants.STATUS_PENDING,
+ )
+
+ @mock.patch(
+ "contentcuration.viewsets.community_library_submission.apply_channel_changes_task"
+ )
+ def test_resolve_submission__approve_marks_special_permissions_distributable(
+ self, apply_task_mock
+ ):
+ """Test that approving a submission marks special permissions as distributable."""
+ self.client.force_authenticate(user=self.admin_user)
+
+ # Create an audited special permissions license
+ special_license = AuditedSpecialPermissionsLicense.objects.create(
+ description="Community library special permissions"
+ )
+ self.assertFalse(special_license.distributable)
+
+ # Add the special permission to the channel version
+ self.channel_version.special_permissions_included.add(special_license)
+
+ # Approve the submission
+ response = self.client.post(
+ reverse(
+ "admin-community-library-submission-resolve",
+ args=[self.submission.id],
+ ),
+ self.resolve_approve_metadata,
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ # Verify the special permission is marked as distributable
+ special_license.refresh_from_db()
+ self.assertTrue(special_license.distributable)
+ self.assertTrue(
+ self.channel_version.special_permissions_included.filter(
+ id=special_license.id
+ ).exists()
+ )
+
+
+class FilteringAndSearchTestCase(StudioAPITestCase):
+ """
+ Test cases for the new filtering and search functionality added for the notifications page.
+ Tests date_updated filters, status__in filter, and channel name search.
+ """
+
+ def setUp(self):
+ super().setUp()
+ self.editor_user = testdata.user(email="editor@user.com")
+ self.admin_user = testdata.user(email="admin@user.com")
+ self.admin_user.is_admin = True
+ self.admin_user.first_name = "Admin"
+ self.admin_user.last_name = "User"
+ self.admin_user.save()
+
+ self.math_channel = testdata.channel(name="Math Basics")
+ self.math_channel.public = False
+ self.math_channel.version = 1
+ self.math_channel.editors.add(self.editor_user)
+ self.math_channel.save()
+
+ self.science_channel = testdata.channel(name="Science Fundamentals")
+ self.science_channel.public = False
+ self.science_channel.version = 1
+ self.science_channel.editors.add(self.editor_user)
+ self.science_channel.save()
+
+ self.history_channel = testdata.channel(name="World History")
+ self.history_channel.public = False
+ self.history_channel.version = 1
+ self.history_channel.editors.add(self.editor_user)
+ self.history_channel.save()
+
+ self.math_advanced_channel = testdata.channel(name="Advanced Math")
+ self.math_advanced_channel.public = False
+ self.math_advanced_channel.version = 1
+ self.math_advanced_channel.editors.add(self.editor_user)
+ self.math_advanced_channel.save()
+
+ with mock.patch(
+ "django.utils.timezone.now",
+ return_value=datetime.datetime(2023, 1, 1, tzinfo=pytz.utc),
+ ):
+ self.old_pending_submission = CommunityLibrarySubmission.objects.create(
+ channel=self.math_channel,
+ author=self.editor_user,
+ channel_version=1,
+ status=community_library_submission_constants.STATUS_PENDING,
+ date_created=datetime.datetime(2023, 1, 1, tzinfo=pytz.utc),
+ )
+
+ with mock.patch(
+ "django.utils.timezone.now",
+ return_value=datetime.datetime(2024, 6, 15, tzinfo=pytz.utc),
+ ):
+ self.recent_approved_submission = CommunityLibrarySubmission.objects.create(
+ channel=self.science_channel,
+ author=self.editor_user,
+ channel_version=1,
+ status=community_library_submission_constants.STATUS_APPROVED,
+ date_created=datetime.datetime(2024, 6, 1, tzinfo=pytz.utc),
+ resolved_by=self.admin_user,
+ )
+
+ with mock.patch(
+ "django.utils.timezone.now",
+ return_value=datetime.datetime(2024, 7, 10, tzinfo=pytz.utc),
+ ):
+ self.recent_rejected_submission = CommunityLibrarySubmission.objects.create(
+ channel=self.history_channel,
+ author=self.editor_user,
+ channel_version=1,
+ status=community_library_submission_constants.STATUS_REJECTED,
+ date_created=datetime.datetime(2024, 7, 1, tzinfo=pytz.utc),
+ resolved_by=self.admin_user,
+ )
+
+ with mock.patch(
+ "django.utils.timezone.now",
+ return_value=datetime.datetime(2024, 12, 1, tzinfo=pytz.utc),
+ ):
+ self.very_recent_pending_submission = (
+ CommunityLibrarySubmission.objects.create(
+ channel=self.math_advanced_channel,
+ author=self.editor_user,
+ channel_version=1,
+ status=community_library_submission_constants.STATUS_PENDING,
+ date_created=datetime.datetime(2024, 12, 1, tzinfo=pytz.utc),
+ )
+ )
+
+ def tearDown(self):
+ super().tearDown()
+
+ def test_filter_by_date_updated_gte(self):
+ """Test filtering submissions updated on or after a specific date"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={"date_updated__gte": "2024-06-01"},
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 3)
+ submission_ids = [r["id"] for r in results]
+ self.assertNotIn(self.old_pending_submission.id, submission_ids)
+
+ def test_filter_by_date_updated_lte(self):
+ """Test filtering submissions updated on or before a specific date"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={"date_updated__lte": "2024-06-30"},
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 2)
+ submission_ids = [r["id"] for r in results]
+ self.assertIn(self.old_pending_submission.id, submission_ids)
+ self.assertIn(self.recent_approved_submission.id, submission_ids)
+
+ def test_filter_by_date_updated_range(self):
+ """Test filtering submissions within a date range"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={
+ "date_updated__gte": "2024-06-01",
+ "date_updated__lte": "2024-07-31",
+ },
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 2)
+ submission_ids = [r["id"] for r in results]
+ self.assertIn(self.recent_approved_submission.id, submission_ids)
+ self.assertIn(self.recent_rejected_submission.id, submission_ids)
+
+ def test_filter_by_status_in_single(self):
+ """Test filtering by a single status"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={
+ "status__in": community_library_submission_constants.STATUS_APPROVED
+ },
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]["id"], self.recent_approved_submission.id)
+ self.assertEqual(
+ results[0]["status"],
+ community_library_submission_constants.STATUS_APPROVED,
+ )
+
+ def test_filter_by_status_in_multiple(self):
+ """Test filtering by multiple statuses"""
+ self.client.force_authenticate(user=self.editor_user)
+ url = reverse("community-library-submission-list")
+ response = self.client.get(
+ f"{url}?status__in={community_library_submission_constants.STATUS_APPROVED},{community_library_submission_constants.STATUS_REJECTED}"
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 2)
+ statuses = [r["status"] for r in results]
+ self.assertIn(community_library_submission_constants.STATUS_APPROVED, statuses)
+ self.assertIn(community_library_submission_constants.STATUS_REJECTED, statuses)
+ self.assertNotIn(
+ community_library_submission_constants.STATUS_PENDING, statuses
+ )
+
+ def test_search_by_channel_name(self):
+ """Test searching submissions by channel name"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={"search": "Math"},
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 2)
+ channel_ids = [r["channel_id"] for r in results]
+ self.assertIn(self.math_channel.id, channel_ids)
+ self.assertIn(self.math_advanced_channel.id, channel_ids)
+
+ def test_search_by_channel_name_partial(self):
+ """Test searching with partial channel name"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={"search": "Science"},
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]["channel_id"], self.science_channel.id)
+
+ def test_combined_filters(self):
+ """Test combining multiple filters together"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse_with_query(
+ "community-library-submission-list",
+ query={
+ "date_updated__gte": "2024-01-01",
+ "status__in": community_library_submission_constants.STATUS_PENDING,
+ "search": "Math",
+ },
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]["id"], self.very_recent_pending_submission.id)
+ self.assertEqual(
+ results[0]["status"],
+ community_library_submission_constants.STATUS_PENDING,
+ )
+ self.assertEqual(results[0]["channel_id"], self.math_advanced_channel.id)
+
+ def test_resolved_by_name_visible_to_editor(self):
+ """Test that editors can now see who resolved their submissions"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.recent_approved_submission.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ result = response.data
+ self.assertIn("resolved_by_id", result)
+ self.assertIn("resolved_by_name", result)
+ self.assertEqual(result["resolved_by_id"], self.admin_user.id)
+ self.assertEqual(
+ result["resolved_by_name"],
+ f"{self.admin_user.first_name} {self.admin_user.last_name}",
+ )
+
+ def test_resolved_by_name_null_for_unresolved(self):
+ """Test that resolved_by_name is None for unresolved submissions"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse(
+ "community-library-submission-detail",
+ args=[self.old_pending_submission.id],
+ ),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ result = response.data
+ self.assertIsNone(result.get("resolved_by_id"))
+ self.assertIsNone(result.get("resolved_by_name"))
+
+ def test_pagination_ordering_by_date_updated(self):
+ """Test that results are ordered by -date_updated (most recent first)"""
+ self.client.force_authenticate(user=self.editor_user)
+ response = self.client.get(
+ reverse("community-library-submission-list"),
+ format="json",
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 4)
+ self.assertEqual(results[0]["id"], self.very_recent_pending_submission.id)
+ self.assertEqual(results[3]["id"], self.old_pending_submission.id)
+
+ def test_admin_can_use_filters(self):
+ """Test that admin users can also use the new filters"""
+ self.client.force_authenticate(user=self.admin_user)
+ response = self.client.get(
+ reverse_with_query(
+ "admin-community-library-submission-list",
+ query={
+ "date_updated__gte": "2024-06-01",
+ "status__in": community_library_submission_constants.STATUS_APPROVED,
+ },
+ )
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+
+ results = response.data
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results[0]["id"], self.recent_approved_submission.id)
+
+
+class CommunityLibrarySubmissionChannelVersionTestCase(StudioAPITestCase):
+ """Test CommunityLibrarySubmission creates ChannelVersion and tokens."""
+
+ def setUp(self):
+ self.user = User.objects.create(email="test@test.com", is_admin=True)
+ self.channel = Channel.objects.create(
+ name="Test Channel",
+ version=10,
+ actor_id=self.user.id,
+ )
+ self.channel.editors.add(self.user)
+
+ def test_submission_creates_channel_version(self):
+ """Test that creating a submission creates a ChannelVersion."""
+ initial_count = ChannelVersion.objects.filter(channel=self.channel).count()
+
+ submission = CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=5,
+ author=self.user,
+ description="Test submission",
+ )
+
+ self.assertIsNotNone(submission)
+ self.assertEqual(
+ ChannelVersion.objects.filter(channel=self.channel).count(),
+ initial_count + 1,
+ )
+
+ channel_version = ChannelVersion.objects.get(channel=self.channel, version=5)
+ self.assertIsNotNone(channel_version)
+
+ def test_submission_creates_token(self):
+ """Test that creating a submission creates a token for the ChannelVersion."""
+ submission = CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=5,
+ author=self.user,
+ description="Test submission",
+ )
+
+ self.assertIsNotNone(submission)
+ channel_version = ChannelVersion.objects.get(channel=self.channel, version=5)
+
+ self.assertIsNotNone(channel_version.secret_token)
+ self.assertFalse(channel_version.secret_token.is_primary)
+
+ def test_submissions_different_versions(self):
+ """Test that submissions for different versions create different tokens."""
+ self.channel.version = 6
+ self.channel.save()
+
+ submission1 = CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=5,
+ author=self.user,
+ description="Version 5 submission",
+ )
+
+ submission2 = CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=6,
+ author=self.user,
+ description="Version 6 submission",
+ )
+
+ self.assertIsNotNone(submission1)
+ self.assertIsNotNone(submission2)
+ v5 = ChannelVersion.objects.get(channel=self.channel, version=5)
+ v6 = ChannelVersion.objects.get(channel=self.channel, version=6)
+
+ self.assertIsNotNone(v5.secret_token)
+ self.assertIsNotNone(v6.secret_token)
+ self.assertNotEqual(v5.secret_token, v6.secret_token)
diff --git a/contentcuration/contentcuration/tests/viewsets/test_user.py b/contentcuration/contentcuration/tests/viewsets/test_user.py
index 5e8554f35a..914404bf24 100644
--- a/contentcuration/contentcuration/tests/viewsets/test_user.py
+++ b/contentcuration/contentcuration/tests/viewsets/test_user.py
@@ -173,3 +173,65 @@ def test_fetch_users_no_permissions(self):
)
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response.json(), [])
+
+
+class MarkReadNotificationsTimestampTestCase(StudioAPITestCase):
+ def setUp(self):
+ super(MarkReadNotificationsTimestampTestCase, self).setUp()
+ self.user = testdata.user()
+
+ def test_mark_read_notifications_timestamp_success(self):
+ self.client.force_authenticate(user=self.user)
+ response = self.client.post(
+ reverse("user-mark-notifications-read"),
+ data={"timestamp": "2023-12-16T10:00:00Z"},
+ format="json",
+ )
+ self.assertEqual(response.status_code, 204, response.content)
+
+ def test_mark_read_notifications_timestamp_invalid_format(self):
+ self.client.force_authenticate(user=self.user)
+ response = self.client.post(
+ reverse("user-mark-notifications-read"),
+ data={"timestamp": "invalid-timestamp"},
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_mark_read_notifications_timestamp_missing_timestamp(self):
+ self.client.force_authenticate(user=self.user)
+ response = self.client.post(
+ reverse("user-mark-notifications-read"),
+ data={},
+ format="json",
+ )
+ self.assertEqual(response.status_code, 400, response.content)
+
+ def test_mark_read_notifications_timestamp_unauthenticated(self):
+ response = self.client.post(
+ reverse("user-mark-notifications-read"),
+ data={"timestamp": "2023-12-16T10:00:00Z"},
+ format="json",
+ )
+ self.assertEqual(response.status_code, 403, response.content)
+
+ def test_mark_read_notifications_timestamp_updates_field(self):
+ timestamp = "2023-12-16T10:00:00Z"
+ self.client.force_authenticate(user=self.user)
+
+ response = self.client.post(
+ reverse("user-mark-notifications-read"),
+ data={"timestamp": timestamp},
+ format="json",
+ )
+
+ self.assertEqual(response.status_code, 204, response.content)
+
+ # Refresh user from database and check the timestamp was updated
+ self.user.refresh_from_db()
+ # Check that the last_read_notification_date field was updated
+ self.assertIsNotNone(self.user.last_read_notification_date)
+ self.assertEqual(
+ self.user.last_read_notification_date.isoformat(),
+ timestamp.replace("Z", "+00:00"),
+ )
diff --git a/contentcuration/contentcuration/urls.py b/contentcuration/contentcuration/urls.py
index 720bbc3d55..6f36a5ac68 100644
--- a/contentcuration/contentcuration/urls.py
+++ b/contentcuration/contentcuration/urls.py
@@ -30,16 +30,27 @@
import contentcuration.views.internal as internal_views
import contentcuration.views.nodes as node_views
import contentcuration.views.settings as settings_views
+import contentcuration.views.subscription as subscription_views
import contentcuration.views.users as registration_views
import contentcuration.views.zip as zip_views
from contentcuration.views import pwa
from contentcuration.viewsets.assessmentitem import AssessmentItemViewSet
+from contentcuration.viewsets.audited_special_permissions_license import (
+ AuditedSpecialPermissionsLicenseViewSet,
+)
from contentcuration.viewsets.bookmark import BookmarkViewSet
from contentcuration.viewsets.channel import AdminChannelViewSet
from contentcuration.viewsets.channel import CatalogViewSet
+from contentcuration.viewsets.channel import ChannelVersionViewSet
from contentcuration.viewsets.channel import ChannelViewSet
from contentcuration.viewsets.channelset import ChannelSetViewSet
from contentcuration.viewsets.clipboard import ClipboardViewSet
+from contentcuration.viewsets.community_library_submission import (
+ AdminCommunityLibrarySubmissionViewSet,
+)
+from contentcuration.viewsets.community_library_submission import (
+ CommunityLibrarySubmissionViewSet,
+)
from contentcuration.viewsets.contentnode import ContentNodeViewSet
from contentcuration.viewsets.feedback import FlagFeedbackEventViewSet
from contentcuration.viewsets.feedback import RecommendationsEventViewSet
@@ -87,6 +98,26 @@ def get_redirect_url(self, *args, **kwargs):
RecommendationsInteractionEventViewSet,
basename="recommendations-interaction-events",
)
+router.register(
+ r"communitylibrary_submission",
+ CommunityLibrarySubmissionViewSet,
+ basename="community-library-submission",
+)
+router.register(
+ r"admin_communitylibrary_submission",
+ AdminCommunityLibrarySubmissionViewSet,
+ basename="admin-community-library-submission",
+)
+router.register(
+ r"audited-special-permissions-license",
+ AuditedSpecialPermissionsLicenseViewSet,
+ basename="audited-special-permissions-license",
+)
+router.register(
+ r"channelversion",
+ ChannelVersionViewSet,
+ basename="channelversion",
+)
urlpatterns = [
re_path(r"^api/", include(router.urls)),
@@ -279,6 +310,30 @@ def get_redirect_url(self, *args, **kwargs):
),
]
+# Add Stripe subscription endpoints
+urlpatterns += [
+ re_path(
+ r"^api/stripe/create-checkout-session/$",
+ subscription_views.CreateCheckoutSessionView.as_view(),
+ name="stripe_create_checkout_session",
+ ),
+ re_path(
+ r"^api/stripe/create-portal-session/$",
+ subscription_views.CreatePortalSessionView.as_view(),
+ name="stripe_create_portal_session",
+ ),
+ re_path(
+ r"^api/stripe/subscription-status/$",
+ subscription_views.SubscriptionStatusView.as_view(),
+ name="stripe_subscription_status",
+ ),
+ re_path(
+ r"^api/stripe/webhook/$",
+ subscription_views.stripe_webhook,
+ name="stripe_webhook",
+ ),
+]
+
urlpatterns += [
re_path(r"^jsreverse/$", django_js_reverse_views.urls_js, name="js_reverse")
]
diff --git a/contentcuration/contentcuration/utils/publish.py b/contentcuration/contentcuration/utils/publish.py
index 9e9e190105..6e18dea57e 100644
--- a/contentcuration/contentcuration/utils/publish.py
+++ b/contentcuration/contentcuration/utils/publish.py
@@ -35,6 +35,7 @@
from le_utils.constants import exercises
from le_utils.constants import file_formats
from le_utils.constants import format_presets
+from le_utils.constants import licenses
from le_utils.constants import modalities
from le_utils.constants import roles
from search.models import ChannelFullTextSearch
@@ -145,20 +146,24 @@ def create_content_database(
user_id,
force_exercises,
progress_tracker=None,
+ is_draft_version=False,
use_staging_tree=False,
):
"""
:type progress_tracker: contentcuration.utils.celery.ProgressTracker|None
"""
+ if not is_draft_version and use_staging_tree:
+ raise ValueError("Staging tree is only supported for draft versions")
+
if not channel.language:
raise ChannelIncompleteError("Channel must have a language set to be published")
- if not use_staging_tree and not force:
+ if not is_draft_version and not force:
raise_if_nodes_are_all_unchanged(channel)
fh, tempdb = tempfile.mkstemp(suffix=".sqlite3")
with using_content_database(tempdb):
- if not use_staging_tree and not channel.main_tree.publishing:
+ if not is_draft_version and not channel.main_tree.publishing:
channel.mark_publishing(user_id)
call_command(
@@ -178,17 +183,19 @@ def create_content_database(
inherit_metadata=bool(channel.ricecooker_version),
)
tree_mapper.map_nodes()
- kolibri_channel = map_channel_to_kolibri_channel(channel, use_staging_tree)
+ kolibri_channel = map_channel_to_kolibri_channel(
+ channel, use_staging_tree, is_draft_version=is_draft_version
+ )
# It should be at this percent already, but just in case.
if progress_tracker:
progress_tracker.track(90)
map_prerequisites(base_tree)
# Need to save as version being published, not current version
- version = "next" if use_staging_tree else channel.version + 1
+ version = "next" if is_draft_version else channel.version + 1
save_export_database(
channel.pk,
version,
- use_staging_tree,
+ is_draft_version,
)
if channel.public:
mapper = ChannelMapper(kolibri_channel)
@@ -208,10 +215,24 @@ def create_kolibri_license_object(ccnode):
def increment_channel_version(channel):
+
channel.version += 1
channel.save()
+def create_draft_channel_version(channel):
+
+ channel_version, created = ccmodels.ChannelVersion.objects.get_or_create(
+ channel=channel,
+ version=None,
+ )
+
+ if created:
+ channel_version.new_token()
+
+ return channel_version
+
+
def assign_license_to_contentcuration_nodes(channel, license):
channel.main_tree.get_family().update(license_id=license.pk)
@@ -777,7 +798,9 @@ def map_prerequisites(root_node):
)
-def map_channel_to_kolibri_channel(channel, use_staging_tree=False):
+def map_channel_to_kolibri_channel(
+ channel, use_staging_tree=False, is_draft_version=False
+):
logging.debug("Generating the channel metadata.")
base_tree = channel.staging_tree if use_staging_tree else channel.main_tree
kolibri_channel = kolibrimodels.ChannelMetadata.objects.create(
@@ -785,8 +808,7 @@ def map_channel_to_kolibri_channel(channel, use_staging_tree=False):
name=channel.name,
description=channel.description,
tagline=channel.tagline,
- version=channel.version
- + 1, # Need to save as version being published, not current version
+ version=0 if is_draft_version else channel.version + 1,
thumbnail=channel.icon_encoding,
root_pk=base_tree.node_id,
root_id=base_tree.node_id,
@@ -870,17 +892,19 @@ def mark_all_nodes_as_published(tree):
logging.info("Marked all nodes as published.")
-def save_export_database(channel_id, version, use_staging_tree=False):
+def get_content_db_path(channel_id, version=None):
+ if version is not None:
+ return os.path.join(settings.DB_ROOT, f"{channel_id}-{version}.sqlite3")
+ return os.path.join(settings.DB_ROOT, f"{channel_id}.sqlite3")
+
+
+def save_export_database(channel_id, version, is_draft_version=False):
logging.debug("Saving export database")
current_export_db_location = get_active_content_database()
- target_paths = [
- os.path.join(settings.DB_ROOT, "{}-{}.sqlite3".format(channel_id, version))
- ]
- # Only create non-version path if not using the staging tree
- if not use_staging_tree:
- target_paths.append(
- os.path.join(settings.DB_ROOT, "{id}.sqlite3".format(id=channel_id))
- )
+ target_paths = [get_content_db_path(channel_id, version)]
+ # Only create non-version path if not is_draft_version
+ if not is_draft_version:
+ target_paths.append(get_content_db_path(channel_id))
for target_export_db_location in target_paths:
with open(current_export_db_location, "rb") as currentf:
@@ -894,23 +918,22 @@ def add_tokens_to_channel(channel):
channel.make_token()
-def fill_published_fields(channel, version_notes):
- channel.last_published = timezone.now()
+def fill_published_fields(channel, version_notes, draft_channel_version=None):
+ is_draft = draft_channel_version is not None
+ date_now = timezone.now()
+
published_nodes = (
channel.main_tree.get_descendants()
.filter(published=True)
.prefetch_related("files")
)
- channel.total_resource_count = published_nodes.exclude(
- kind_id=content_kinds.TOPIC
- ).count()
+ total_resource_count = published_nodes.exclude(kind_id=content_kinds.TOPIC).count()
kind_counts = list(
published_nodes.values("kind_id")
.annotate(count=Count("kind_id"))
.order_by("kind_id")
)
- channel.published_kind_count = json.dumps(kind_counts)
- channel.published_size = (
+ published_size = (
published_nodes.values("files__checksum", "files__file_size")
.distinct()
.aggregate(resource_size=Sum("files__file_size"))["resource_size"]
@@ -920,29 +943,126 @@ def fill_published_fields(channel, version_notes):
node_languages = published_nodes.exclude(language=None).values_list(
"language", flat=True
)
- file_languages = published_nodes.values_list("files__language", flat=True)
+ file_languages = published_nodes.exclude(files__language=None).values_list(
+ "files__language", flat=True
+ )
language_list = list(set(chain(node_languages, file_languages)))
- for lang in language_list:
- if lang:
- channel.included_languages.add(lang)
+ included_licenses = published_nodes.exclude(license=None).values_list(
+ "license", flat=True
+ )
+ license_list = sorted(set(included_licenses))
- # TODO: Eventually, consolidate above operations to just use this field for storing historical data
- channel.published_data.update(
- {
- channel.version: {
- "resource_count": channel.total_resource_count,
- "kind_count": kind_counts,
- "size": channel.published_size,
- "date_published": channel.last_published.strftime(
- settings.DATE_TIME_FORMAT
- ),
- "version_notes": version_notes,
- "included_languages": language_list,
- }
- }
+ included_categories_dicts = published_nodes.exclude(categories=None).values_list(
+ "categories", flat=True
+ )
+ category_list = sorted(
+ set(
+ chain.from_iterable(
+ (
+ node_categories_dict.keys()
+ for node_categories_dict in included_categories_dicts
+ )
+ )
+ )
+ )
+
+ # Calculate non-distributable licenses (All Rights Reserved)
+ all_rights_reserved_id = (
+ ccmodels.License.objects.filter(license_name=licenses.ALL_RIGHTS_RESERVED)
+ .values_list("id", flat=True)
+ .first()
+ )
+
+ non_distributable_licenses_included = (
+ [all_rights_reserved_id]
+ if all_rights_reserved_id and all_rights_reserved_id in license_list
+ else []
+ )
+
+ # records for each unique description so reviewers can approve/reject them individually.
+ # This allows centralized tracking of custom licenses across all channels.
+ special_permissions_id = (
+ ccmodels.License.objects.filter(license_name=licenses.SPECIAL_PERMISSIONS)
+ .values_list("id", flat=True)
+ .first()
)
- channel.save()
+
+ special_perms_descriptions = None
+ if special_permissions_id and special_permissions_id in license_list:
+ special_perms_descriptions = list(
+ published_nodes.filter(license_id=special_permissions_id)
+ .exclude(license_description__isnull=True)
+ .exclude(license_description="")
+ .values_list("license_description", flat=True)
+ .distinct()
+ )
+
+ if special_perms_descriptions:
+ new_licenses = [
+ ccmodels.AuditedSpecialPermissionsLicense(
+ description=description, distributable=False
+ )
+ for description in special_perms_descriptions
+ ]
+
+ ccmodels.AuditedSpecialPermissionsLicense.objects.bulk_create(
+ new_licenses, ignore_conflicts=True
+ )
+
+ if not is_draft:
+ channel.last_published = date_now
+ channel.total_resource_count = total_resource_count
+ channel.published_kind_count = json.dumps(kind_counts)
+ channel.published_size = published_size
+
+ channel.included_languages.set([lang for lang in language_list if lang])
+
+ # TODO: Eventually, consolidate above operations to just use this field for storing historical data
+ channel.published_data.update(
+ {
+ channel.version: {
+ "resource_count": total_resource_count,
+ "kind_count": kind_counts,
+ "size": published_size,
+ "date_published": date_now.strftime(settings.DATE_TIME_FORMAT),
+ "version_notes": version_notes,
+ "included_languages": language_list,
+ "included_licenses": license_list,
+ "included_categories": category_list,
+ }
+ }
+ )
+ channel.save()
+
+ version_obj = draft_channel_version if is_draft else channel.version_info
+ if version_obj is not None:
+ version_obj.resource_count = total_resource_count
+ version_obj.kind_count = kind_counts
+ version_obj.size = int(published_size)
+ version_obj.date_published = date_now
+ version_obj.version_notes = version_notes
+ version_obj.included_languages = language_list
+ version_obj.included_licenses = license_list
+ version_obj.included_categories = category_list
+ version_obj.non_distributable_licenses_included = (
+ non_distributable_licenses_included
+ )
+ version_obj.save()
+
+ if special_perms_descriptions:
+ version_obj.special_permissions_included.set(
+ ccmodels.AuditedSpecialPermissionsLicense.objects.filter(
+ description__in=special_perms_descriptions
+ )
+ )
+ else:
+ version_obj.special_permissions_included.clear()
+
+ if not is_draft and channel.public:
+ ccmodels.AuditedSpecialPermissionsLicense.mark_channel_version_as_distributable(
+ version_obj.id
+ )
def sync_contentnode_and_channel_tsvectors(channel_id):
@@ -1019,6 +1139,7 @@ def publish_channel( # noqa: C901
send_email=False,
progress_tracker=None,
language=settings.LANGUAGE_CODE,
+ is_draft_version=False,
use_staging_tree=False,
):
"""
@@ -1039,24 +1160,35 @@ def publish_channel( # noqa: C901
user_id,
force_exercises,
progress_tracker=progress_tracker,
+ is_draft_version=is_draft_version,
use_staging_tree=use_staging_tree,
)
add_tokens_to_channel(channel)
- if not use_staging_tree:
+ if is_draft_version:
+ draft_channel_version = create_draft_channel_version(channel)
+ fill_published_fields(
+ channel, version_notes, draft_channel_version=draft_channel_version
+ )
+ else:
increment_channel_version(channel)
+ ccmodels.ChannelVersion.objects.filter(
+ channel=channel, version=None
+ ).delete()
+ draft_db_path = get_content_db_path(channel_id, "next")
+ if storage.exists(draft_db_path):
+ storage.delete(draft_db_path)
+
sync_contentnode_and_channel_tsvectors(channel_id=channel.id)
mark_all_nodes_as_published(base_tree)
fill_published_fields(channel, version_notes)
+ base_tree.publishing = False
+ base_tree.changed = False
+ base_tree.published = True
+ base_tree.save()
- # Attributes not getting set for some reason, so just save it here
- base_tree.publishing = False
- base_tree.changed = False
- base_tree.published = True
- base_tree.save()
-
- # Delete public channel cache.
- if not use_staging_tree and channel.public:
- delete_public_channel_cache_keys()
+ # Delete public channel cache.
+ if channel.public:
+ delete_public_channel_cache_keys()
if send_email:
with override(language):
@@ -1077,8 +1209,9 @@ def publish_channel( # noqa: C901
finally:
if kolibri_temp_db and os.path.exists(kolibri_temp_db):
os.remove(kolibri_temp_db)
- base_tree.publishing = False
- base_tree.save()
+ if not is_draft_version:
+ base_tree.publishing = False
+ base_tree.save()
elapsed = time.time() - start
@@ -1090,3 +1223,36 @@ def publish_channel( # noqa: C901
except SlowPublishError as e:
report_exception(e)
return channel
+
+
+def ensure_versioned_database_exists(channel_id, channel_version):
+ """
+ Ensures that the versioned database exists, and if not, copies the unversioned database to the versioned path.
+ This happens if the channel was published back when versioned databases were not used.
+ """
+ if channel_version == 0:
+ raise ValueError("An unpublished channel cannot have a versioned database.")
+
+ unversioned_db_storage_path = get_content_db_path(channel_id)
+ versioned_db_storage_path = get_content_db_path(channel_id, channel_version)
+
+ if not storage.exists(versioned_db_storage_path):
+ if not storage.exists(unversioned_db_storage_path):
+ # This should never happen, a published channel should always have an unversioned database
+ raise FileNotFoundError(
+ f"Neither unversioned nor versioned database found for channel {channel_id}."
+ )
+
+ # NOTE: This should not result in a race condition in the case that a newer
+ # version of the channel is published before the task running this function
+ # is executed. In that case, the publishing logic would have already created
+ # the versioned database. The only case where this could be problematic is
+ # if this happens between the check above this comment and the commands below
+ # it. However, this is EXTREMELY unlikely, and could probably only be solved
+ # by introducing a locking mechanism for the database storage objects.
+ with storage.open(unversioned_db_storage_path, "rb") as unversioned_db_file:
+ storage.save(versioned_db_storage_path, unversioned_db_file)
+
+ logging.info(
+ f"Versioned database for channel {channel_id} did not exist, copied the unversioned database to {versioned_db_storage_path}."
+ )
diff --git a/contentcuration/contentcuration/views/admin.py b/contentcuration/contentcuration/views/admin.py
index 8e674c3ac4..0dc4ece51b 100644
--- a/contentcuration/contentcuration/views/admin.py
+++ b/contentcuration/contentcuration/views/admin.py
@@ -25,7 +25,10 @@ def send_custom_email(request):
data = json.loads(request.body)
try:
sendcustomemails_task.enqueue(
- request.user, data["subject"], data["message"], data["query"]
+ request.user,
+ subject=data["subject"],
+ message=data["message"],
+ query=data["query"],
)
except KeyError:
raise ObjectDoesNotExist("Missing attribute from data: {}".format(data))
diff --git a/contentcuration/contentcuration/views/base.py b/contentcuration/contentcuration/views/base.py
index 082d96376d..327128f163 100644
--- a/contentcuration/contentcuration/views/base.py
+++ b/contentcuration/contentcuration/views/base.py
@@ -76,6 +76,8 @@
"clipboard_tree_id",
"policies",
"feature_flags",
+ "newest_notification_date",
+ "last_read_notification_date",
)
@@ -85,6 +87,7 @@ def current_user_for_context(user):
user_data = {field: getattr(user, field) for field in user_fields}
+ user_data["disk_space"] = user.get_effective_disk_space()
user_data["user_rev"] = user.get_server_rev()
return json_for_parse_from_data(user_data)
diff --git a/contentcuration/contentcuration/views/json_dump.py b/contentcuration/contentcuration/views/json_dump.py
index 0678420ecd..81b52e1209 100644
--- a/contentcuration/contentcuration/views/json_dump.py
+++ b/contentcuration/contentcuration/views/json_dump.py
@@ -1,5 +1,6 @@
import json
+from django.core.serializers.json import DjangoJSONEncoder
from rest_framework.renderers import JSONRenderer
"""
@@ -15,7 +16,9 @@ def _json_dumps(value):
"""
json.dumps parameters for dumping unicode into JS
"""
- return json.dumps(value, separators=(",", ":"), ensure_ascii=False)
+ return json.dumps(
+ value, separators=(",", ":"), ensure_ascii=False, cls=DjangoJSONEncoder
+ )
def json_for_parse_from_data(data):
diff --git a/contentcuration/contentcuration/views/subscription.py b/contentcuration/contentcuration/views/subscription.py
new file mode 100644
index 0000000000..784ba29798
--- /dev/null
+++ b/contentcuration/contentcuration/views/subscription.py
@@ -0,0 +1,286 @@
+import logging
+from datetime import datetime
+from datetime import timezone
+
+import stripe
+from django.conf import settings
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_POST
+from rest_framework import serializers
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from contentcuration.models import User
+from contentcuration.models import UserSubscription
+
+BYTES_PER_GB = 10 ** 9
+MIN_STORAGE_GB = 1
+MAX_STORAGE_GB = 50
+
+logger = logging.getLogger(__name__)
+
+stripe.api_key = settings.STRIPE_SECRET_KEY
+stripe.api_version = settings.STRIPE_API_VERSION
+
+
+class CheckoutSerializer(serializers.Serializer):
+ storage_gb = serializers.IntegerField(
+ min_value=MIN_STORAGE_GB, max_value=MAX_STORAGE_GB
+ )
+
+
+class CreateCheckoutSessionView(APIView):
+ """
+ Create a Stripe Checkout Session and return the URL for redirect.
+ """
+
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ user = request.user
+
+ subscription = getattr(user, "subscription", None)
+ if subscription and subscription.is_active:
+ return Response(
+ {"error": "You already have an active subscription"}, status=400
+ )
+
+ serializer = CheckoutSerializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ storage_gb = serializer.validated_data["storage_gb"]
+
+ try:
+ success_url = request.build_absolute_uri(
+ "/settings/#/storage?upgrade=success"
+ )
+ cancel_url = request.build_absolute_uri("/settings/#/storage")
+
+ checkout_session_params = {
+ "mode": "subscription",
+ "line_items": [
+ {
+ "price": settings.STRIPE_PRICE_ID,
+ "quantity": storage_gb,
+ }
+ ],
+ "success_url": success_url,
+ "cancel_url": cancel_url,
+ "client_reference_id": str(user.id),
+ "customer_email": user.email,
+ }
+
+ if subscription and subscription.stripe_customer_id:
+ checkout_session_params["customer"] = subscription.stripe_customer_id
+ del checkout_session_params["customer_email"]
+
+ session = stripe.checkout.Session.create(**checkout_session_params)
+
+ return Response({"checkout_url": session.url})
+
+ except stripe.error.StripeError as e:
+ logger.error(f"Stripe error creating checkout session: {e}")
+ return Response({"error": str(e)}, status=400)
+
+
+class CreatePortalSessionView(APIView):
+ """
+ Create a Stripe Customer Portal session for subscription management.
+ """
+
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ user = request.user
+ subscription = getattr(user, "subscription", None)
+
+ if not subscription or not subscription.stripe_customer_id:
+ return Response({"error": "No subscription found"}, status=400)
+
+ try:
+ session = stripe.billing_portal.Session.create(
+ customer=subscription.stripe_customer_id,
+ return_url=request.build_absolute_uri("/settings/#/storage"),
+ )
+ return Response({"portal_url": session.url})
+
+ except stripe.error.StripeError as e:
+ logger.error(f"Stripe error creating portal session: {e}")
+ return Response({"error": str(e)}, status=400)
+
+
+class SubscriptionStatusView(APIView):
+ """
+ Returns subscription status for the current user.
+ """
+
+ permission_classes = [IsAuthenticated]
+
+ def get(self, request):
+ user = request.user
+ subscription = getattr(user, "subscription", None)
+
+ if not subscription:
+ return Response(
+ {
+ "status": None,
+ "is_active": False,
+ "storage_bytes": 0,
+ "cancel_at_period_end": False,
+ }
+ )
+
+ data = {
+ "status": subscription.stripe_subscription_status,
+ "is_active": subscription.is_active,
+ "storage_bytes": subscription.subscription_disk_space,
+ "cancel_at_period_end": subscription.cancel_at_period_end,
+ }
+ if subscription.current_period_end:
+ data["current_period_end"] = subscription.current_period_end.isoformat()
+
+ return Response(data)
+
+
+class CheckoutCompletedSerializer(serializers.Serializer):
+ """Validates and processes checkout.session.completed events."""
+
+ client_reference_id = serializers.PrimaryKeyRelatedField(
+ queryset=User.objects.all()
+ )
+ customer = serializers.CharField()
+ subscription = serializers.CharField()
+
+ def save(self):
+ data = self.validated_data
+
+ # Retrieve the subscription to get the quantity (GB purchased).
+ # Also set by customer.subscription.updated, but that webhook may
+ # arrive later or not at all in some configurations.
+ stripe_sub = stripe.Subscription.retrieve(data["subscription"])
+ quantity_gb = stripe_sub["items"]["data"][0]["quantity"]
+
+ UserSubscription.objects.update_or_create(
+ user=data["client_reference_id"],
+ defaults={
+ "stripe_customer_id": data["customer"],
+ "stripe_subscription_id": data["subscription"],
+ "stripe_subscription_status": "active",
+ "subscription_disk_space": quantity_gb * BYTES_PER_GB,
+ },
+ )
+
+
+class SubscriptionDeletedSerializer(serializers.Serializer):
+ """
+ Validates and processes customer.subscription.deleted events.
+ Fires only after the subscription has fully ended (including any
+ remaining paid period), so it is safe to revoke storage here.
+ """
+
+ id = serializers.SlugRelatedField(
+ slug_field="stripe_subscription_id",
+ queryset=UserSubscription.objects.all(),
+ )
+
+ def save(self):
+ sub = self.validated_data["id"]
+ sub.stripe_subscription_status = "canceled"
+ sub.subscription_disk_space = 0
+ sub.save()
+
+
+class TimestampField(serializers.IntegerField):
+ """Converts a Unix timestamp to a timezone-aware datetime."""
+
+ def to_internal_value(self, data):
+ value = super().to_internal_value(data)
+ return datetime.fromtimestamp(value, tz=timezone.utc)
+
+
+class SubscriptionItemSerializer(serializers.Serializer):
+ quantity = serializers.IntegerField()
+
+
+class SubscriptionItemsDataSerializer(serializers.Serializer):
+ data = SubscriptionItemSerializer(many=True)
+
+
+STRIPE_SUBSCRIPTION_STATUSES = [
+ "active",
+ "canceled",
+ "incomplete",
+ "incomplete_expired",
+ "past_due",
+ "paused",
+ "trialing",
+ "unpaid",
+]
+
+
+class SubscriptionUpdatedSerializer(serializers.Serializer):
+ """Validates and processes customer.subscription.updated events."""
+
+ id = serializers.SlugRelatedField(
+ slug_field="stripe_subscription_id",
+ queryset=UserSubscription.objects.all(),
+ )
+ status = serializers.ChoiceField(choices=STRIPE_SUBSCRIPTION_STATUSES)
+ cancel_at_period_end = serializers.BooleanField(default=False)
+ cancel_at = serializers.IntegerField(allow_null=True, default=None)
+ current_period_end = TimestampField(required=False)
+ items = SubscriptionItemsDataSerializer()
+
+ def save(self):
+ sub = self.validated_data["id"]
+ data = self.validated_data
+ new_status = data["status"]
+ sub.stripe_subscription_status = new_status
+
+ sub.cancel_at_period_end = (
+ data["cancel_at_period_end"] or data["cancel_at"] is not None
+ )
+
+ if data.get("current_period_end"):
+ sub.current_period_end = data["current_period_end"]
+
+ if new_status in ("active", "trialing"):
+ quantity_gb = data["items"]["data"][0]["quantity"]
+ sub.subscription_disk_space = quantity_gb * BYTES_PER_GB
+ else:
+ sub.subscription_disk_space = 0
+
+ sub.save()
+
+
+_webhook_serializers = {
+ "checkout.session.completed": CheckoutCompletedSerializer,
+ "customer.subscription.deleted": SubscriptionDeletedSerializer,
+ "customer.subscription.updated": SubscriptionUpdatedSerializer,
+}
+
+
+@csrf_exempt
+@require_POST
+def stripe_webhook(request):
+ try:
+ event = stripe.Webhook.construct_event(
+ request.body,
+ request.META.get("HTTP_STRIPE_SIGNATURE"),
+ settings.STRIPE_WEBHOOK_SECRET,
+ )
+ except (ValueError, stripe.error.SignatureVerificationError):
+ return JsonResponse({"error": "Invalid payload or signature"}, status=400)
+
+ serializer_class = _webhook_serializers.get(event["type"])
+ if serializer_class:
+ serializer = serializer_class(data=event["data"]["object"])
+ if serializer.is_valid():
+ serializer.save()
+ else:
+ logger.warning(
+ f"Invalid webhook payload for {event['type']}: {serializer.errors}"
+ )
+
+ return JsonResponse({"status": "success"})
diff --git a/contentcuration/contentcuration/viewsets/audited_special_permissions_license.py b/contentcuration/contentcuration/viewsets/audited_special_permissions_license.py
new file mode 100644
index 0000000000..df4a766971
--- /dev/null
+++ b/contentcuration/contentcuration/viewsets/audited_special_permissions_license.py
@@ -0,0 +1,53 @@
+from django_filters.rest_framework import BooleanFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from django_filters.rest_framework import FilterSet
+from django_filters.rest_framework import UUIDFilter
+from rest_framework.permissions import IsAuthenticated
+
+from contentcuration.models import AuditedSpecialPermissionsLicense
+from contentcuration.viewsets.base import ReadOnlyValuesViewset
+from contentcuration.viewsets.common import UUIDInFilter
+
+
+class AuditedSpecialPermissionsLicenseFilter(FilterSet):
+ """
+ Filter for AuditedSpecialPermissionsLicense viewset.
+ Supports filtering by IDs, distributable status, and channelVersion.
+ """
+
+ by_ids = UUIDInFilter(field_name="id")
+ distributable = BooleanFilter()
+ channel_version = UUIDFilter(
+ field_name="channel_versions__id",
+ help_text="Filter by ChannelVersion ID",
+ )
+
+ class Meta:
+ model = None
+ fields = ("by_ids", "distributable", "channel_version")
+
+ def __init__(self, *args, **kwargs):
+
+ self.Meta.model = AuditedSpecialPermissionsLicense
+ super().__init__(*args, **kwargs)
+
+
+class AuditedSpecialPermissionsLicenseViewSet(ReadOnlyValuesViewset):
+ """
+ Read-only viewset for AuditedSpecialPermissionsLicense.
+ Allows filtering by IDs, distributable status, and channelVersion.
+ """
+
+ permission_classes = [IsAuthenticated]
+ filter_backends = [DjangoFilterBackend]
+ filterset_class = AuditedSpecialPermissionsLicenseFilter
+
+ values = (
+ "id",
+ "description",
+ "distributable",
+ )
+
+ def get_queryset(self):
+
+ return AuditedSpecialPermissionsLicense.objects.all()
diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py
index c2462836d1..46d6d063e8 100644
--- a/contentcuration/contentcuration/viewsets/channel.py
+++ b/contentcuration/contentcuration/viewsets/channel.py
@@ -8,6 +8,7 @@
from django.conf import settings
from django.db import IntegrityError
from django.db.models import Exists
+from django.db.models import FilteredRelation
from django.db.models import OuterRef
from django.db.models import Q
from django.db.models import Subquery
@@ -21,6 +22,9 @@
from django_cte import With
from django_filters.rest_framework import BooleanFilter
from django_filters.rest_framework import CharFilter
+from kolibri_public.utils.export_channel_to_kolibri_public import (
+ export_channel_to_kolibri_public,
+)
from le_utils.constants import content_kinds
from le_utils.constants import roles
from rest_framework import serializers
@@ -39,16 +43,23 @@
from search.utils import get_fts_search_query
import contentcuration.models as models
+from contentcuration.constants import (
+ community_library_submission as community_library_submission_constants,
+)
from contentcuration.decorators import cache_no_user_data
from contentcuration.models import Change
from contentcuration.models import Channel
+from contentcuration.models import ChannelVersion
+from contentcuration.models import CommunityLibrarySubmission
from contentcuration.models import ContentNode
+from contentcuration.models import Country
from contentcuration.models import File
from contentcuration.models import generate_storage_url
from contentcuration.models import SecretToken
from contentcuration.models import User
from contentcuration.utils.garbage_collect import get_deleted_chefs_root
from contentcuration.utils.pagination import CachedListPagination
+from contentcuration.utils.pagination import ValuesViewsetCursorPagination
from contentcuration.utils.pagination import ValuesViewsetPageNumberPagination
from contentcuration.utils.publish import ChannelIncompleteError
from contentcuration.utils.publish import publish_channel
@@ -86,6 +97,12 @@ class CatalogListPagination(CachedListPagination):
max_page_size = 1000
+class ChannelVersionListPagination(ValuesViewsetCursorPagination):
+ ordering = "-version"
+ page_size_query_param = "max_results"
+ max_page_size = 100
+
+
primary_token_subquery = Subquery(
SecretToken.objects.filter(channels=OuterRef("id"), is_primary=True)
.values("token")
@@ -437,7 +454,12 @@ class ChannelViewSet(ValuesViewset):
ordering = "-modified"
field_map = channel_field_map
- values = base_channel_values + ("edit", "view", "unpublished_changes")
+ values = base_channel_values + (
+ "edit",
+ "view",
+ "unpublished_changes",
+ "draft_token",
+ )
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
@@ -493,6 +515,14 @@ def get_queryset(self):
view=Exists(user_queryset.filter(view_only_channels=OuterRef("id"))),
)
+ def _annotate_draft_token(self, queryset):
+ draft_token_subquery = Subquery(
+ ChannelVersion.objects.filter(channel=OuterRef("id"), version=None).values(
+ "secret_token__token"
+ )[:1]
+ )
+ return queryset.annotate(draft_token=draft_token_subquery)
+
def annotate_queryset(self, queryset):
queryset = queryset.annotate(primary_token=primary_token_subquery)
channel_main_tree_nodes = ContentNode.objects.filter(
@@ -514,6 +544,8 @@ def annotate_queryset(self, queryset):
unpublished_changes=Exists(_unpublished_changes_query(OuterRef("id")))
)
+ queryset = self._annotate_draft_token(queryset)
+
return queryset
def publish_from_changes(self, changes):
@@ -564,7 +596,9 @@ def publish(self, pk, version_notes="", language=None):
{
"published": True,
"publishing": False,
+ "version": channel.version,
"primary_token": channel.get_human_token().token,
+ "draft_token": None,
"last_published": channel.last_published,
"unpublished_changes": _unpublished_changes_query(
channel
@@ -606,28 +640,27 @@ def publish(self, pk, version_notes="", language=None):
raise
def publish_next_from_changes(self, changes):
+
errors = []
for publish in changes:
try:
- self.publish_next(publish["key"])
+ self.publish_next(
+ publish["key"],
+ use_staging_tree=publish.get("use_staging_tree", False),
+ )
except Exception as e:
log_sync_exception(e, user=self.request.user, change=publish)
publish["errors"] = [str(e)]
errors.append(publish)
return errors
- def publish_next(self, pk):
+ def publish_next(self, pk, use_staging_tree=False):
logging.debug("Entering the publish staging channel endpoint")
channel = self.get_edit_queryset().get(pk=pk)
if channel.deleted:
raise ValidationError("Cannot publish a deleted channel")
- elif channel.staging_tree.publishing:
- raise ValidationError("Channel staging tree is already publishing")
-
- channel.staging_tree.publishing = True
- channel.staging_tree.save()
with create_change_tracker(
pk, CHANNEL, channel.id, self.request.user, "export-channel-staging-tree"
@@ -637,15 +670,19 @@ def publish_next(self, pk):
self.request.user.pk,
channel.id,
progress_tracker=progress_tracker,
- use_staging_tree=True,
+ is_draft_version=True,
+ use_staging_tree=use_staging_tree,
)
+ draft_token = channel.get_draft_token()
Change.create_changes(
[
generate_update_event(
channel.id,
CHANNEL,
{
- "primary_token": channel.get_human_token().token,
+ "draft_token": draft_token.token
+ if draft_token
+ else None,
},
channel_id=channel.id,
),
@@ -653,12 +690,8 @@ def publish_next(self, pk):
applied=True,
)
except ChannelIncompleteError:
- channel.staging_tree.publishing = False
- channel.staging_tree.save()
raise ValidationError("Channel is not ready to be published")
except Exception:
- channel.staging_tree.publishing = False
- channel.staging_tree.save()
raise
def sync_from_changes(self, changes):
@@ -768,6 +801,65 @@ def deploy(self, user, pk):
created_by_id=user.id,
)
+ def add_to_community_library_from_changes(self, changes):
+ errors = []
+ for change in changes:
+ try:
+ self.add_to_community_library(
+ channel_id=change["key"],
+ channel_version=change["channel_version"],
+ categories=change["categories"],
+ country_codes=change["country_codes"],
+ )
+ except Exception as e:
+ log_sync_exception(e, user=self.request.user, change=change)
+ change["errors"] = [str(e)]
+ errors.append(change)
+ return errors
+
+ def add_to_community_library(
+ self, channel_id, channel_version, categories, country_codes
+ ):
+ # Note: The `categories` field should contain a _dict_, with the category IDs as keys
+ # and `True` as a value. This is to match the representation
+ # of sets in the changes architecture.
+
+ # The change to add a channel to the community library can only
+ # be created server-side, so in theory we should not be getting
+ # malformed requests here. However, just to be safe, we still
+ # do basic checks.
+
+ channel = self.get_edit_queryset().get(pk=channel_id)
+ countries = Country.objects.filter(code__in=country_codes)
+
+ if channel.public:
+ raise ValidationError(
+ "Public channels cannot be added to the community library"
+ )
+ if channel_version <= 0 or channel_version > channel.version:
+ raise ValidationError("Invalid channel version")
+
+ # Because of changes architecture, the categories are passed as a dict
+ # with the category IDs as keys and `True` as a value. At this point,
+ # we are no longer working with changes, so we switch to the more
+ # convenient representation as a list.
+ categories_list = [key for key, val in categories.items() if val]
+
+ export_channel_to_kolibri_public(
+ channel_id=channel_id,
+ channel_version=channel_version,
+ public=False, # Community library
+ categories=categories_list,
+ countries=countries,
+ )
+
+ new_live_submission = CommunityLibrarySubmission.objects.get(
+ channel_id=channel_id,
+ channel_version=channel_version,
+ status=community_library_submission_constants.STATUS_APPROVED,
+ )
+ new_live_submission.mark_live()
+
@action(
detail=True,
methods=["get"],
@@ -814,6 +906,62 @@ def get_languages_in_channel(
langs_in_content = self._get_channel_content_languages(pk, main_tree_id)
return JsonResponse({"languages": langs_in_content})
+ @action(
+ detail=True,
+ methods=["get"],
+ url_path="version_detail",
+ url_name="version-detail",
+ )
+ def get_version_detail(self, request, pk=None) -> Response:
+ """
+ Get the version detail for a channel.
+
+ :param request: The request object
+ :param pk: The ID of the channel
+ :return: Response with the version detail of the channel
+ :rtype: Response
+ """
+ channel = self.get_edit_object()
+
+ if not channel.version_info:
+ return Response({})
+
+ version_data = (
+ ChannelVersion.objects.filter(id=channel.version_info.id)
+ .values(
+ "id",
+ "version",
+ "resource_count",
+ "kind_count",
+ "size",
+ "date_published",
+ "version_notes",
+ "included_languages",
+ "included_licenses",
+ "included_categories",
+ "non_distributable_licenses_included",
+ )
+ .first()
+ )
+
+ if not version_data:
+ return Response({})
+
+ return Response(version_data)
+
+ @action(
+ detail=True,
+ methods=["get"],
+ url_path="has_community_library_submission",
+ url_name="has-community-library-submission",
+ )
+ def has_community_library_submission(self, request, pk=None) -> Response:
+ channel = self.get_object()
+ has_submission = CommunityLibrarySubmission.objects.filter(
+ channel_id=channel.id
+ ).exists()
+ return Response({"has_community_library_submission": has_submission})
+
def _channel_exists(self, channel_id) -> bool:
"""
Check if a channel exists.
@@ -902,6 +1050,38 @@ def _get_channel_content_languages(
return unique_lang_ids
+class ChannelVersionFilter(RequiredFilterSet):
+ class Meta:
+ model = ChannelVersion
+ fields = {
+ "channel": ["exact"],
+ "version": ["exact", "gte", "lte"],
+ }
+
+
+class ChannelVersionViewSet(ReadOnlyValuesViewset):
+ queryset = ChannelVersion.objects.all()
+ permission_classes = [IsAuthenticated]
+ pagination_class = ChannelVersionListPagination
+ filterset_class = ChannelVersionFilter
+ ordering_fields = ["version"]
+ ordering = "-version"
+
+ values = (
+ "id",
+ "channel",
+ "version",
+ "size",
+ "resource_count",
+ "kind_count",
+ "date_published",
+ "version_notes",
+ "included_languages",
+ "included_licenses",
+ "included_categories",
+ )
+
+
@method_decorator(
cache_page(
settings.PUBLIC_CHANNELS_CACHE_DURATION, key_prefix="public_catalog_list"
@@ -965,6 +1145,17 @@ def annotate_queryset(self, queryset):
class AdminChannelFilter(BaseChannelFilter):
+
+ latest_community_library_submission_status = CharFilter(
+ method="filter_latest_community_library_submission_status"
+ )
+ community_library_live = BooleanFilter(
+ method="filter_community_library_live",
+ )
+ has_community_library_submission = BooleanFilter(
+ method="filter_has_community_library_submission",
+ )
+
def filter_keywords(self, queryset, name, value):
keywords = value.split(" ")
editors_first_name = reduce(
@@ -982,6 +1173,20 @@ def filter_keywords(self, queryset, name, value):
| editors_email
)
+ def filter_latest_community_library_submission_status(self, queryset, name, value):
+ values = self.request.query_params.getlist(name)
+ if values:
+ return queryset.filter(
+ latest_community_library_submission__status__in=values
+ )
+ return queryset
+
+ def filter_community_library_live(self, queryset, name, value):
+ return queryset.filter(has_any_live_community_library_submission=value)
+
+ def filter_has_community_library_submission(self, queryset, name, value):
+ return queryset.filter(latest_community_library_submission__isnull=(not value))
+
class AdminChannelSerializer(ChannelSerializer):
"""
@@ -1002,6 +1207,15 @@ class Meta:
list_serializer_class = BulkListSerializer
nested_writes = True
+ def validate_public(self, value):
+ if value and hasattr(self, "instance") and self.instance:
+ if self.instance.is_community_channel():
+ raise ValidationError(
+ "This channel has been added to the Community Library and cannot be marked public.",
+ code="public_community_conflict",
+ )
+ return value
+
class AdminChannelViewSet(ChannelViewSet, RESTUpdateModelMixin, RESTDestroyModelMixin):
pagination_class = CatalogListPagination
@@ -1013,6 +1227,8 @@ class AdminChannelViewSet(ChannelViewSet, RESTUpdateModelMixin, RESTDestroyModel
"created": "main_tree__created",
"source_url": format_source_url,
"demo_server_url": format_demo_server_url,
+ "latest_community_library_submission_id": "latest_community_library_submission__id",
+ "latest_community_library_submission_status": "latest_community_library_submission__status",
}
values = (
@@ -1032,6 +1248,9 @@ class AdminChannelViewSet(ChannelViewSet, RESTUpdateModelMixin, RESTDestroyModel
"source_url",
"demo_server_url",
"primary_token",
+ "latest_community_library_submission__id",
+ "latest_community_library_submission__status",
+ "has_any_live_community_library_submission",
)
def perform_destroy(self, instance):
@@ -1061,11 +1280,28 @@ def get_queryset(self):
channel_main_tree_nodes = ContentNode.objects.filter(
tree_id=OuterRef("main_tree__tree_id")
)
+ latest_community_library_submission_id = Subquery(
+ CommunityLibrarySubmission.objects.filter(channel_id=OuterRef("id"))
+ .order_by("-date_created")
+ .values("id")[:1]
+ )
queryset = Channel.objects.all().annotate(
modified=Subquery(
channel_main_tree_nodes.values("modified").order_by("-modified")[:1]
),
primary_token=primary_token_subquery,
+ latest_community_library_submission=FilteredRelation(
+ "community_library_submissions",
+ condition=Q(
+ community_library_submissions__id=latest_community_library_submission_id,
+ ),
+ ),
+ has_any_live_community_library_submission=Exists(
+ CommunityLibrarySubmission.objects.filter(
+ channel_id=OuterRef("id"),
+ status=community_library_submission_constants.STATUS_LIVE,
+ )
+ ),
)
return queryset
diff --git a/contentcuration/contentcuration/viewsets/community_library_submission.py b/contentcuration/contentcuration/viewsets/community_library_submission.py
new file mode 100644
index 0000000000..33fc6f9a94
--- /dev/null
+++ b/contentcuration/contentcuration/viewsets/community_library_submission.py
@@ -0,0 +1,361 @@
+from django.db.models import OuterRef
+from django.db.models import Subquery
+from django_filters import BaseInFilter
+from django_filters import ChoiceFilter
+from django_filters.rest_framework import DateTimeFilter
+from django_filters.rest_framework import DjangoFilterBackend
+from django_filters.rest_framework import FilterSet
+from rest_framework.decorators import action
+from rest_framework.filters import SearchFilter
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.relations import PrimaryKeyRelatedField
+from rest_framework.response import Response
+from rest_framework.serializers import ValidationError
+
+from contentcuration.constants import (
+ community_library_submission as community_library_submission_constants,
+)
+from contentcuration.models import AuditedSpecialPermissionsLicense
+from contentcuration.models import Change
+from contentcuration.models import Channel
+from contentcuration.models import ChannelVersion
+from contentcuration.models import CommunityLibrarySubmission
+from contentcuration.models import Country
+from contentcuration.tasks import apply_channel_changes_task
+from contentcuration.utils.pagination import ValuesViewsetCursorPagination
+from contentcuration.viewsets.base import BulkListSerializer
+from contentcuration.viewsets.base import BulkModelSerializer
+from contentcuration.viewsets.base import ReadOnlyValuesViewset
+from contentcuration.viewsets.base import RESTCreateModelMixin
+from contentcuration.viewsets.base import RESTDestroyModelMixin
+from contentcuration.viewsets.base import RESTUpdateModelMixin
+from contentcuration.viewsets.base import ValuesViewsetOrderingFilter
+from contentcuration.viewsets.common import UserFilteredPrimaryKeyRelatedField
+from contentcuration.viewsets.sync.utils import (
+ generate_added_to_community_library_event,
+)
+from contentcuration.viewsets.user import IsAdminUser
+
+
+class ChoiceInFilter(BaseInFilter, ChoiceFilter):
+ """
+ Allows passing multiple statuses in a single query param like status="STATUS_A,STATUS_B"
+ """
+
+ pass
+
+
+class CommunityLibrarySubmissionFilterSet(FilterSet):
+ """
+ FilterSet for CommunityLibrarySubmission to support notifications page filtering.
+ """
+
+ date_updated__lte = DateTimeFilter(field_name="date_updated", lookup_expr="lte")
+ date_updated__gte = DateTimeFilter(field_name="date_updated", lookup_expr="gte")
+ status__in = ChoiceInFilter(
+ field_name="status",
+ choices=community_library_submission_constants.status_choices,
+ )
+
+ class Meta:
+ model = CommunityLibrarySubmission
+ fields = ["channel", "date_updated__lte", "date_updated__gte", "status__in"]
+
+
+class CommunityLibrarySubmissionSerializer(BulkModelSerializer):
+ countries = PrimaryKeyRelatedField(
+ many=True,
+ queryset=Country.objects.all(),
+ required=False,
+ )
+ channel = UserFilteredPrimaryKeyRelatedField(
+ queryset=Channel.objects.all(),
+ edit=False,
+ )
+
+ class Meta:
+ model = CommunityLibrarySubmission
+ fields = [
+ "id",
+ "description",
+ "channel",
+ "countries",
+ "categories",
+ ]
+ list_serializer_class = BulkListSerializer
+
+ def create(self, validated_data):
+ channel = validated_data["channel"]
+ user = self.context["request"].user
+
+ # Prevent creating submissions while a publish is in progress
+ if getattr(getattr(channel, "main_tree", None), "publishing", False):
+ raise ValidationError(
+ "Cannot create a community library submission while the channel is being published."
+ )
+
+ if channel.version == 0:
+ # The channel is not published
+ raise ValidationError(
+ "Cannot create a community library submission for an "
+ "unpublished channel."
+ )
+
+ if channel.public:
+ raise ValidationError(
+ "Cannot create a community library submission for a public channel."
+ )
+
+ if not channel.editors.filter(id=user.id).exists():
+ raise ValidationError(
+ "Only editors can create a community library "
+ "submission for this channel."
+ )
+
+ validated_data["channel_version"] = channel.version
+ validated_data["author"] = self.context["request"].user
+
+ countries = validated_data.pop("countries", [])
+ instance = super().create(validated_data)
+
+ instance.countries.set(countries)
+
+ instance.save()
+ return instance
+
+ def update(self, instance, validated_data):
+ if (
+ "channel" in validated_data
+ and instance.channel.id != validated_data["channel"].id
+ ):
+ raise ValidationError(
+ "Cannot change the channel corresponding to "
+ "a community library submission."
+ )
+
+ if "countries" in validated_data:
+ countries = validated_data.pop("countries")
+ instance.countries.set(countries)
+
+ return super().update(instance, validated_data)
+
+
+class CommunityLibrarySubmissionResolveSerializer(CommunityLibrarySubmissionSerializer):
+ class Meta(CommunityLibrarySubmissionSerializer.Meta):
+ fields = CommunityLibrarySubmissionSerializer.Meta.fields + [
+ "status",
+ "resolution_reason",
+ "feedback_notes",
+ "internal_notes",
+ ]
+
+ def create(self, validated_data):
+ raise ValidationError(
+ "Cannot create a community library submission with this serializer. "
+ "Use the standard CommunityLibrarySubmissionSerializer instead."
+ )
+
+ def update(self, instance, validated_data):
+ if instance.status != community_library_submission_constants.STATUS_PENDING:
+ raise ValidationError(
+ "Cannot resolve a community library submission that is not pending."
+ )
+
+ if (
+ "status" in validated_data
+ and validated_data["status"]
+ == community_library_submission_constants.STATUS_APPROVED
+ and instance.channel.public
+ ):
+ raise ValidationError(
+ "Cannot approve a community library submission for a channel that has been marked public."
+ )
+
+ if "status" not in validated_data or validated_data["status"] not in [
+ community_library_submission_constants.STATUS_APPROVED,
+ community_library_submission_constants.STATUS_REJECTED,
+ ]:
+ raise ValidationError(
+ "Status must be either APPROVED or REJECTED when resolving a submission."
+ )
+
+ if (
+ "status" not in validated_data
+ or validated_data["status"]
+ == community_library_submission_constants.STATUS_REJECTED
+ ):
+ if not validated_data.get("resolution_reason", "").strip():
+ raise ValidationError(
+ "Resolution reason must be provided when rejecting a submission."
+ )
+ if not validated_data.get("feedback_notes", "").strip():
+ raise ValidationError(
+ "Feedback notes must be provided when rejecting a submission."
+ )
+
+ return super().update(instance, validated_data)
+
+
+class CommunityLibrarySubmissionPagination(ValuesViewsetCursorPagination):
+ ordering = "-date_updated"
+ page_size_query_param = "max_results"
+ max_page_size = 100
+
+
+def get_author_name(item):
+ return "{} {}".format(item["author__first_name"], item["author__last_name"])
+
+
+def get_resolved_by_name(item):
+ if item.get("resolved_by__first_name") or item.get("resolved_by__last_name"):
+ return "{} {}".format(
+ item.get("resolved_by__first_name", ""),
+ item.get("resolved_by__last_name", ""),
+ ).strip()
+ return None
+
+
+class CommunityLibrarySubmissionViewSetMixin:
+ """
+ Mixin with logic shared between the CommunityLibrarySubmissionViewSet and
+ AdminCommunityLibrarySubmissionViewSet.
+ """
+
+ values = (
+ "id",
+ "description",
+ "channel_id",
+ "channel__name",
+ "channel_version",
+ "author_id",
+ "author__first_name",
+ "author__last_name",
+ "categories",
+ "date_created",
+ "status",
+ "resolution_reason",
+ "feedback_notes",
+ "date_updated",
+ "resolved_by_id",
+ "resolved_by__first_name",
+ "resolved_by__last_name",
+ )
+ field_map = {
+ "author_name": get_author_name,
+ "resolved_by_name": get_resolved_by_name,
+ "channel_name": lambda item: item.get("channel__name"),
+ }
+ queryset = CommunityLibrarySubmission.objects.all()
+ filter_backends = [DjangoFilterBackend, SearchFilter, ValuesViewsetOrderingFilter]
+ filterset_class = CommunityLibrarySubmissionFilterSet
+ search_fields = ["channel__name"]
+ pagination_class = CommunityLibrarySubmissionPagination
+
+ ordering_fields = ["date_updated", "date_created"]
+ ordering = "-date_updated"
+
+ def consolidate(self, items, queryset):
+ countries = {}
+ for (submission_id, country_code,) in Country.objects.filter(
+ community_library_submissions__in=queryset
+ ).values_list("community_library_submissions", "code"):
+ if submission_id not in countries:
+ countries[submission_id] = []
+ countries[submission_id].append(country_code)
+
+ for item in items:
+ item["countries"] = countries.get(item["id"], [])
+
+ return items
+
+
+class CommunityLibrarySubmissionViewSet(
+ CommunityLibrarySubmissionViewSetMixin,
+ RESTCreateModelMixin,
+ RESTUpdateModelMixin,
+ RESTDestroyModelMixin,
+ ReadOnlyValuesViewset,
+):
+ permission_classes = [IsAuthenticated]
+ serializer_class = CommunityLibrarySubmissionSerializer
+
+
+class AdminCommunityLibrarySubmissionViewSet(
+ CommunityLibrarySubmissionViewSetMixin,
+ ReadOnlyValuesViewset,
+):
+ permission_classes = [IsAdminUser]
+
+ values = CommunityLibrarySubmissionViewSetMixin.values + (
+ "internal_notes",
+ "version_token",
+ )
+ field_map = CommunityLibrarySubmissionViewSetMixin.field_map.copy()
+
+ def annotate_queryset(self, queryset):
+ queryset = super().annotate_queryset(queryset)
+ return queryset.annotate(
+ version_token=Subquery(
+ ChannelVersion.objects.filter(
+ channel_id=OuterRef("channel_id"),
+ version=OuterRef("channel_version"),
+ ).values("secret_token__token")[:1]
+ )
+ )
+
+ def _mark_previous_pending_submissions_as_superseded(self, submission):
+ CommunityLibrarySubmission.objects.filter(
+ status=community_library_submission_constants.STATUS_PENDING,
+ channel=submission.channel,
+ channel_version__lt=submission.channel_version,
+ ).update(status=community_library_submission_constants.STATUS_SUPERSEDED)
+
+ def _add_to_community_library(self, submission):
+ country_codes = sorted(country.code for country in submission.countries.all())
+
+ Change.create_change(
+ generate_added_to_community_library_event(
+ key=submission.channel.id,
+ channel_version=submission.channel_version,
+ categories=submission.categories,
+ country_codes=country_codes,
+ ),
+ created_by_id=submission.resolved_by_id,
+ # This change is not publishable and should not trigger publish-related logic
+ unpublishable=True,
+ )
+ apply_channel_changes_task.fetch_or_enqueue(
+ submission.resolved_by,
+ channel_id=submission.channel.id,
+ )
+
+ @action(
+ methods=["post"],
+ detail=True,
+ serializer_class=CommunityLibrarySubmissionResolveSerializer,
+ url_name="resolve",
+ url_path="resolve",
+ )
+ def resolve(self, request, pk=None):
+ instance = self.get_edit_object()
+ serializer = self.get_serializer(instance, data=request.data, partial=True)
+ serializer.is_valid(raise_exception=True)
+
+ submission = serializer.save(
+ resolved_by=request.user,
+ )
+
+ submission.notify_update_to_channel_editors()
+
+ if submission.status == community_library_submission_constants.STATUS_APPROVED:
+ self._mark_previous_pending_submissions_as_superseded(submission)
+ self._add_to_community_library(submission)
+ published_version = ChannelVersion.objects.get(
+ channel=submission.channel,
+ version=submission.channel_version,
+ )
+ AuditedSpecialPermissionsLicense.mark_channel_version_as_distributable(
+ published_version.id
+ )
+
+ return Response(self.serialize_object())
diff --git a/contentcuration/contentcuration/viewsets/sync/base.py b/contentcuration/contentcuration/viewsets/sync/base.py
index 1d4be37c6b..2379e5b0c2 100644
--- a/contentcuration/contentcuration/viewsets/sync/base.py
+++ b/contentcuration/contentcuration/viewsets/sync/base.py
@@ -12,6 +12,7 @@
from contentcuration.viewsets.contentnode import PrerequisitesUpdateHandler
from contentcuration.viewsets.file import FileViewSet
from contentcuration.viewsets.invitation import InvitationViewSet
+from contentcuration.viewsets.sync.constants import ADDED_TO_COMMUNITY_LIBRARY
from contentcuration.viewsets.sync.constants import ASSESSMENTITEM
from contentcuration.viewsets.sync.constants import BOOKMARK
from contentcuration.viewsets.sync.constants import CHANNEL
@@ -98,6 +99,7 @@ def get_change_type(obj):
DEPLOYED: "deploy_from_changes",
UPDATED_DESCENDANTS: "update_descendants_from_changes",
PUBLISHED_NEXT: "publish_next_from_changes",
+ ADDED_TO_COMMUNITY_LIBRARY: "add_to_community_library_from_changes",
}
diff --git a/contentcuration/contentcuration/viewsets/sync/constants.py b/contentcuration/contentcuration/viewsets/sync/constants.py
index 54091c9203..35d5ee5536 100644
--- a/contentcuration/contentcuration/viewsets/sync/constants.py
+++ b/contentcuration/contentcuration/viewsets/sync/constants.py
@@ -9,6 +9,7 @@
DEPLOYED = 8
UPDATED_DESCENDANTS = 9
PUBLISHED_NEXT = 10
+ADDED_TO_COMMUNITY_LIBRARY = 11
ALL_CHANGES = set(
@@ -23,6 +24,13 @@
DEPLOYED,
UPDATED_DESCENDANTS,
PUBLISHED_NEXT,
+ ADDED_TO_COMMUNITY_LIBRARY,
+ ]
+)
+
+SERVER_ONLY_CHANGES = set(
+ [
+ ADDED_TO_COMMUNITY_LIBRARY,
]
)
@@ -40,6 +48,7 @@
VIEWER_M2M = "viewer_m2m"
SAVEDSEARCH = "savedsearch"
CLIPBOARD = "clipboard"
+SESSION = "session"
ALL_TABLES = set(
@@ -54,6 +63,10 @@
FILE,
INVITATION,
USER,
+ # SESSION is not a real backend model/viewset, but the frontend
+ # does have a SESSION table, so adding this here enables us to
+ # sync changes to this SESSION table
+ SESSION,
SAVEDSEARCH,
EDITOR_M2M,
VIEWER_M2M,
diff --git a/contentcuration/contentcuration/viewsets/sync/endpoint.py b/contentcuration/contentcuration/viewsets/sync/endpoint.py
index 6825833823..33ff296623 100644
--- a/contentcuration/contentcuration/viewsets/sync/endpoint.py
+++ b/contentcuration/contentcuration/viewsets/sync/endpoint.py
@@ -20,6 +20,7 @@
from contentcuration.tasks import apply_user_changes_task
from contentcuration.viewsets.sync.constants import CHANNEL
from contentcuration.viewsets.sync.constants import CREATED
+from contentcuration.viewsets.sync.constants import SERVER_ONLY_CHANGES
CHANGE_RETURN_LIMIT = 200
@@ -72,7 +73,11 @@ def handle_changes(self, request):
# Changes that cannot be made
disallowed_changes = []
for c in changes:
- if c.get("channel_id") is None and c.get("user_id") == request.user.id:
+ if c.get("type") in SERVER_ONLY_CHANGES:
+ disallowed_changes.append(c)
+ elif (
+ c.get("channel_id") is None and c.get("user_id") == request.user.id
+ ):
user_only_changes.append(c)
elif c.get("channel_id") in allowed_ids:
channel_changes.append(c)
@@ -150,6 +155,7 @@ def return_changes(self, request, channel_revs):
"table",
"change_type",
"kwargs",
+ "unpublishable",
)
.order_by("server_rev")[:CHANGE_RETURN_LIMIT]
)
diff --git a/contentcuration/contentcuration/viewsets/sync/utils.py b/contentcuration/contentcuration/viewsets/sync/utils.py
index a0073ce731..8f5ce98a05 100644
--- a/contentcuration/contentcuration/viewsets/sync/utils.py
+++ b/contentcuration/contentcuration/viewsets/sync/utils.py
@@ -3,6 +3,7 @@
from django.conf import settings
from contentcuration.utils.sentry import report_exception
+from contentcuration.viewsets.sync.constants import ADDED_TO_COMMUNITY_LIBRARY
from contentcuration.viewsets.sync.constants import ALL_TABLES
from contentcuration.viewsets.sync.constants import CHANNEL
from contentcuration.viewsets.sync.constants import CONTENTNODE
@@ -97,10 +98,28 @@ def generate_update_descendants_event(key, mods, channel_id=None, user_id=None):
return event
-def generate_publish_next_event(key, version_notes="", language=None):
+def generate_publish_next_event(key, use_staging_tree=False):
event = _generate_event(key, CHANNEL, PUBLISHED_NEXT, key, None)
- event["version_notes"] = version_notes
- event["language"] = language
+ event["use_staging_tree"] = use_staging_tree
+ return event
+
+
+def generate_added_to_community_library_event(
+ key,
+ channel_version,
+ categories=None,
+ country_codes=None,
+):
+ event = _generate_event(
+ key,
+ CHANNEL,
+ ADDED_TO_COMMUNITY_LIBRARY,
+ channel_id=key,
+ user_id=None,
+ )
+ event["channel_version"] = channel_version
+ event["categories"] = categories or dict()
+ event["country_codes"] = country_codes or list()
return event
diff --git a/contentcuration/contentcuration/viewsets/user.py b/contentcuration/contentcuration/viewsets/user.py
index a515364532..806e5a69c8 100644
--- a/contentcuration/contentcuration/viewsets/user.py
+++ b/contentcuration/contentcuration/viewsets/user.py
@@ -17,6 +17,7 @@
from django_filters.rest_framework import BooleanFilter
from django_filters.rest_framework import CharFilter
from django_filters.rest_framework import FilterSet
+from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import BasePermission
@@ -109,6 +110,13 @@ class Meta:
fields = ("ids",)
+class MarkNotificationsReadSerializer(serializers.Serializer):
+ timestamp = serializers.DateTimeField(
+ required=True,
+ help_text="Timestamp of the last read notification.",
+ )
+
+
class UserSerializer(BulkModelSerializer):
class Meta:
model = User
@@ -149,6 +157,24 @@ def get_storage_used(self, request):
def refresh_storage_used(self, request):
return Response(request.user.set_space_used())
+ @action(
+ detail=False,
+ methods=["post"],
+ serializer_class=MarkNotificationsReadSerializer,
+ )
+ def mark_notifications_read(self, request):
+ """
+ Allows a user to mark the timestamp of their last read notification.
+ """
+ user = request.user
+
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+
+ timestamp = serializer.validated_data["timestamp"]
+ user.mark_notifications_read(timestamp)
+ return Response(status=HTTP_204_NO_CONTENT)
+
def annotate_queryset(self, queryset):
queryset = queryset.annotate(
editable_channels__ids=NotNullArrayAgg("editable_channels__id"),
@@ -286,8 +312,9 @@ def remove_self(self, request, pk=None):
if not channel_id:
return HttpResponseBadRequest("Channel ID is required.")
- channel = Channel.objects.get(id=channel_id)
- if not channel:
+ try:
+ channel = Channel.objects.get(id=channel_id)
+ except Channel.DoesNotExist:
return HttpResponseNotFound("Channel not found {}".format(channel_id))
if request.user != user and not request.user.can_edit(channel_id):
@@ -404,7 +431,7 @@ class AdminUserViewSet(
"edit_count",
"view_count",
)
- queryset = User.objects.all()
+ queryset = User.objects.filter(deleted=False)
def annotate_queryset(self, queryset):
edit_channel_query = (
diff --git a/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py b/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py
index 3a0aee8fcd..07080fb849 100644
--- a/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py
+++ b/contentcuration/kolibri_public/management/commands/export_channels_to_kolibri_public.py
@@ -1,23 +1,14 @@
import logging
import os
-import shutil
-import tempfile
from datetime import datetime
from datetime import timedelta
-from django.conf import settings
-from django.core.files.storage import default_storage as storage
-from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db.models import F
from django.db.models import Q
from django.utils import timezone
-from kolibri_content.apps import KolibriContentConfig
-from kolibri_content.models import ChannelMetadata as ExportedChannelMetadata
-from kolibri_content.router import get_active_content_database
-from kolibri_content.router import using_content_database
from kolibri_public.models import ChannelMetadata
-from kolibri_public.utils.mapper import ChannelMapper
+from kolibri_public.utils import export_channel_to_kolibri_public
from contentcuration.models import Channel
from contentcuration.models import User
@@ -55,7 +46,7 @@ def handle(self, *args, **options):
count = 0
for channel_id in ids_to_export:
try:
- self._export_channel(channel_id)
+ export_channel_to_kolibri_public(channel_id)
count += 1
except FileNotFoundError:
logger.warning(
@@ -71,31 +62,6 @@ def handle(self, *args, **options):
)
logger.info("Successfully put {} channels into kolibri_public".format(count))
- def _export_channel(self, channel_id):
- logger.info("Putting channel {} into kolibri_public".format(channel_id))
- db_location = os.path.join(
- settings.DB_ROOT, "{id}.sqlite3".format(id=channel_id)
- )
- with storage.open(db_location) as storage_file:
- with tempfile.NamedTemporaryFile(suffix=".sqlite3") as db_file:
- shutil.copyfileobj(storage_file, db_file)
- db_file.seek(0)
- with using_content_database(db_file.name):
- # Run migration to handle old content databases published prior to current fields being added.
- call_command(
- "migrate",
- app_label=KolibriContentConfig.label,
- database=get_active_content_database(),
- )
- channel = ExportedChannelMetadata.objects.get(id=channel_id)
- logger.info(
- "Found channel {} for id: {} mapping now".format(
- channel.name, channel_id
- )
- )
- mapper = ChannelMapper(channel)
- mapper.run()
-
def _republish_problem_channels(self):
twenty_19 = datetime(year=2019, month=1, day=1)
five_minutes = timedelta(minutes=5)
diff --git a/contentcuration/kolibri_public/migrations/0007_new_channel_metadata.py b/contentcuration/kolibri_public/migrations/0007_new_channel_metadata.py
new file mode 100644
index 0000000000..1e2144d6b3
--- /dev/null
+++ b/contentcuration/kolibri_public/migrations/0007_new_channel_metadata.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.2.24 on 2025-07-22 10:38
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("contentcuration", "0156_communitylibrarysubmission_admin_fields"),
+ ("kolibri_public", "0006_auto_20250417_1516"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="channelmetadata",
+ name="categories",
+ field=models.JSONField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="channelmetadata",
+ name="countries",
+ field=models.ManyToManyField(
+ related_name="public_channels", to="contentcuration.Country"
+ ),
+ ),
+ ]
diff --git a/contentcuration/kolibri_public/migrations/0008_channelmetadata_categories_bitmask_0.py b/contentcuration/kolibri_public/migrations/0008_channelmetadata_categories_bitmask_0.py
new file mode 100644
index 0000000000..ac60bbdd37
--- /dev/null
+++ b/contentcuration/kolibri_public/migrations/0008_channelmetadata_categories_bitmask_0.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.24 on 2025-08-08 17:32
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("kolibri_public", "0007_new_channel_metadata"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="channelmetadata",
+ name="categories_bitmask_0",
+ field=models.BigIntegerField(blank=True, default=0, null=True),
+ ),
+ ]
diff --git a/contentcuration/kolibri_public/models.py b/contentcuration/kolibri_public/models.py
index c0056d9cf9..154ea5cb5f 100644
--- a/contentcuration/kolibri_public/models.py
+++ b/contentcuration/kolibri_public/models.py
@@ -1,12 +1,15 @@
from django.db import models
-from django.db.models import F
from kolibri_content import base_models
from kolibri_content.fields import JSONField
-from kolibri_public.search import bitmask_fieldnames
-from kolibri_public.search import metadata_bitmasks
+from kolibri_public.search import channelmetadata_bitmask_fieldnames
+from kolibri_public.search import channelmetadata_metadata_bitmasks
+from kolibri_public.search import contentnode_bitmask_fieldnames
+from kolibri_public.search import contentnode_metadata_bitmasks
+from kolibri_public.search import has_all_labels
from mptt.managers import TreeManager
from mptt.querysets import TreeQuerySet
+from contentcuration.models import Country
from contentcuration.models import Language
@@ -16,31 +19,7 @@ class ContentTag(base_models.ContentTag):
class ContentNodeQueryset(TreeQuerySet):
def has_all_labels(self, field_name, labels):
- bitmasks = metadata_bitmasks[field_name]
- bits = {}
- for label in labels:
- if label in bitmasks:
- bitmask_fieldname = bitmasks[label]["bitmask_field_name"]
- if bitmask_fieldname not in bits:
- bits[bitmask_fieldname] = 0
- bits[bitmask_fieldname] += bitmasks[label]["bits"]
-
- filters = {}
- annotations = {}
- for bitmask_fieldname, bits in bits.items():
- annotation_fieldname = "{}_{}".format(bitmask_fieldname, "masked")
- # To get the correct result, i.e. an AND that all the labels are present,
- # we need to check that the aggregated value is euqal to the bits.
- # If we wanted an OR (which would check for any being present),
- # we would have to use GREATER THAN 0 here.
- filters[annotation_fieldname] = bits
- # This ensures that the annotated value is the result of the AND operation
- # so if all the values are present, the result will be the same as the bits
- # but if any are missing, it will not be equal to the bits, but will only be
- # 0 if none of the bits are present.
- annotations[annotation_fieldname] = F(bitmask_fieldname).bitand(bits)
-
- return self.annotate(**annotations).filter(**filters)
+ return has_all_labels(self, contentnode_metadata_bitmasks, field_name, labels)
class ContentNodeManager(
@@ -78,7 +57,7 @@ class ContentNode(base_models.ContentNode):
objects = ContentNodeManager()
-for field_name in bitmask_fieldnames:
+for field_name in contentnode_bitmask_fieldnames:
field = models.BigIntegerField(default=0, null=True, blank=True)
field.contribute_to_class(ContentNode, field_name)
@@ -95,7 +74,20 @@ class AssessmentMetaData(base_models.AssessmentMetaData):
pass
+class ChannelMetadataQueryset(models.QuerySet):
+ def has_all_labels(self, field_name, labels):
+ return has_all_labels(
+ self, channelmetadata_metadata_bitmasks, field_name, labels
+ )
+
+
+class ChannelMetadataManager(models.Manager.from_queryset(ChannelMetadataQueryset)):
+ pass
+
+
class ChannelMetadata(base_models.ChannelMetadata):
+ # Note: The `categories` field should contain a _list_, NOT a _dict_.
+
# precalculated fields during annotation/migration
published_size = models.BigIntegerField(default=0, null=True, blank=True)
total_resource_count = models.IntegerField(default=0, null=True, blank=True)
@@ -104,6 +96,15 @@ class ChannelMetadata(base_models.ChannelMetadata):
)
order = models.PositiveIntegerField(default=0, null=True, blank=True)
public = models.BooleanField()
+ categories = models.JSONField(null=True, blank=True)
+ countries = models.ManyToManyField(Country, related_name="public_channels")
+
+ objects = ChannelMetadataManager()
+
+
+for field_name in channelmetadata_bitmask_fieldnames:
+ field = models.BigIntegerField(default=0, null=True, blank=True)
+ field.contribute_to_class(ChannelMetadata, field_name)
class MPTTTreeIDManager(models.Model):
diff --git a/contentcuration/kolibri_public/search.py b/contentcuration/kolibri_public/search.py
index fad6590b4f..ac1cef0aaa 100644
--- a/contentcuration/kolibri_public/search.py
+++ b/contentcuration/kolibri_public/search.py
@@ -26,37 +26,54 @@
from le_utils.constants.labels.subjects import SUBJECTSLIST
-metadata_lookup = {
+contentnode_metadata_lookup = {
"learning_activities": LEARNINGACTIVITIESLIST,
"categories": SUBJECTSLIST,
"grade_levels": LEVELSLIST,
"accessibility_labels": ACCESSIBILITYCATEGORIESLIST,
"learner_needs": NEEDSLIST,
}
+contentnode_metadata_bitmasks = {}
+contentnode_bitmask_fieldnames = {}
-
-metadata_bitmasks = {}
-
-bitmask_fieldnames = {}
-
-
-for key, labels in metadata_lookup.items():
- bitmask_lookup = {}
- i = 0
- while labels[i : i + 64]:
- bitmask_field_name = "{}_bitmask_{}".format(key, i)
- bitmask_fieldnames[bitmask_field_name] = []
- for j, label in enumerate(labels):
- info = {
- "bitmask_field_name": bitmask_field_name,
- "field_name": key,
- "bits": 2 ** j,
- "label": label,
- }
- bitmask_lookup[label] = info
- bitmask_fieldnames[bitmask_field_name].append(info)
- i += 64
- metadata_bitmasks[key] = bitmask_lookup
+channelmetadata_metadata_lookup = {
+ "categories": SUBJECTSLIST,
+}
+channelmetadata_metadata_bitmasks = {}
+channelmetadata_bitmask_fieldnames = {}
+
+
+def _populate_bitmask_data(metadata_lookup, metadata_bitmasks, bitmask_fieldnames):
+
+ for key, labels in metadata_lookup.items():
+ bitmask_lookup = {}
+ i = 0
+ while (chunk := labels[i : i + 64]) :
+ bitmask_field_name = "{}_bitmask_{}".format(key, i)
+ bitmask_fieldnames[bitmask_field_name] = []
+ for j, label in enumerate(chunk):
+ info = {
+ "bitmask_field_name": bitmask_field_name,
+ "field_name": key,
+ "bits": 2 ** j,
+ "label": label,
+ }
+ bitmask_lookup[label] = info
+ bitmask_fieldnames[bitmask_field_name].append(info)
+ i += 64
+ metadata_bitmasks[key] = bitmask_lookup
+
+
+_populate_bitmask_data(
+ contentnode_metadata_lookup,
+ contentnode_metadata_bitmasks,
+ contentnode_bitmask_fieldnames,
+)
+_populate_bitmask_data(
+ channelmetadata_metadata_lookup,
+ channelmetadata_metadata_bitmasks,
+ channelmetadata_bitmask_fieldnames,
+)
def _get_available_languages(base_queryset):
@@ -84,10 +101,62 @@ def _get_available_channels(base_queryset):
)
+def _get_available_channel_languages(base_queryset):
+ from contentcuration.models import Language
+
+ return list(
+ Language.objects.filter(public_channels__in=base_queryset)
+ .values("id", lang_name=F("native_name"))
+ .distinct()
+ )
+
+
+def _get_available_countries(base_queryset):
+ from contentcuration.models import Country
+
+ return list(
+ Country.objects.filter(public_channels__in=base_queryset)
+ .values("code", "name")
+ .distinct()
+ )
+
+
+def get_channel_available_metadata_labels(base_queryset):
+ from kolibri_public.models import ChannelMetadata
+
+ content_cache_key = str(
+ ChannelMetadata.objects.all().aggregate(updated=Max("last_updated"))["updated"]
+ )
+ cache_key = "channel-labels:{}:{}".format(
+ content_cache_key,
+ hashlib.md5(str(base_queryset.query).encode("utf8")).hexdigest(),
+ )
+ if cache_key not in cache:
+ base_queryset = base_queryset.order_by()
+ aggregates = {}
+ for field in channelmetadata_bitmask_fieldnames:
+ field_agg = field + "_agg"
+ aggregates[field_agg] = BitOr(field)
+ output = {}
+ if aggregates:
+ agg = base_queryset.aggregate(**aggregates)
+ for field, values in channelmetadata_bitmask_fieldnames.items():
+ bit_value = agg[field + "_agg"]
+ for value in values:
+ if value["field_name"] not in output:
+ output[value["field_name"]] = []
+ if bit_value is not None and bit_value & value["bits"]:
+ output[value["field_name"]].append(value["label"])
+ output["languages"] = _get_available_channel_languages(base_queryset)
+ output["countries"] = _get_available_countries(base_queryset)
+ cache.set(cache_key, output, timeout=None)
+ return cache.get(cache_key)
+
+
# Remove the SQLite Bitwise OR definition as not needed.
-def get_available_metadata_labels(base_queryset):
+def get_contentnode_available_metadata_labels(base_queryset):
# Updated to use the kolibri_public ChannelMetadata model
from kolibri_public.models import ChannelMetadata
@@ -101,12 +170,12 @@ def get_available_metadata_labels(base_queryset):
if cache_key not in cache:
base_queryset = base_queryset.order_by()
aggregates = {}
- for field in bitmask_fieldnames:
+ for field in contentnode_bitmask_fieldnames:
field_agg = field + "_agg"
aggregates[field_agg] = BitOr(field)
output = {}
agg = base_queryset.aggregate(**aggregates)
- for field, values in bitmask_fieldnames.items():
+ for field, values in contentnode_bitmask_fieldnames.items():
bit_value = agg[field + "_agg"]
for value in values:
if value["field_name"] not in output:
@@ -123,10 +192,12 @@ def get_all_contentnode_label_metadata():
# Updated to use the kolibri_public ContentNode model
from kolibri_public.models import ContentNode
- return get_available_metadata_labels(ContentNode.objects.filter(available=True))
+ return get_contentnode_available_metadata_labels(
+ ContentNode.objects.filter(available=True)
+ )
-def annotate_label_bitmasks(queryset):
+def annotate_label_bitmasks(queryset, bitmask_fieldnames):
update_statements = {}
for bitmask_fieldname, label_info in bitmask_fieldnames.items():
update_statements[bitmask_fieldname] = sum(
@@ -142,3 +213,39 @@ def annotate_label_bitmasks(queryset):
for info in label_info
)
queryset.update(**update_statements)
+
+
+def annotate_contentnode_label_bitmasks(queryset):
+ return annotate_label_bitmasks(queryset, contentnode_bitmask_fieldnames)
+
+
+def annotate_channelmetadata_label_bitmasks(queryset):
+ return annotate_label_bitmasks(queryset, channelmetadata_bitmask_fieldnames)
+
+
+def has_all_labels(queryset, metadata_bitmasks, field_name, labels):
+ bitmasks = metadata_bitmasks[field_name]
+ bits = {}
+ for label in labels:
+ if label in bitmasks:
+ bitmask_fieldname = bitmasks[label]["bitmask_field_name"]
+ if bitmask_fieldname not in bits:
+ bits[bitmask_fieldname] = 0
+ bits[bitmask_fieldname] += bitmasks[label]["bits"]
+
+ filters = {}
+ annotations = {}
+ for bitmask_fieldname, bits in bits.items():
+ annotation_fieldname = "{}_{}".format(bitmask_fieldname, "masked")
+ # To get the correct result, i.e. an AND that all the labels are present,
+ # we need to check that the aggregated value is euqal to the bits.
+ # If we wanted an OR (which would check for any being present),
+ # we would have to use GREATER THAN 0 here.
+ filters[annotation_fieldname] = bits
+ # This ensures that the annotated value is the result of the AND operation
+ # so if all the values are present, the result will be the same as the bits
+ # but if any are missing, it will not be equal to the bits, but will only be
+ # 0 if none of the bits are present.
+ annotations[annotation_fieldname] = F(bitmask_fieldname).bitand(bits)
+
+ return queryset.annotate(**annotations).filter(**filters)
diff --git a/contentcuration/kolibri_public/tests/test_channelmetadata_viewset.py b/contentcuration/kolibri_public/tests/test_channelmetadata_viewset.py
new file mode 100644
index 0000000000..0fad180b2b
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/test_channelmetadata_viewset.py
@@ -0,0 +1,431 @@
+from uuid import UUID
+
+from django.urls import reverse
+from kolibri_public.models import ChannelMetadata
+from kolibri_public.tests.utils.mixer import KolibriPublicMixer
+from le_utils.constants.labels.subjects import SUBJECTSLIST
+
+from contentcuration.models import Country
+from contentcuration.models import Language
+from contentcuration.tests import testdata
+from contentcuration.tests.base import StudioAPITestCase
+from contentcuration.tests.helpers import reverse_with_query
+
+
+class ChannelMetadataViewSetTestCase(StudioAPITestCase):
+ def test_annotate_countries(self):
+ mixer = KolibriPublicMixer()
+
+ country1 = mixer.blend(Country, code="C1")
+ country2 = mixer.blend(Country, code="C2")
+ country3 = mixer.blend(Country, code="C3")
+
+ channel = mixer.blend(
+ ChannelMetadata, countries=[country1, country2, country3], public=False
+ )
+
+ user = testdata.user("any@user.com")
+ self.client.force_authenticate(user)
+
+ response = self.client.get(
+ reverse_with_query(
+ "publicchannel-detail",
+ args=[channel.id],
+ query={"public": "false"},
+ ),
+ )
+
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertCountEqual(response.data["countries"], ["C1", "C2", "C3"])
+
+
+class ChannelMetadataFilterTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+
+ mixer = KolibriPublicMixer()
+
+ self.categories = [
+ SUBJECTSLIST[0],
+ SUBJECTSLIST[1],
+ SUBJECTSLIST[2],
+ SUBJECTSLIST[3],
+ ]
+ self.category_bitmasks = [
+ 1,
+ 2,
+ 4,
+ 8,
+ ]
+
+ mixer.blend(Country, code="C1")
+ mixer.blend(Country, code="C2")
+ mixer.blend(Country, code="C3")
+
+ self.user = testdata.user("any@user.com")
+
+ # Manually set the bitmasks for testing
+ self.metadata1 = mixer.blend(
+ ChannelMetadata,
+ categories_bitmask_0=(
+ self.category_bitmasks[0]
+ | self.category_bitmasks[1]
+ | self.category_bitmasks[3]
+ ),
+ countries=["C1", "C3"],
+ public=False,
+ )
+ self.metadata2 = mixer.blend(
+ ChannelMetadata,
+ categories_bitmask_0=(
+ self.category_bitmasks[0]
+ | self.category_bitmasks[2]
+ | self.category_bitmasks[3]
+ ),
+ countries=["C1", "C2", "C3"],
+ public=False,
+ )
+ self.metadata3 = mixer.blend(
+ ChannelMetadata,
+ categories_bitmask_0=(
+ self.category_bitmasks[0]
+ | self.category_bitmasks[1]
+ | self.category_bitmasks[2]
+ ),
+ countries=["C3"],
+ public=False,
+ )
+
+ def test_filter_by_categories_bitmask__provided(self):
+ self.client.force_authenticate(self.user)
+
+ filter_query = {
+ "categories": f"{self.categories[0]},{self.categories[1]}",
+ "public": "false",
+ }
+
+ response = self.client.get(
+ reverse_with_query(
+ "publicchannel-list",
+ query=filter_query,
+ ),
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertCountEqual(
+ [UUID(item["id"]) for item in response.data],
+ [UUID(self.metadata1.id), UUID(self.metadata3.id)],
+ )
+
+ def test_filter_by_categories_bitmask__not_provided(self):
+ self.client.force_authenticate(self.user)
+
+ response = self.client.get(
+ reverse_with_query(
+ "publicchannel-list",
+ query={"public": "false"},
+ ),
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertCountEqual(
+ [UUID(item["id"]) for item in response.data],
+ [UUID(self.metadata1.id), UUID(self.metadata2.id), UUID(self.metadata3.id)],
+ )
+
+ def test_filter_by_countries(self):
+ self.client.force_authenticate(self.user)
+
+ filter_query = {"countries": "C1,C2", "public": "false"}
+
+ response = self.client.get(
+ reverse_with_query(
+ "publicchannel-list",
+ query=filter_query,
+ ),
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertCountEqual(
+ [UUID(item["id"]) for item in response.data],
+ [UUID(self.metadata1.id), UUID(self.metadata2.id)],
+ )
+
+ response1 = next(
+ filter(
+ lambda item: UUID(item["id"]) == UUID(self.metadata1.id), response.data
+ )
+ )
+ response2 = next(
+ filter(
+ lambda item: UUID(item["id"]) == UUID(self.metadata2.id), response.data
+ )
+ )
+
+ self.assertCountEqual(response1["countries"], ["C1", "C3"])
+ self.assertCountEqual(response2["countries"], ["C1", "C2", "C3"])
+
+
+class ChannelMetadataSearchFilterTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+
+ mixer = KolibriPublicMixer()
+ self.user = testdata.user("search@user.com")
+
+ self.channel_alpha = mixer.blend(
+ ChannelMetadata,
+ name="Alpha Mathematics Course",
+ description="A course about algebra and calculus",
+ tagline="",
+ public=False,
+ )
+ self.channel_beta = mixer.blend(
+ ChannelMetadata,
+ name="Beta Science Module",
+ description="Covers physics and chemistry",
+ tagline="Learn science today",
+ public=False,
+ )
+ self.channel_gamma = mixer.blend(
+ ChannelMetadata,
+ name="Gamma History",
+ description="World history overview",
+ tagline="",
+ public=False,
+ )
+
+ def _list(self, query):
+ self.client.force_authenticate(self.user)
+ return self.client.get(reverse_with_query("publicchannel-list", query=query))
+
+ def test_search_by_name(self):
+ response = self._list({"search": "Mathematics", "public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+ ids = [UUID(item["id"]) for item in response.data]
+ self.assertIn(UUID(self.channel_alpha.id), ids)
+ self.assertNotIn(UUID(self.channel_beta.id), ids)
+ self.assertNotIn(UUID(self.channel_gamma.id), ids)
+
+ def test_search_case_insensitive(self):
+ response = self._list({"search": "mathematics", "public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+ ids = [UUID(item["id"]) for item in response.data]
+ self.assertIn(UUID(self.channel_alpha.id), ids)
+
+ def test_search_no_results(self):
+ response = self._list({"search": "zzznomatch999", "public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(len(response.data), 0)
+
+ def test_search_combined_with_countries(self):
+ country, _ = Country.objects.get_or_create(
+ code="MX", defaults={"name": "Mexico"}
+ )
+ self.channel_alpha.countries.add(country)
+
+ response = self._list(
+ {"search": "Mathematics", "countries": "MX", "public": "false"}
+ )
+ self.assertEqual(response.status_code, 200, response.content)
+ ids = [UUID(item["id"]) for item in response.data]
+ self.assertIn(UUID(self.channel_alpha.id), ids)
+ self.assertNotIn(UUID(self.channel_gamma.id), ids)
+
+
+class ChannelMetadataLanguageFilterTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+
+ mixer = KolibriPublicMixer()
+ self.user = testdata.user("lang@user.com")
+
+ self.lang_en = Language.objects.get_or_create(
+ id="en", defaults={"lang_code": "en", "readable_name": "English"}
+ )[0]
+ self.lang_fr = Language.objects.get_or_create(
+ id="fr", defaults={"lang_code": "fr", "readable_name": "French"}
+ )[0]
+ self.lang_ar = Language.objects.get_or_create(
+ id="ar", defaults={"lang_code": "ar", "readable_name": "Arabic"}
+ )[0]
+
+ # channel_en: primary language English
+ self.channel_en = mixer.blend(ChannelMetadata, public=False)
+ self.channel_en.root.lang = self.lang_en
+ self.channel_en.root.save()
+ self.channel_en.included_languages.add(self.lang_en)
+
+ # channel_fr: primary language French, also includes Arabic
+ self.channel_fr = mixer.blend(ChannelMetadata, public=False)
+ self.channel_fr.root.lang = self.lang_fr
+ self.channel_fr.root.save()
+ self.channel_fr.included_languages.add(self.lang_ar, self.lang_fr)
+
+ # channel_multi: no primary language, but includes English and French
+ self.channel_multi = mixer.blend(ChannelMetadata, public=False)
+ self.channel_multi.included_languages.add(self.lang_en, self.lang_fr)
+
+ def _list(self, query):
+ self.client.force_authenticate(self.user)
+ return self.client.get(reverse_with_query("publicchannel-list", query=query))
+
+ def test_filter_by_included_language(self):
+ response = self._list({"languages": "ar", "public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+ ids = [UUID(item["id"]) for item in response.data]
+ self.assertIn(UUID(self.channel_fr.id), ids)
+ self.assertNotIn(UUID(self.channel_en.id), ids)
+ self.assertNotIn(UUID(self.channel_multi.id), ids)
+
+ def test_filter_by_multiple_languages(self):
+ response = self._list({"languages": "en,fr", "public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+ ids = [UUID(item["id"]) for item in response.data]
+ self.assertIn(UUID(self.channel_en.id), ids)
+ self.assertIn(UUID(self.channel_fr.id), ids)
+ self.assertIn(UUID(self.channel_multi.id), ids)
+
+ def test_filter_languages_combined_with_search(self):
+ self.channel_en.name = "English Math Channel"
+ self.channel_en.save()
+ self.channel_multi.name = "English Science Channel"
+ self.channel_multi.save()
+
+ response = self._list({"languages": "en", "search": "Math", "public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+ ids = [UUID(item["id"]) for item in response.data]
+ self.assertIn(UUID(self.channel_en.id), ids)
+ self.assertNotIn(UUID(self.channel_multi.id), ids)
+
+
+class ChannelMetadataLabelsActionTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+
+ mixer = KolibriPublicMixer()
+ self.user = testdata.user("labels@user.com")
+
+ self.categories = [SUBJECTSLIST[0], SUBJECTSLIST[1]]
+
+ self.lang_en = Language.objects.get_or_create(
+ id="en", defaults={"lang_code": "en", "readable_name": "English"}
+ )[0]
+ self.lang_fr = Language.objects.get_or_create(
+ id="fr", defaults={"lang_code": "fr", "readable_name": "French"}
+ )[0]
+
+ self.country_us = Country.objects.get_or_create(
+ code="US", defaults={"name": "United States"}
+ )[0]
+ self.country_mx = Country.objects.get_or_create(
+ code="MX", defaults={"name": "Mexico"}
+ )[0]
+ # Country not associated with any channel
+ self.country_br = Country.objects.get_or_create(
+ code="BR", defaults={"name": "Brazil"}
+ )[0]
+
+ self.channel1 = mixer.blend(
+ ChannelMetadata,
+ categories_bitmask_0=1 | 2, # SUBJECTSLIST[0] and [1]
+ public=False,
+ )
+ self.channel1.included_languages.add(self.lang_en)
+ self.channel1.countries.add(self.country_us)
+
+ self.channel2 = mixer.blend(
+ ChannelMetadata,
+ categories_bitmask_0=2, # SUBJECTSLIST[1] only
+ public=False,
+ )
+ self.channel2.included_languages.add(self.lang_fr)
+ self.channel2.countries.add(self.country_mx)
+
+ def _labels(self, query=None):
+ self.client.force_authenticate(self.user)
+ url = reverse("publicchannel-labels")
+ return self.client.get(url, query or {})
+
+ def test_labels_returns_200(self):
+ response = self._labels({"public": "false"})
+ self.assertEqual(response.status_code, 200, response.content)
+
+ def test_labels_returns_available_languages(self):
+ response = self._labels({"public": "false"})
+ language_ids = [lang["id"] for lang in response.data["languages"]]
+ self.assertIn("en", language_ids)
+ self.assertIn("fr", language_ids)
+
+ def test_labels_returns_available_countries(self):
+ response = self._labels({"public": "false"})
+ country_codes = [c["code"] for c in response.data["countries"]]
+ self.assertIn("US", country_codes)
+ self.assertIn("MX", country_codes)
+ # Country not linked to any channel should not appear
+ self.assertNotIn("BR", country_codes)
+
+ def test_labels_returns_available_categories(self):
+ response = self._labels({"public": "false"})
+ categories = response.data.get("categories", [])
+ self.assertIn(self.categories[0], categories)
+ self.assertIn(self.categories[1], categories)
+
+ def test_labels_respects_filter_params(self):
+ # When filtering to only channel1's language, only channel1's country should appear
+ response = self._labels({"public": "false", "languages": "en"})
+ self.assertEqual(response.status_code, 200, response.content)
+ country_codes = [c["code"] for c in response.data["countries"]]
+ self.assertIn("US", country_codes)
+ self.assertNotIn("MX", country_codes)
+
+ def test_labels_country_objects_have_code_and_name(self):
+ response = self._labels({"public": "false"})
+ for country in response.data["countries"]:
+ self.assertIn("code", country)
+ self.assertIn("name", country)
+
+ def test_labels_language_objects_have_id_and_lang_name(self):
+ response = self._labels({"public": "false"})
+ for lang in response.data["languages"]:
+ self.assertIn("id", lang)
+ self.assertIn("lang_name", lang)
+
+
+class ChannelMetadataLibraryFieldTestCase(StudioAPITestCase):
+ def setUp(self):
+ super().setUp()
+ self.mixer = KolibriPublicMixer()
+ self.user = testdata.user("library@test.com")
+ self.client.force_authenticate(self.user)
+
+ def test_public_channel_returns_library_kolibri(self):
+ """
+ A public channel in the v2 API returns library: "KOLIBRI".
+ """
+ channel = self.mixer.blend(ChannelMetadata, public=True)
+
+ response = self.client.get(
+ reverse_with_query(
+ "publicchannel-detail",
+ args=[channel.id],
+ query={"public": "true"},
+ ),
+ )
+
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(response.data["library"], "KOLIBRI")
+
+ def test_non_public_channel_returns_library_community(self):
+ """
+ A non-public channel in the v2 API returns library: "COMMUNITY".
+ """
+ channel = self.mixer.blend(ChannelMetadata, public=False)
+
+ response = self.client.get(
+ reverse_with_query(
+ "publicchannel-detail",
+ args=[channel.id],
+ query={"public": "false"},
+ ),
+ )
+
+ self.assertEqual(response.status_code, 200, response.content)
+ self.assertEqual(response.data["library"], "COMMUNITY")
diff --git a/contentcuration/kolibri_public/tests/test_content_app.py b/contentcuration/kolibri_public/tests/test_content_app.py
index 30d14df272..cd82c03ded 100644
--- a/contentcuration/kolibri_public/tests/test_content_app.py
+++ b/contentcuration/kolibri_public/tests/test_content_app.py
@@ -500,6 +500,114 @@ def test_channelmetadata_has_exercises_filter(self):
with_filter_response.data[0]["name"], self.channel_data["name"]
)
+ def test_channelmetadata_public_filter_default_true(self):
+ community_channel = models.ContentNode.objects.create(
+ pk=uuid.uuid4().hex,
+ channel_id=uuid.uuid4().hex,
+ content_id=uuid.uuid4().hex,
+ kind="topic",
+ title="community channel",
+ )
+ models.ChannelMetadata.objects.create(
+ id=uuid.uuid4().hex,
+ name="community channel metadata",
+ root=community_channel,
+ # community channel
+ public=False,
+ )
+
+ # By default, only public channels should be returned
+ response = self.client.get(reverse("publicchannel-list"))
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(response.data[0]["name"], self.channel_data["name"])
+ self.assertTrue(response.data[0]["public"])
+
+ def test_channelmetadata_public_filter_explicit_true(self):
+ community_channel = models.ContentNode.objects.create(
+ pk=uuid.uuid4().hex,
+ channel_id=uuid.uuid4().hex,
+ content_id=uuid.uuid4().hex,
+ kind="topic",
+ title="community channel 2",
+ )
+ models.ChannelMetadata.objects.create(
+ id=uuid.uuid4().hex,
+ name="community channel metadata 2",
+ root=community_channel,
+ public=False,
+ )
+
+ # Explicitly request only public channels
+ response = self.client.get(reverse("publicchannel-list"), {"public": True})
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(response.data[0]["name"], self.channel_data["name"])
+ self.assertTrue(response.data[0]["public"])
+
+ def test_channelmetadata_public_filter_explicit_false(self):
+ community_channel = models.ContentNode.objects.create(
+ pk=uuid.uuid4().hex,
+ channel_id=uuid.uuid4().hex,
+ content_id=uuid.uuid4().hex,
+ kind="topic",
+ title="community channel 3",
+ )
+ community_metadata = models.ChannelMetadata.objects.create(
+ id=uuid.uuid4().hex,
+ name="community channel metadata 3",
+ root=community_channel,
+ public=False,
+ )
+
+ # Explicitly request only community channels
+ response = self.client.get(reverse("publicchannel-list"), {"public": False})
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(response.data[0]["name"], community_metadata.name)
+ self.assertFalse(response.data[0]["public"])
+
+ def test_channelmetadata_public_filter_mixed_channels(self):
+ community_channel1 = models.ContentNode.objects.create(
+ pk=uuid.uuid4().hex,
+ channel_id=uuid.uuid4().hex,
+ content_id=uuid.uuid4().hex,
+ kind="topic",
+ title="community channel 4",
+ )
+ models.ChannelMetadata.objects.create(
+ id=uuid.uuid4().hex,
+ name="community channel metadata 4",
+ root=community_channel1,
+ public=False,
+ )
+
+ community_channel2 = models.ContentNode.objects.create(
+ pk=uuid.uuid4().hex,
+ channel_id=uuid.uuid4().hex,
+ content_id=uuid.uuid4().hex,
+ kind="topic",
+ title="community channel 5",
+ )
+ models.ChannelMetadata.objects.create(
+ id=uuid.uuid4().hex,
+ name="community channel metadata 5",
+ root=community_channel2,
+ public=False,
+ )
+
+ # Test public filter
+ public_response = self.client.get(
+ reverse("publicchannel-list"), {"public": True}
+ )
+ self.assertEqual(len(public_response.data), 1)
+ self.assertTrue(public_response.data[0]["public"])
+
+ # Test community filter
+ community_response = self.client.get(
+ reverse("publicchannel-list"), {"public": False}
+ )
+ self.assertEqual(len(community_response.data), 2)
+ for channel in community_response.data:
+ self.assertFalse(channel["public"])
+
def test_filtering_coach_content_anon(self):
response = self.client.get(
reverse("publiccontentnode-list"),
diff --git a/contentcuration/kolibri_public/tests/test_export_channels_to_kolibri_public.py b/contentcuration/kolibri_public/tests/test_export_channels_to_kolibri_public.py
new file mode 100644
index 0000000000..cb7fb54487
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/test_export_channels_to_kolibri_public.py
@@ -0,0 +1,130 @@
+import os.path
+import shutil
+import tempfile
+import uuid
+from unittest import mock
+
+from django.conf import settings
+from django.core.management import call_command
+from django.test import TestCase
+from kolibri_content.apps import KolibriContentConfig
+from kolibri_content.models import ChannelMetadata as ExportedChannelMetadata
+from kolibri_content.router import get_active_content_database
+from kolibri_content.router import using_content_database
+from kolibri_public.tests.utils.mixer import KolibriPublicMixer
+from kolibri_public.utils.export_channel_to_kolibri_public import (
+ export_channel_to_kolibri_public,
+)
+
+from contentcuration.models import Country
+from contentcuration.tests.utils.restricted_filesystemstorage import (
+ RestrictedFileSystemStorage,
+)
+
+
+class ExportTestCase(TestCase):
+ def setUp(self):
+ super().setUp()
+
+ self._temp_directory_ctx = tempfile.TemporaryDirectory()
+ test_db_root_dir = self._temp_directory_ctx.__enter__()
+
+ self.storage = RestrictedFileSystemStorage(location=test_db_root_dir)
+
+ self._storage_patch_ctx = mock.patch(
+ "kolibri_public.utils.export_channel_to_kolibri_public.storage",
+ new=self.storage,
+ )
+ self._storage_patch_ctx.__enter__()
+
+ os.makedirs(os.path.join(test_db_root_dir, settings.DB_ROOT), exist_ok=True)
+
+ self.channel_id = uuid.UUID(int=42).hex
+ self.channel_version = 1
+
+ self.versioned_db_path = os.path.join(
+ test_db_root_dir,
+ settings.DB_ROOT,
+ f"{self.channel_id}-{self.channel_version}.sqlite3",
+ )
+ open(self.versioned_db_path, "w").close()
+
+ with using_content_database(self.versioned_db_path):
+ call_command(
+ "migrate",
+ app_label=KolibriContentConfig.label,
+ database=get_active_content_database(),
+ )
+
+ mixer = KolibriPublicMixer()
+ self.exported_channel_metadata = mixer.blend(
+ ExportedChannelMetadata,
+ id=self.channel_id,
+ version=self.channel_version,
+ )
+
+ self.unversioned_db_path = os.path.join(
+ test_db_root_dir, settings.DB_ROOT, f"{self.channel_id}.sqlite3"
+ )
+ shutil.copyfile(self.versioned_db_path, self.unversioned_db_path)
+
+ def tearDown(self):
+ self._temp_directory_ctx.__exit__(None, None, None)
+ self._storage_patch_ctx.__exit__(None, None, None)
+
+ super().tearDown()
+
+ @mock.patch("kolibri_public.utils.export_channel_to_kolibri_public.ChannelMapper")
+ def test_export_channel_to_kolibri_public__existing_version(
+ self, mock_channel_mapper
+ ):
+ categories = ["Category1", "Category2"]
+ country1 = Country.objects.create(code="C1", name="Country 1")
+ country2 = Country.objects.create(code="C2", name="Country 2")
+ countries = [country1, country2]
+
+ export_channel_to_kolibri_public(
+ channel_id=self.channel_id,
+ channel_version=1,
+ public=True,
+ categories=categories,
+ countries=countries,
+ )
+
+ mock_channel_mapper.assert_called_once_with(
+ channel=self.exported_channel_metadata,
+ public=True,
+ categories=categories,
+ countries=countries,
+ )
+ mock_channel_mapper.return_value.run.assert_called_once_with()
+
+ @mock.patch("kolibri_public.utils.export_channel_to_kolibri_public.ChannelMapper")
+ def test_export_channel_to_kolibri_public__without_version(
+ self, mock_channel_mapper
+ ):
+ export_channel_to_kolibri_public(
+ channel_id=self.channel_id,
+ )
+
+ mock_channel_mapper.assert_called_once_with(
+ channel=self.exported_channel_metadata,
+ public=True,
+ categories=None,
+ countries=None,
+ )
+ mock_channel_mapper.return_value.run.assert_called_once_with()
+
+ def test_export_channel_to_kolibri_public__bad_channel(self):
+ with self.assertRaises(FileNotFoundError):
+ export_channel_to_kolibri_public(
+ channel_id="dummy_id",
+ channel_version=1,
+ )
+
+ def test_export_channel_to_kolibri_public__bad_version(self):
+ with self.assertRaises(FileNotFoundError):
+ export_channel_to_kolibri_public(
+ channel_id=self.channel_id,
+ channel_version=2,
+ )
diff --git a/contentcuration/kolibri_public/tests/test_mapper.py b/contentcuration/kolibri_public/tests/test_mapper.py
index d938233e63..92225a2b8a 100644
--- a/contentcuration/kolibri_public/tests/test_mapper.py
+++ b/contentcuration/kolibri_public/tests/test_mapper.py
@@ -1,6 +1,9 @@
+import datetime
import os
import tempfile
+from unittest import mock
+import pytz
from django.core.management import call_command
from django.test import TestCase
from kolibri_content import models as kolibri_content_models
@@ -12,8 +15,10 @@
from kolibri_public.tests.base import OKAY_TAG
from kolibri_public.utils.mapper import ChannelMapper
from le_utils.constants import content_kinds
+from le_utils.constants.labels.subjects import SUBJECTSLIST
from contentcuration.models import Channel
+from contentcuration.models import Country
from contentcuration.tests.testdata import user
@@ -28,16 +33,24 @@ def overrides(self):
kolibri_public_models.LocalFile: {
"available": True,
},
+ kolibri_public_models.ChannelMetadata: {
+ "last_updated": self.dummy_date,
+ },
}
- @classmethod
- def setUpClass(cls):
- super(ChannelMapperTest, cls).setUpClass()
+ def setUp(self):
+ super().setUp()
call_command("loadconstants")
- _, cls.tempdb = tempfile.mkstemp(suffix=".sqlite3")
+ _, self.tempdb = tempfile.mkstemp(suffix=".sqlite3")
admin_user = user()
- with using_content_database(cls.tempdb):
+ self.dummy_date = datetime.datetime(2020, 1, 1, 0, 0, 0, tzinfo=pytz.utc)
+ self._date_patcher = mock.patch(
+ "kolibri_public.utils.annotation.timezone.now", return_value=self.dummy_date
+ )
+ self._date_patcher.start()
+
+ with using_content_database(self.tempdb):
call_command(
"migrate",
"content",
@@ -52,23 +65,20 @@ def setUpClass(cls):
},
)
builder.insert_into_default_db()
- cls.source_root = kolibri_content_models.ContentNode.objects.get(
+ self.source_root = kolibri_content_models.ContentNode.objects.get(
id=builder.root_node["id"]
)
- cls.channel = kolibri_content_models.ChannelMetadata.objects.get(
+ self.channel = kolibri_content_models.ChannelMetadata.objects.get(
id=builder.channel["id"]
)
contentcuration_channel = Channel.objects.create(
actor_id=admin_user.id,
- id=cls.channel.id,
- name=cls.channel.name,
+ id=self.channel.id,
+ name=self.channel.name,
public=True,
)
contentcuration_channel.main_tree.published = True
contentcuration_channel.main_tree.save()
- cls.mapper = ChannelMapper(cls.channel)
- cls.mapper.run()
- cls.mapped_root = cls.mapper.mapped_root
def _assert_model(self, source, mapped, Model):
for field in Model._meta.fields:
@@ -79,7 +89,11 @@ def _assert_model(self, source, mapped, Model):
self.overrides[Model][column], getattr(mapped, column)
)
else:
- self.assertEqual(getattr(source, column), getattr(mapped, column))
+ self.assertEqual(
+ getattr(source, column),
+ getattr(mapped, column),
+ f"Mismatch in model {Model.__name__}, column {column}",
+ )
def _assert_node(self, source, mapped):
"""
@@ -133,6 +147,10 @@ def _recurse_and_assert(self, sources, mappeds, recursion_depth=0):
def test_map(self):
with using_content_database(self.tempdb):
+ self.mapper = ChannelMapper(self.channel)
+ self.mapper.run()
+ self.mapped_root = self.mapper.mapped_root
+
self._recurse_and_assert([self.source_root], [self.mapped_root])
self._assert_model(
self.channel,
@@ -142,6 +160,10 @@ def test_map(self):
def test_map_replace(self):
with using_content_database(self.tempdb):
+ self.mapper = ChannelMapper(self.channel)
+ self.mapper.run()
+ self.mapped_root = self.mapper.mapped_root
+
mapper = ChannelMapper(self.channel)
mapper.run()
self._recurse_and_assert([self.source_root], [mapper.mapped_root])
@@ -151,10 +173,127 @@ def test_map_replace(self):
kolibri_public_models.ChannelMetadata,
)
- @classmethod
- def tearDownClass(cls):
+ def test_categories__none_provided(self):
+ with using_content_database(self.tempdb):
+ kolibri_content_models.ContentNode.objects.filter(
+ channel_id=self.channel.id,
+ ).update(categories=None)
+
+ mapper = ChannelMapper(self.channel)
+ mapper.run()
+
+ self.assertEqual(mapper.mapped_channel.categories, [])
+
+ def test_categories__only_provided(self):
+ with using_content_database(self.tempdb):
+ kolibri_content_models.ContentNode.objects.filter(
+ channel_id=self.channel.id,
+ ).update(categories=None)
+
+ categories = ["Category1", "Category2"]
+ mapper = ChannelMapper(self.channel, categories=categories)
+ mapper.run()
+
+ self.assertEqual(mapper.mapped_channel.categories, categories)
+
+ def test_categories__only_on_content_nodes(self):
+ with using_content_database(self.tempdb):
+ source_contentnodes_queryset = (
+ kolibri_content_models.ContentNode.objects.filter(
+ channel_id=self.channel.id,
+ )
+ )
+ contentnode1 = source_contentnodes_queryset[0]
+ contentnode2 = source_contentnodes_queryset[1]
+
+ source_contentnodes_queryset.update(categories=None)
+ contentnode1.categories = "Category1,Category2"
+ contentnode2.categories = "Category3"
+ contentnode1.save()
+ contentnode2.save()
+
+ mapper = ChannelMapper(self.channel)
+ mapper.run()
+
+ self.assertEqual(
+ mapper.mapped_channel.categories,
+ ["Category1", "Category2", "Category3"],
+ )
+
+ def test_categories__both_provided_and_on_content_nodes(self):
+ with using_content_database(self.tempdb):
+ source_contentnodes_queryset = (
+ kolibri_content_models.ContentNode.objects.filter(
+ channel_id=self.channel.id,
+ )
+ )
+ contentnode1 = source_contentnodes_queryset[0]
+ contentnode2 = source_contentnodes_queryset[1]
+
+ source_contentnodes_queryset.update(categories=None)
+ contentnode1.categories = "Category1,Category2"
+ contentnode2.categories = "Category3"
+ contentnode1.save()
+ contentnode2.save()
+
+ categories = ["Category3", "Category4"]
+ mapper = ChannelMapper(self.channel, categories=categories)
+ mapper.run()
+
+ self.assertEqual(
+ mapper.mapped_channel.categories,
+ ["Category1", "Category2", "Category3", "Category4"],
+ )
+
+ def test_countries__none_provided(self):
+ with using_content_database(self.tempdb):
+ mapper = ChannelMapper(self.channel)
+ mapper.run()
+
+ self.assertEqual(mapper.mapped_channel.countries.count(), 0)
+
+ def test_countries__provided(self):
+ with using_content_database(self.tempdb):
+ country1 = Country.objects.create(code="C1", name="Country 1")
+ country2 = Country.objects.create(code="C2", name="Country 2")
+
+ countries = [country1, country2]
+ mapper = ChannelMapper(self.channel, countries=countries)
+ mapper.run()
+
+ self.assertCountEqual(mapper.mapped_channel.countries.all(), countries)
+
+ def test_categories_bitmask_annotation(self):
+ with using_content_database(self.tempdb):
+ categories = [
+ SUBJECTSLIST[0], # 1
+ SUBJECTSLIST[2], # 4
+ SUBJECTSLIST[4], # 16
+ ]
+
+ # Delete all categories in the tree, so that only explicitly provided categories
+ # are used
+ kolibri_content_models.ContentNode.objects.filter(
+ channel_id=self.channel.id,
+ ).update(categories=None)
+
+ mapper = ChannelMapper(
+ channel=self.channel,
+ public=False,
+ categories=categories,
+ )
+ mapper.run()
+
+ self.assertTrue(
+ hasattr(mapper.mapped_channel, "categories_bitmask_0"),
+ "ChannelMetadata should have categories_bitmask_0 field",
+ )
+ self.assertEqual(mapper.mapped_channel.categories_bitmask_0, 1 | 4 | 16)
+
+ def tearDown(self):
# Clean up datbase connection after the test
- cleanup_content_database_connection(cls.tempdb)
- super(ChannelMapperTest, cls).tearDownClass()
- if os.path.exists(cls.tempdb):
- os.remove(cls.tempdb)
+ self._date_patcher.stop()
+ cleanup_content_database_connection(self.tempdb)
+ super().tearDown()
+ if os.path.exists(self.tempdb):
+ os.remove(self.tempdb)
diff --git a/contentcuration/kolibri_public/tests/test_public_models_mutual_exclusivity.py b/contentcuration/kolibri_public/tests/test_public_models_mutual_exclusivity.py
new file mode 100644
index 0000000000..5e10a4026f
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/test_public_models_mutual_exclusivity.py
@@ -0,0 +1,255 @@
+from django.contrib.auth import get_user_model
+from django.core.exceptions import ValidationError
+from django.urls import reverse
+from rest_framework import status
+
+from contentcuration.constants import community_library_submission as constants
+from contentcuration.models import Channel
+from contentcuration.models import CommunityLibrarySubmission
+from contentcuration.tests import testdata
+from contentcuration.tests.base import StudioAPITestCase
+from contentcuration.tests.base import StudioTestCase
+from contentcuration.viewsets.channel import AdminChannelSerializer
+
+
+User = get_user_model()
+
+
+class ChannelMutualExclusivityTestCase(StudioTestCase):
+ def setUp(self):
+ self.user = testdata.user()
+ self.channel = Channel.objects.create(
+ actor_id=self.user.id,
+ name="Test Channel",
+ description="Test Description",
+ version=0,
+ )
+ self.channel.version = 1
+ self.channel.save()
+ self.channel.editors.add(self.user)
+
+ def test_public_channel_cannot_be_submitted_to_community_library(self):
+ """Test that a public channel cannot be submitted to community library."""
+ self.channel.public = True
+ self.channel.save()
+
+ with self.assertRaises(ValidationError) as context:
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=1,
+ author=self.user,
+ description="Test submission",
+ )
+
+ self.assertIn(
+ "Cannot create a community library submission for a public channel",
+ str(context.exception),
+ )
+
+ def test_community_channel_cannot_be_marked_public(self):
+ """Test that a community channel cannot be marked public."""
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=1,
+ author=self.user,
+ description="Test submission",
+ status=constants.STATUS_APPROVED,
+ )
+
+ self.channel.public = True
+ with self.assertRaises(ValidationError) as context:
+ self.channel.clean()
+
+ self.assertIn(
+ "This channel has been added to the Community Library and cannot be marked public",
+ str(context.exception),
+ )
+
+ def test_is_community_channel_method(self):
+ """Test the is_community_channel method."""
+ self.assertFalse(self.channel.is_community_channel())
+
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=1,
+ author=self.user,
+ description="Test submission",
+ status=constants.STATUS_APPROVED,
+ )
+ self.assertTrue(self.channel.is_community_channel())
+
+ self.channel.version = 2
+ self.channel.save()
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=2,
+ author=self.user,
+ description="Test submission 2",
+ status=constants.STATUS_LIVE,
+ )
+ self.assertTrue(self.channel.is_community_channel())
+
+ self.channel.version = 3
+ self.channel.save()
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=3,
+ author=self.user,
+ description="Test submission 3",
+ status=constants.STATUS_PENDING,
+ )
+ self.assertTrue(self.channel.is_community_channel())
+
+ def test_non_community_channel_can_be_marked_public(self):
+ """Test that a non-community channel can be marked public."""
+ self.channel.public = True
+ self.channel.clean()
+ self.channel.save()
+ self.assertTrue(self.channel.public)
+
+ def test_non_public_channel_can_be_submitted_to_community_library(self):
+ """Test that a non-public channel can be submitted to community library."""
+ submission = CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=1,
+ author=self.user,
+ description="Test submission",
+ )
+ self.assertEqual(submission.channel, self.channel)
+
+
+class AdminChannelSerializerMutualExclusivityTestCase(StudioTestCase):
+ """Test mutual exclusivity rules for AdminChannelSerializer."""
+
+ def setUp(self):
+ self.user = testdata.user()
+ self.channel = Channel.objects.create(
+ actor_id=self.user.id,
+ name="Test Channel",
+ description="Test Description",
+ version=0,
+ )
+
+ self.channel.version = 1
+ self.channel.save()
+ self.channel.editors.add(self.user)
+
+ def test_serializer_validates_community_channel_cannot_be_public(self):
+ """Test that serializer prevents marking community channel as public."""
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=1,
+ author=self.user,
+ description="Test submission",
+ status=constants.STATUS_APPROVED,
+ )
+
+ serializer = AdminChannelSerializer(
+ instance=self.channel, data={"public": True}, partial=True
+ )
+
+ self.assertFalse(serializer.is_valid())
+ self.assertIn(
+ "This channel has been added to the Community Library and cannot be marked public",
+ str(serializer.errors),
+ )
+
+ def test_serializer_allows_non_community_channel_to_be_public(self):
+ """Test that serializer allows non-community channel to be public."""
+ serializer = AdminChannelSerializer(
+ instance=self.channel, data={"public": True}, partial=True
+ )
+
+ self.assertTrue(serializer.is_valid())
+ serializer.save()
+ self.channel.refresh_from_db()
+ self.assertTrue(self.channel.public)
+
+ def test_serializer_allows_community_channel_to_remain_non_public(self):
+ """Test that serializer allows community channel to remain non-public."""
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=1,
+ author=self.user,
+ description="Test submission",
+ status=constants.STATUS_APPROVED,
+ )
+
+ serializer = AdminChannelSerializer(
+ instance=self.channel,
+ data={"source_url": "https://example.com"},
+ partial=True,
+ )
+
+ self.assertTrue(serializer.is_valid())
+ serializer.save()
+ self.channel.refresh_from_db()
+ self.assertEqual(self.channel.source_url, "https://example.com")
+ self.assertFalse(self.channel.public)
+
+
+class CommunityLibrarySubmissionMutualExclusivityAPITestCase(StudioAPITestCase):
+ """Test mutual exclusivity rules via API endpoints."""
+
+ def setUp(self):
+ super().setUp()
+
+ self.user = testdata.user()
+ self.channel = testdata.channel()
+ self.channel.public = False
+ self.channel.version = 1
+ self.channel.editors.add(self.user)
+ self.channel.save()
+
+ def tearDown(self):
+ super().tearDown()
+
+ def test_api_prevents_public_channel_submission_to_community_library(self):
+ """Test that API prevents submitting public channel to community library."""
+ self.channel.public = True
+ self.channel.save()
+
+ self.client.force_authenticate(user=self.user)
+
+ url = reverse("community-library-submission-list")
+ data = {
+ "channel": self.channel.id,
+ "description": "Test submission",
+ "countries": [],
+ }
+
+ response = self.client.post(url, data, format="json")
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertIn(
+ "Cannot create a community library submission for a public channel",
+ str(response.data),
+ )
+
+ def test_api_prevents_approving_submission_for_public_channel(self):
+ """Test that API prevents approving submission for channel that became public."""
+ submission = CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=self.channel.version,
+ description="Test submission",
+ status=constants.STATUS_PENDING,
+ author=self.user,
+ )
+
+ self.channel.public = True
+ self.channel.save()
+
+ self.client.force_authenticate(user=self.admin_user)
+
+ url = reverse(
+ "admin-community-library-submission-resolve", kwargs={"pk": submission.id}
+ )
+ data = {
+ "status": constants.STATUS_APPROVED,
+ }
+
+ response = self.client.post(url, data, format="json")
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertIn(
+ "Cannot approve a community library submission for a channel that has been marked public",
+ str(response.data),
+ )
diff --git a/contentcuration/kolibri_public/tests/test_public_v1_api.py b/contentcuration/kolibri_public/tests/test_public_v1_api.py
index 75037eba6a..6d545dbb11 100644
--- a/contentcuration/kolibri_public/tests/test_public_v1_api.py
+++ b/contentcuration/kolibri_public/tests/test_public_v1_api.py
@@ -2,6 +2,9 @@
from django.core.cache import cache
from django.urls import reverse
+from contentcuration.constants import community_library_submission as cls_constants
+from contentcuration.models import ChannelVersion
+from contentcuration.models import CommunityLibrarySubmission
from contentcuration.tests.base import BaseAPITestCase
from contentcuration.tests.testdata import generated_base64encoding
@@ -74,3 +77,445 @@ def test_public_channels_endpoint(self):
self.assertEqual(first_channel["name"], self.channel.name)
self.assertEqual(first_channel["id"], self.channel.id)
self.assertEqual(first_channel["icon_encoding"], generated_base64encoding())
+
+ def test_public_channel_lookup_with_channel_version_token_uses_channel_version(
+ self,
+ ):
+ """
+ A channel version token should resolve to the matched ChannelVersion,
+ not the channel's current published version.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+
+ self.channel.version = 7
+ self.channel.published_data = {
+ "2": {"version_notes": "v2 notes"},
+ "4": {"version_notes": "v4 notes"},
+ "7": {"version_notes": "v7 notes"},
+ }
+ self.channel.save()
+
+ channel_version, _created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=4,
+ defaults={
+ "kind_count": [],
+ "included_languages": [],
+ "resource_count": 0,
+ "size": 0,
+ },
+ )
+ version_token = channel_version.new_token().token
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ response = self.client.get(lookup_url + "?channel_versions=true")
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(response.data[0]["version"], 4)
+ self.assertNotEqual(response.data[0]["version"], self.channel.version)
+ self.assertEqual(
+ response.data[0]["version_notes"], {2: "v2 notes", 4: "v4 notes"}
+ )
+
+ def test_public_channel_lookup_channel_version_and_channel_tokens_have_same_keys(
+ self,
+ ):
+ """
+ Lookup responses from channel-version-token and channel-token endpoints
+ should expose the same top-level keys, even if values differ.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+
+ self.channel.version = 9
+ self.channel.published_data = {
+ "3": {"version_notes": "v3 notes"},
+ "9": {"version_notes": "v9 notes"},
+ }
+ self.channel.save()
+
+ latest_channel_version, _created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=9,
+ defaults={
+ "kind_count": [],
+ "included_languages": [],
+ "resource_count": 0,
+ "size": 0,
+ },
+ )
+ latest_version_token = latest_channel_version.new_token().token
+ channel_token = self.channel.make_token().token
+
+ channel_version_response = self.client.get(
+ reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": latest_version_token},
+ )
+ + "?channel_versions=true"
+ )
+ channel_response = self.client.get(
+ reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": channel_token},
+ )
+ )
+
+ self.assertEqual(channel_version_response.status_code, 200)
+ self.assertEqual(channel_response.status_code, 200)
+ self.assertEqual(len(channel_version_response.data), 1)
+ self.assertEqual(len(channel_response.data), 1)
+
+ self.assertSetEqual(
+ set(channel_version_response.data[0].keys()),
+ set(channel_response.data[0].keys()),
+ )
+
+ def test_channel_version_token_returns_snapshot_info_not_current_channel_info(self):
+ """
+ When a channel version token is used, the returned name, description, and
+ thumbnail should come from the ChannelVersion snapshot captured at publish time,
+ not from the channel's current (possibly updated) values.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+
+ # Set the channel info BEFORE the ChannelVersion is created so that the
+ # snapshot captures these values.
+ self.channel.name = "Original Published Name"
+ self.channel.description = "Original published description"
+ self.channel.thumbnail_encoding = {"base64": generated_base64encoding()}
+ self.channel.version = 3
+ self.channel.published_data = {"3": {"version_notes": "v3 notes"}}
+ self.channel.save()
+
+ # The ChannelVersion for version == channel.version is auto-created by
+ # Channel.on_update(); re-fetch it to get the snapshot that was captured.
+ channel_version = ChannelVersion.objects.get(channel=self.channel, version=3)
+ version_token = channel_version.new_token().token
+
+ # Now mutate the channel's info AFTER the snapshot was taken.
+ self.channel.name = "Updated Name — should NOT appear in response"
+ self.channel.description = "Updated description — should NOT appear in response"
+ self.channel.thumbnail_encoding = {"base64": "UPDATED_ENCODING"}
+ self.channel.save()
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ response = self.client.get(lookup_url + "?channel_versions=true")
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+ result = response.data[0]
+
+ # Values must match the snapshot, not the current channel state.
+ self.assertEqual(result["name"], "Original Published Name")
+ self.assertEqual(result["description"], "Original published description")
+ self.assertEqual(result["icon_encoding"], generated_base64encoding())
+
+ # Sanity-check: confirm the channel itself now has the updated values.
+ self.channel.refresh_from_db()
+ self.assertNotEqual(result["name"], self.channel.name)
+ self.assertNotEqual(result["description"], self.channel.description)
+
+ def test_channel_version_token_lookup_requires_channel_versions_param(self):
+ """
+ Without channel_versions=true, a channel-version token must return 404.
+ With channel_versions=true it must return 200 with the correct version.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.version = 4
+ self.channel.published_data = {"4": {"version_notes": "v4 notes"}}
+ self.channel.save()
+ # Channel.on_update() auto-creates ChannelVersion(version=4) when channel.save() is called.
+ # The get_or_create below finds that existing record; defaults are not applied.
+ # new_token() creates the secret token if it doesn't already exist.
+ channel_version, _created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=4,
+ defaults={
+ "kind_count": [],
+ "included_languages": [],
+ "resource_count": 0,
+ "size": 0,
+ },
+ )
+ version_token = channel_version.new_token().token
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+
+ # Without the param: must 404
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 404)
+
+ # With channel_versions=true: must 200 with the correct version
+ response = self.client.get(lookup_url + "?channel_versions=true")
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(response.data[0]["version"], 4)
+
+ def test_channel_version_token_without_param_returns_404(self):
+ """
+ A channel-version token used without ?channel_versions=true returns 404.
+ The gate must be active by default so older Kolibri clients never
+ accidentally receive data they cannot parse correctly.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.version = 11
+ self.channel.published_data = {"11": {"version_notes": "v11 notes"}}
+ self.channel.save()
+
+ channel_version, _created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=11,
+ defaults={
+ "kind_count": [],
+ "included_languages": [],
+ "resource_count": 0,
+ "size": 0,
+ },
+ )
+ version_token = channel_version.new_token().token
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_channel_version_token_with_approved_submission_returns_library_community(
+ self,
+ ):
+ """
+ A channel-version token whose ChannelVersion has a CommunityLibrarySubmission
+ with APPROVED status returns library: "COMMUNITY".
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.version = 5
+ self.channel.published_data = {"5": {"version_notes": "v5 notes"}}
+ self.channel.save()
+
+ # CommunityLibrarySubmission.save() calls ChannelVersion.objects.get_or_create(version=5)
+ # (finding the one already created by Channel.on_update()) and then calls new_token()
+ # to create the secret token. self.user is already an editor of self.channel (from setUp).
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=5,
+ author=self.user,
+ status=cls_constants.STATUS_APPROVED,
+ )
+
+ channel_version = ChannelVersion.objects.get(channel=self.channel, version=5)
+ version_token = channel_version.secret_token.token
+
+ lookup_url = (
+ reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ + "?channel_versions=true"
+ )
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.data[0]["library"], "COMMUNITY")
+
+ def test_channel_version_token_with_live_submission_returns_library_community(self):
+ """
+ A channel-version token whose ChannelVersion has a CommunityLibrarySubmission
+ with LIVE status returns library: "COMMUNITY".
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.version = 7
+ self.channel.published_data = {"7": {"version_notes": "v7 notes"}}
+ self.channel.save()
+
+ # CommunityLibrarySubmission.save() validates that self.channel.public is False
+ # (it is False by default) and that self.user is a channel editor (added in setUp).
+ # It also calls ChannelVersion.objects.get_or_create(version=7) and new_token().
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=7,
+ author=self.user,
+ status=cls_constants.STATUS_LIVE,
+ )
+
+ channel_version = ChannelVersion.objects.get(channel=self.channel, version=7)
+ version_token = channel_version.secret_token.token
+
+ lookup_url = (
+ reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ + "?channel_versions=true"
+ )
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.data[0]["library"], "COMMUNITY")
+
+ def test_channel_version_token_with_pending_submission_returns_library_null(self):
+ """
+ A channel-version token whose ChannelVersion has a CommunityLibrarySubmission
+ with PENDING status (not approved or live) returns library: null.
+ This validates that the status filter in _get_channel_version_library is correct.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.version = 8
+ self.channel.published_data = {"8": {"version_notes": "v8 notes"}}
+ self.channel.save()
+
+ # CommunityLibrarySubmission with PENDING status should NOT qualify.
+ CommunityLibrarySubmission.objects.create(
+ channel=self.channel,
+ channel_version=8,
+ author=self.user,
+ status=cls_constants.STATUS_PENDING,
+ )
+
+ channel_version = ChannelVersion.objects.get(channel=self.channel, version=8)
+ version_token = channel_version.secret_token.token
+
+ lookup_url = (
+ reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ + "?channel_versions=true"
+ )
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIsNone(response.data[0]["library"])
+
+ def test_channel_version_token_without_submission_returns_library_null(self):
+ """
+ A channel-version token with no associated CommunityLibrarySubmission
+ returns library: null.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.version = 6
+ self.channel.published_data = {"6": {"version_notes": "v6 notes"}}
+ self.channel.save()
+
+ # Channel.on_update() creates ChannelVersion(version=6); get_or_create finds it.
+ # No CommunityLibrarySubmission is created, so no token is auto-generated.
+ # new_token() creates the secret token here.
+ channel_version, _created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=6,
+ defaults={
+ "kind_count": [],
+ "included_languages": [],
+ "resource_count": 0,
+ "size": 0,
+ },
+ )
+ version_token = channel_version.new_token().token
+
+ lookup_url = (
+ reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ + "?channel_versions=true"
+ )
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIsNone(response.data[0]["library"])
+
+ def test_public_channel_token_returns_library_kolibri(self):
+ """
+ A regular channel token for a public channel returns library: "KOLIBRI".
+ """
+ self.channel.public = True
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.save()
+
+ channel_token = self.channel.make_token().token
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": channel_token},
+ )
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.data[0]["library"], "KOLIBRI")
+
+ def test_non_public_channel_token_returns_library_null(self):
+ """
+ A regular channel token for a non-public channel returns library: null.
+ """
+ self.channel.public = False
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+ self.channel.save()
+
+ channel_token = self.channel.make_token().token
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": channel_token},
+ )
+ response = self.client.get(lookup_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIsNone(response.data[0]["library"])
+
+ def test_channel_version_with_none_version_returns_all_version_notes(self):
+ """
+ When a ChannelVersion has version=None, _get_version_notes must not raise
+ TypeError and must return all entries from channel.published_data.
+ """
+ self.channel.main_tree.published = True
+ self.channel.main_tree.save()
+
+ self.channel.published_data = {
+ "1": {"version_notes": "v1 notes"},
+ "3": {"version_notes": "v3 notes"},
+ }
+ # Set channel.version so Channel.on_update() auto-creates ChannelVersion(version=3).
+ self.channel.version = 3
+ self.channel.save()
+
+ # Manually create a ChannelVersion with version=None to reproduce the Sentry bug.
+ channel_version, _created = ChannelVersion.objects.get_or_create(
+ channel=self.channel,
+ version=None,
+ defaults={
+ "kind_count": [],
+ "included_languages": [],
+ "resource_count": 0,
+ "size": 0,
+ },
+ )
+ version_token = channel_version.new_token().token
+
+ lookup_url = reverse(
+ "get_public_channel_lookup",
+ kwargs={"version": "v1", "identifier": version_token},
+ )
+ response = self.client.get(lookup_url + "?channel_versions=true")
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(
+ response.data[0]["version_notes"],
+ {1: "v1 notes", 3: "v3 notes"},
+ )
diff --git a/contentcuration/kolibri_public/tests/utils/mixer.py b/contentcuration/kolibri_public/tests/utils/mixer.py
new file mode 100644
index 0000000000..6e39d09304
--- /dev/null
+++ b/contentcuration/kolibri_public/tests/utils/mixer.py
@@ -0,0 +1,33 @@
+from django.db import models as django_models
+from kolibri_content.fields import DateTimeTzField
+from kolibri_content.fields import UUIDField
+from kolibri_content.router import get_active_content_database
+from mixer.backend.django import GenFactory
+from mixer.backend.django import Mixer
+
+
+class KolibriPublicMixer(Mixer):
+ """Slightly modified Mixer that works correctly with the active
+ content database and with UUIDField.
+ """
+
+ def __init__(self, *args, **kwargs):
+ mixer_factory = GenFactory()
+ mixer_factory.generators[UUIDField] = mixer_factory.generators[
+ django_models.UUIDField
+ ]
+ mixer_factory.generators[DateTimeTzField] = mixer_factory.generators[
+ django_models.DateTimeField
+ ]
+
+ return super().__init__(*args, factory=mixer_factory, **kwargs)
+
+ def postprocess(self, target):
+ if self.params.get("commit"):
+ # Not sure why the `force_insert` is needed, but using the
+ # mixer causes "Save with update_fields did not affect any rows" error
+ # if this is not specified
+ used_db = get_active_content_database(return_none_if_not_set=True)
+ target.save(using=used_db, force_insert=True)
+
+ return target
diff --git a/contentcuration/kolibri_public/utils/annotation.py b/contentcuration/kolibri_public/utils/annotation.py
index 7295c97e39..0d2f5f4d34 100644
--- a/contentcuration/kolibri_public/utils/annotation.py
+++ b/contentcuration/kolibri_public/utils/annotation.py
@@ -1,33 +1,49 @@
"""
-Functions in here are the subset of annotation functions from Kolibri related to channel metadata.
+Functions in here are a modified subset of annotation functions from Kolibri related to channel metadata.
https://github.com/learningequality/kolibri/blob/caec91dd2da5617adfb50332fb698068248e8e47/kolibri/core/content/utils/annotation.py#L731
"""
-import datetime
+from itertools import chain
from django.db.models import Q
from django.db.models import Sum
+from django.utils import timezone
from kolibri_public.models import ChannelMetadata
from kolibri_public.models import ContentNode
from kolibri_public.models import LocalFile
+from kolibri_public.search import annotate_channelmetadata_label_bitmasks
from le_utils.constants import content_kinds
from contentcuration.models import Channel
-def set_channel_metadata_fields(channel_id, public=None):
+def set_channel_metadata_fields(
+ channel_id,
+ public=None,
+ categories=None,
+ countries=None,
+):
+ # Note: The `categories` argument should be a _list_, NOT a _dict_.
+
# Remove unneeded db_lock
- channel = ChannelMetadata.objects.get(id=channel_id)
+ channel_queryset = ChannelMetadata.objects.filter(id=channel_id)
+ channel = channel_queryset.get()
+
calculate_published_size(channel)
calculate_total_resource_count(channel)
calculate_included_languages(channel)
+ calculate_included_categories(channel, categories)
calculate_next_order(channel, public=public)
# Add this to ensure we keep this up to date.
- channel.last_updated = datetime.datetime.now()
+ channel.last_updated = timezone.now()
if public is not None:
channel.public = public
+ if countries is not None:
+ channel.countries.set(countries)
channel.save()
+ annotate_channelmetadata_label_bitmasks(channel_queryset)
+
def files_for_nodes(nodes):
return LocalFile.objects.filter(files__contentnode__in=nodes)
@@ -67,6 +83,28 @@ def calculate_included_languages(channel):
channel.included_languages.add(*list(languages))
+def calculate_included_categories(channel, categories):
+ content_nodes = ContentNode.objects.filter(
+ channel_id=channel.id, available=True
+ ).exclude(categories=None)
+
+ categories_comma_separated_lists = content_nodes.values_list(
+ "categories", flat=True
+ )
+ contentnode_categories = set(
+ chain.from_iterable(
+ (
+ categories_comma_separated_list.split(",")
+ for categories_comma_separated_list in categories_comma_separated_lists
+ )
+ )
+ )
+
+ all_categories = sorted(set(categories or []).union(contentnode_categories))
+ channel.categories = all_categories
+ channel.save()
+
+
def calculate_next_order(channel, public=False):
# This has been edited from the source Kolibri, in order
# to make the order match given by the public channel API on Studio.
diff --git a/contentcuration/kolibri_public/utils/export_channel_to_kolibri_public.py b/contentcuration/kolibri_public/utils/export_channel_to_kolibri_public.py
new file mode 100644
index 0000000000..e81a8badf1
--- /dev/null
+++ b/contentcuration/kolibri_public/utils/export_channel_to_kolibri_public.py
@@ -0,0 +1,91 @@
+import logging
+import os
+import shutil
+import tempfile
+
+from django.conf import settings
+from django.core.files.storage import default_storage as storage
+from django.core.management import call_command
+from kolibri_content.apps import KolibriContentConfig
+from kolibri_content.models import ChannelMetadata as ExportedChannelMetadata
+from kolibri_content.router import get_active_content_database
+from kolibri_content.router import using_content_database
+from kolibri_public.utils.mapper import ChannelMapper
+
+
+logger = logging.getLogger(__file__)
+
+
+class using_temp_migrated_content_database:
+ """
+ A wrapper context manager for read-only access to a content database
+ that might not have all current migrations applied. Works by copying
+ the database to a temporary file, applying migrations to this temporary
+ database and then using this temporary database.
+ """
+
+ def __init__(self, database_storage_path):
+ self.database_path = database_storage_path
+ self._inner_mgr = None
+
+ def __enter__(self):
+ self._named_temporary_file_mgr = tempfile.NamedTemporaryFile(suffix=".sqlite3")
+ self.temp_database_file = self._named_temporary_file_mgr.__enter__()
+
+ with storage.open(self.database_path, "rb") as db_file:
+ shutil.copyfileobj(db_file, self.temp_database_file)
+ self.temp_database_file.seek(0)
+
+ with using_content_database(self.temp_database_file.name):
+ # Run migration to handle old content databases published prior to current fields being added.
+ call_command(
+ "migrate",
+ app_label=KolibriContentConfig.label,
+ database=get_active_content_database(),
+ )
+
+ self._inner_mgr = using_content_database(self.temp_database_file.name)
+ self._inner_mgr.__enter__()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._inner_mgr.__exit__(exc_type, exc_val, exc_tb)
+ self._named_temporary_file_mgr.__exit__(exc_type, exc_val, exc_tb)
+
+
+def export_channel_to_kolibri_public(
+ channel_id,
+ channel_version=None,
+ public=True,
+ categories=None,
+ countries=None,
+):
+ # Note: The `categories` argument should be a _list_, NOT a _dict_.
+ logger.info("Putting channel {} into kolibri_public".format(channel_id))
+
+ versioned_db_filename = "{id}-{version}.sqlite3".format(
+ id=channel_id, version=channel_version
+ )
+ unversioned_db_filename = "{id}.sqlite3".format(id=channel_id)
+
+ versioned_db_storage_path = os.path.join(settings.DB_ROOT, versioned_db_filename)
+ unversioned_db_storage_path = os.path.join(
+ settings.DB_ROOT, unversioned_db_filename
+ )
+
+ if channel_version is None:
+ db_storage_path = unversioned_db_storage_path
+ else:
+ db_storage_path = versioned_db_storage_path
+
+ with using_temp_migrated_content_database(db_storage_path):
+ channel = ExportedChannelMetadata.objects.get(id=channel_id)
+ logger.info(
+ "Found channel {} for id: {} mapping now".format(channel.name, channel_id)
+ )
+ mapper = ChannelMapper(
+ channel=channel,
+ public=public,
+ categories=categories,
+ countries=countries,
+ )
+ mapper.run()
diff --git a/contentcuration/kolibri_public/utils/mapper.py b/contentcuration/kolibri_public/utils/mapper.py
index 01dc1f0726..0b17527b48 100644
--- a/contentcuration/kolibri_public/utils/mapper.py
+++ b/contentcuration/kolibri_public/utils/mapper.py
@@ -3,7 +3,7 @@
from kolibri_content import models as kolibri_content_models
from kolibri_content.base_models import MAX_TAG_LENGTH
from kolibri_public import models as kolibri_public_models
-from kolibri_public.search import annotate_label_bitmasks
+from kolibri_public.search import annotate_contentnode_label_bitmasks
from kolibri_public.utils.annotation import set_channel_metadata_fields
from le_utils.constants import content_kinds
@@ -19,9 +19,23 @@ class ChannelMapper(object):
Foreign Keyed from the root ContentNode.
"""
- def __init__(self, channel, public=True):
+ def __init__(
+ self,
+ channel,
+ public=True,
+ categories=None,
+ countries=None,
+ ):
+ # Note: The argument `channel` is an instance of `kolibri_content.models.ChannelMetadata,`
+ # which belongs to a specific channel version to be exported. Therefore, we do not
+ # need to explicitly pass the channel version as an argument here.
+
+ # Note: The `categories` argument should be a _list_, NOT a _dict_.
+
self.channel = channel
self.public = public
+ self.categories = categories
+ self.countries = countries
@property
def overrides(self):
@@ -54,10 +68,24 @@ def run(self):
)
self.mapped_channel.public = self.public
self.mapped_channel.save_base(raw=True)
- annotate_label_bitmasks(self.mapped_root.get_descendants(include_self=True))
+
+ annotate_contentnode_label_bitmasks(
+ self.mapped_root.get_descendants(include_self=True)
+ )
# Rather than set the ancestors fields after mapping, like it is done in Kolibri
# here we set it during mapping as we are already recursing through the tree.
- set_channel_metadata_fields(self.mapped_channel.id, public=self.public)
+
+ set_channel_metadata_fields(
+ self.mapped_channel.id,
+ public=self.public,
+ categories=self.categories,
+ countries=self.countries,
+ )
+
+ # Refreshing this is needed, because otherwise the fields set in the
+ # set_channel_metadata_fields function will not be reflected in the
+ # self.mapped_channel object.
+ self.mapped_channel.refresh_from_db()
def _map_model(self, source, Model):
properties = {}
diff --git a/contentcuration/kolibri_public/views.py b/contentcuration/kolibri_public/views.py
index 00817d4e64..6fe7446de4 100644
--- a/contentcuration/kolibri_public/views.py
+++ b/contentcuration/kolibri_public/views.py
@@ -33,16 +33,23 @@
from django_filters.rest_framework import NumberFilter
from django_filters.rest_framework import UUIDFilter
from kolibri_public import models
-from kolibri_public.search import get_available_metadata_labels
+from kolibri_public.search import get_channel_available_metadata_labels
+from kolibri_public.search import get_contentnode_available_metadata_labels
from kolibri_public.stopwords import stopwords_set
from le_utils.constants import content_kinds
+from le_utils.constants import library as library_constants
from rest_framework import status
+from rest_framework.decorators import action
+from rest_framework.filters import SearchFilter
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from contentcuration.middleware.locale import locale_exempt
from contentcuration.middleware.session import session_exempt
+from contentcuration.models import ChannelVersion
+from contentcuration.models import Country
from contentcuration.models import generate_storage_url
+from contentcuration.utils.pagination import CachedListPagination
from contentcuration.utils.pagination import ValuesViewsetCursorPagination
from contentcuration.viewsets.base import BaseValuesViewset
from contentcuration.viewsets.base import ReadOnlyValuesViewset
@@ -82,13 +89,51 @@ def wrapper_func(*args, **kwargs):
MODALITIES = set(["QUIZ"])
+def bitmask_contains_and(queryset, name, value):
+ """
+ A filtering method that filters instances matching all provided
+ comma-separated values using bitmask fields on the model.
+ """
+ return queryset.has_all_labels(name, value.split(","))
+
+
+class UUIDInFilter(BaseInFilter, UUIDFilter):
+ pass
+
+
+class CharInFilter(BaseInFilter, CharFilter):
+ pass
+
+
class ChannelMetadataFilter(FilterSet):
+ def __init__(self, data=None, *args, **kwargs):
+ # if filterset is bound, use initial values as defaults
+ if data is not None:
+ data = data.copy()
+ for name, f in self.base_filters.items():
+ initial = f.extra.get("initial")
+ # filter param is either missing or empty, use initial as default
+ if not data.get(name) and initial:
+ data[name] = initial
+ super().__init__(data, *args, **kwargs)
+
available = BooleanFilter(method="filter_available", label="Available")
has_exercise = BooleanFilter(method="filter_has_exercise", label="Has exercises")
+ categories = CharFilter(method=bitmask_contains_and, label="Categories")
+ countries = CharInFilter(field_name="countries", label="Countries")
+ public = BooleanFilter(field_name="public", label="Public", initial=True)
+ languages = CharInFilter(field_name="included_languages__id", label="Languages")
class Meta:
model = models.ChannelMetadata
- fields = ("available", "has_exercise")
+ fields = (
+ "available",
+ "has_exercise",
+ "categories",
+ "countries",
+ "public",
+ "languages",
+ )
def filter_has_exercise(self, queryset, name, value):
queryset = queryset.annotate(
@@ -107,11 +152,22 @@ def filter_available(self, queryset, name, value):
return queryset.filter(root__available=value)
+class ChannelMetadataListPagination(CachedListPagination):
+ page_size = None
+ page_size_query_param = "page_size"
+ max_page_size = 1000
+
+
@method_decorator(metadata_cache, name="dispatch")
class ChannelMetadataViewSet(ReadOnlyValuesViewset):
- filter_backends = (DjangoFilterBackend,)
+ filter_backends = (
+ SearchFilter,
+ DjangoFilterBackend,
+ )
# Update from filter_class to filterset_class for newer version of Django Filters
filterset_class = ChannelMetadataFilter
+ pagination_class = ChannelMetadataListPagination
+ search_fields = ("name",)
# Add an explicit allow any permission class to override the Studio default
permission_classes = (AllowAny,)
@@ -133,6 +189,7 @@ class ChannelMetadataViewSet(ReadOnlyValuesViewset):
"public",
"total_resource_count",
"published_size",
+ "categories",
)
field_map = {
@@ -145,9 +202,13 @@ class ChannelMetadataViewSet(ReadOnlyValuesViewset):
}
def get_queryset(self):
- return models.ChannelMetadata.objects.all()
+ return models.ChannelMetadata.objects.all().order_by("name")
def consolidate(self, items, queryset):
+ # Only keep a single item for every channel ID, to get rid of possible
+ # duplicates caused by filtering
+ items = list(OrderedDict((item["id"], item) for item in items).values())
+
included_languages = {}
for (
channel_id,
@@ -160,18 +221,57 @@ def consolidate(self, items, queryset):
if channel_id not in included_languages:
included_languages[channel_id] = []
included_languages[channel_id].append(language_id)
+
+ channel_versions_q = Q()
+
+ # Getting channel tokens in the consolidate method instead of doing it in the annotate method
+ # to make invisible the difference in the representation between public and private models UUIDFields
+ for channel in items:
+ channel_versions_q |= Q(
+ channel_id=channel["id"], version=channel["version"]
+ )
+
+ channel_tokens = {}
+
+ for channel_version in ChannelVersion.objects.filter(channel_versions_q).values(
+ "channel_id", "secret_token__token"
+ ):
+ channel_tokens[channel_version["channel_id"]] = channel_version[
+ "secret_token__token"
+ ]
+
+ countries = {}
+ for (channel_id, country_code) in Country.objects.filter(
+ public_channels__in=queryset
+ ).values_list("public_channels", "code"):
+ if channel_id not in countries:
+ countries[channel_id] = []
+ countries[channel_id].append(country_code)
+
for item in items:
item["included_languages"] = included_languages.get(item["id"], [])
+ item["countries"] = countries.get(item["id"], [])
+ item["token"] = channel_tokens.get(item["id"])
item["last_published"] = item["last_updated"]
- return items
-
-
-class UUIDInFilter(BaseInFilter, UUIDFilter):
- pass
+ # v2 non-public channels are always community library channels (unlike v1
+ # channel tokens, which return null for non-public channels).
+ item["library"] = (
+ library_constants.KOLIBRI
+ if item["public"]
+ else library_constants.COMMUNITY
+ )
+ return items
-class CharInFilter(BaseInFilter, CharFilter):
- pass
+ @action(detail=False, methods=["get"])
+ def labels(self, request):
+ """
+ Returns available filter option values for the channel list.
+ The response is an object with keys for each filterable field, each
+ containing the set of values present across the filtered queryset.
+ """
+ queryset = self.filter_queryset(self.get_queryset())
+ return Response(get_channel_available_metadata_labels(queryset))
contentnode_filter_fields = [
@@ -228,12 +328,12 @@ class ContentNodeFilter(FilterSet):
parent__isnull = BooleanFilter(field_name="parent", lookup_expr="isnull")
include_coach_content = BooleanFilter(method="filter_include_coach_content")
contains_quiz = CharFilter(method="filter_contains_quiz")
- grade_levels = CharFilter(method="bitmask_contains_and")
- resource_types = CharFilter(method="bitmask_contains_and")
- learning_activities = CharFilter(method="bitmask_contains_and")
- accessibility_labels = CharFilter(method="bitmask_contains_and")
- categories = CharFilter(method="bitmask_contains_and")
- learner_needs = CharFilter(method="bitmask_contains_and")
+ grade_levels = CharFilter(method=bitmask_contains_and)
+ resource_types = CharFilter(method=bitmask_contains_and)
+ learning_activities = CharFilter(method=bitmask_contains_and)
+ accessibility_labels = CharFilter(method=bitmask_contains_and)
+ categories = CharFilter(method=bitmask_contains_and)
+ learner_needs = CharFilter(method=bitmask_contains_and)
keywords = CharFilter(method="filter_keywords")
channels = UUIDInFilter(field_name="channel_id")
languages = CharInFilter(field_name="lang_id")
@@ -344,9 +444,6 @@ def filter_keywords(self, queryset, name, value):
return queryset.filter(query)
- def bitmask_contains_and(self, queryset, name, value):
- return queryset.has_all_labels(name, value.split(","))
-
def map_file(file):
file["checksum"] = file.pop("local_file__id")
@@ -533,7 +630,10 @@ def get_paginated_response(self, data):
[
("more", self.get_more()),
("results", data),
- ("labels", get_available_metadata_labels(self.queryset)),
+ (
+ "labels",
+ get_contentnode_available_metadata_labels(self.queryset),
+ ),
]
)
)
diff --git a/contentcuration/kolibri_public/views_v1.py b/contentcuration/kolibri_public/views_v1.py
index 1aec8b0ba6..973915a651 100644
--- a/contentcuration/kolibri_public/views_v1.py
+++ b/contentcuration/kolibri_public/views_v1.py
@@ -1,4 +1,5 @@
import json
+from collections import OrderedDict
from django.conf import settings
from django.contrib.sites.models import Site
@@ -10,6 +11,7 @@
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import cache_page
from kolibri_content.constants.schema_versions import MIN_CONTENT_SCHEMA_VERSION
+from le_utils.constants import library as library_constants
from le_utils.uuidv5 import generate_ecosystem_namespaced_uuid
from rest_framework import viewsets
from rest_framework.decorators import api_view
@@ -17,8 +19,10 @@
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
+from contentcuration.constants import community_library_submission as cls_constants
from contentcuration.decorators import cache_no_user_data
from contentcuration.models import Channel
+from contentcuration.models import ChannelVersion
from contentcuration.serializers import PublicChannelSerializer
@@ -28,6 +32,60 @@ def _get_channel_list(version, params, identifier=None):
raise LookupError()
+def _get_version_notes(channel, channel_version):
+ data = {
+ int(k): v["version_notes"]
+ for k, v in channel.published_data.items()
+ if channel_version.version is None or int(k) <= channel_version.version
+ }
+ return OrderedDict(sorted(data.items()))
+
+
+def get_thumbnail_encoding(channel_version):
+ if channel_version.channel_thumbnail_encoding:
+ return channel_version.channel_thumbnail_encoding.get("base64")
+ return None
+
+
+def _get_channel_version_library(channel_version):
+ channel = channel_version.channel
+ if channel.community_library_submissions.filter(
+ channel_version=channel_version.version,
+ status__in=[cls_constants.STATUS_APPROVED, cls_constants.STATUS_LIVE],
+ ).exists():
+ return library_constants.COMMUNITY
+ return None
+
+
+def _serialize_channel_version(channel_version_qs):
+ channel_version = channel_version_qs.first()
+ if not channel_version or not channel_version.channel:
+ return []
+
+ channel = channel_version.channel
+ return [
+ {
+ "id": channel_version.channel_id,
+ "name": channel_version.channel_name,
+ "language": channel_version.channel_language_id,
+ "public": channel.public,
+ "description": channel_version.channel_description,
+ "icon_encoding": get_thumbnail_encoding(channel_version),
+ "version_notes": _get_version_notes(channel, channel_version),
+ "version": channel_version.version,
+ "kind_count": channel_version.kind_count,
+ "included_languages": channel_version.included_languages,
+ "total_resource_count": channel_version.resource_count,
+ "published_size": channel_version.size,
+ "last_published": channel_version.date_published,
+ "matching_tokens": [channel_version.secret_token.token]
+ if channel_version.secret_token
+ else [],
+ "library": _get_channel_version_library(channel_version),
+ }
+ ]
+
+
def _get_channel_list_v1(params, identifier=None):
keyword = params.get("keyword", "").strip()
language_id = params.get("language", "").strip()
@@ -40,6 +98,20 @@ def _get_channel_list_v1(params, identifier=None):
)
if not channels.exists():
channels = Channel.objects.filter(pk=identifier)
+
+ if not channels.exists() and params.get("channel_versions") == "true":
+ # Only resolve ChannelVersion tokens when the caller explicitly opts in.
+ # This prevents older Kolibri clients from accidentally retrieving data
+ # they cannot parse correctly.
+ channel_version = ChannelVersion.objects.select_related(
+ "secret_token", "channel"
+ ).filter(
+ secret_token__token=identifier,
+ channel__deleted=False,
+ )
+ if channel_version.exists():
+ # return early as we won't need to apply the other filters for channel version tokens
+ return channel_version
else:
channels = Channel.objects.prefetch_related("secret_tokens").filter(
Q(public=True) | Q(secret_tokens__token__in=token_list)
@@ -96,7 +168,12 @@ def get_public_channel_lookup(request, version, identifier):
return HttpResponseNotFound(
_("No channel matching {} found").format(escape(identifier))
)
- return Response(PublicChannelSerializer(channel_list, many=True).data)
+
+ if channel_list.model == ChannelVersion:
+ channel_list = _serialize_channel_version(channel_list)
+ return Response(channel_list)
+ else:
+ return Response(PublicChannelSerializer(channel_list, many=True).data)
@api_view(["GET"])
diff --git a/contentcuration/locale/ar/LC_MESSAGES/README.md b/contentcuration/locale/ar/LC_MESSAGES/README.md
index 0f82b94d50..014a952e9a 100644
--- a/contentcuration/locale/ar/LC_MESSAGES/README.md
+++ b/contentcuration/locale/ar/LC_MESSAGES/README.md
@@ -1 +1 @@
-The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
+The JSON messages files in this folder were generated by kolibri-i18n csvToJSON.js
diff --git a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json
index d5d976b6ec..fb4f8f3528 100644
--- a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json
@@ -67,8 +67,6 @@
"AdministrationAppError.unauthorizedDetails": "يجب أن تكون مسؤولاً في الاستوديو لتتمكن من عرض هذه الصفحة",
"AdministrationIndex.channelsLabel": "القنوات التعليمية",
"AdministrationIndex.usersLabel": "المستخدمون",
- "Alert.closeButtonLabel": "موافقة",
- "Alert.dontShowAgain": "لا تعرض هذه الرسالة مرة أخرى",
"AnswersEditor.answersLabel": "الأجوبة",
"AnswersEditor.newAnswerBtnLabel": "إجابة جديدة",
"AnswersEditor.noAnswersPlaceholder": "لا توجد خيارات لإجابة هذا السؤال",
@@ -184,26 +182,31 @@
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "مميزة بنجمة",
"CatalogFilterBar.subtitles": "الترجمات",
- "CatalogFilters.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري",
- "CatalogFilters.coachLabel": "مصادر للمدرِّبين",
- "CatalogFilters.copyright": "© {year} المساواة في التعلم",
- "CatalogFilters.formatLabel": "الصيغ",
- "CatalogFilters.frequentlyAskedQuestionsLink": "الأسئلة الأكثر تكراراً",
- "CatalogFilters.includesLabel": "عرض القنوات فقط مع",
- "CatalogFilters.licenseLabel": "التراخيص",
- "CatalogFilters.searchLabel": "الكلمات المفتاح",
- "CatalogFilters.searchText": "بحث",
- "CatalogFilters.starredLabel": "مميزة بنجمة",
- "CatalogFilters.subtitlesLabel": "عناوين الصور أو النص السمعي ",
+ "CatalogFilterPanelContent.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري",
+ "CatalogFilterPanelContent.coachLabel": "مصادر للمدرِّبين",
+ "CatalogFilterPanelContent.copyright": "© {year} منظمة Learning Equality",
+ "CatalogFilterPanelContent.formatLabel": "الصيغ",
+ "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "الأسئلة الأكثر تكراراً",
+ "CatalogFilterPanelContent.includesLabel": "عرض القنوات فقط مع",
+ "CatalogFilterPanelContent.licenseLabel": "التراخيص",
+ "CatalogFilterPanelContent.searchLabel": "الكلمات المفتاح",
+ "CatalogFilterPanelContent.starredLabel": "تم التمييز بنجمة",
+ "CatalogFilterPanelContent.subtitlesLabel": "عناوين الصور أو النص السمعي ",
+ "CatalogFilters.filterLabel": "فلترة",
"CatalogList.cancelButton": "إلغاء",
"CatalogList.channelSelectionCount": "{count, plural, zero {قنوات} one {# قناة تم تحديدها} two {# قناتان تم تحديدهما} few {# قنوات تم تحديدها} many {# قنوات تم تحديدها}\n =1 {# قناة تم تحديدها}\n other {# قنوات تم تحديدها}}",
+ "CatalogList.copyToken": "نسخ معرّف القناة",
"CatalogList.downloadButton": "تحميل",
"CatalogList.downloadCSV": "تنزيل ملف CSV",
"CatalogList.downloadPDF": "تنزيل ملف PDF",
"CatalogList.downloadingMessage": "بدأ التنزيل",
+ "CatalogList.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني",
+ "CatalogList.moreOptions": "خيارات أخرى",
"CatalogList.resultsText": "{count, plural, zero {نتائج} one {# نتيجة تم العثور عليها} two {# نتيجتان تم العثور عليهما} few {# نتائج تم العثور عليها} many {# نتائج تم العثور عليها}\n =1 {#نتيجة تم العثور عليها}\n other {# نتائج تم العثور عليها}}",
"CatalogList.selectAll": "تحديد الكل",
"CatalogList.selectChannels": "تنزيل ملخص القنوات المحددة",
+ "CatalogList.title": "مكتبة المحتوى",
+ "CatalogList.viewContent": "عرض القناة على كوليبري",
"CategoryOptions.noCategoryFoundText": " لم يتم العثور على الفئة",
"ChangePasswordForm.cancelAction": "إلغاء",
"ChangePasswordForm.changePasswordHeader": "تغيير كلمة المرور",
@@ -224,9 +227,6 @@
"ChannelCatalogFrontPage.languagesHeading": "اللغات",
"ChannelCatalogFrontPage.numberOfChannels": "{ num } قنوات",
"ChannelCatalogFrontPage.subtitlesIncludedText": "عناوين الصور أو النص السمعي ",
- "ChannelDeletedError.backToHomeAction": "العودة إلى الصفحة الرئيسية",
- "ChannelDeletedError.channelDeletedDetails": "هذه القناة غير موجودة أو ربما تمت إزالتها. يرجى التواصل معنا عبر البريد الإلكتروني content@learningequality.org في حال كنت تظن أن هنالك خطأ.",
- "ChannelDeletedError.channelDeletedHeader": "لم يتم إيجاد القناة التعليمية",
"ChannelDetailsModal.downloadButton": "تنزيل ملخص القناة",
"ChannelDetailsModal.downloadCSV": "تنزيل ملف CSV",
"ChannelDetailsModal.downloadPDF": "تنزيل ملف PDF",
@@ -263,23 +263,17 @@
"ChannelInvitation.editText": "{sender} قام بدعوتك لتحرير {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "التوجّه إلى القناة",
"ChannelInvitation.viewText": "{sender} قام بدعوتك لعرض {channel}",
- "ChannelItem.cancel": "إلغاء",
"ChannelItem.channelDeletedSnackbar": "تم حذف القناة",
"ChannelItem.channelLanguageNotSetIndicator": "لم يتم تعيين لغة",
"ChannelItem.channelRemovedSnackbar": "تمت إزالة القناة",
"ChannelItem.copyToken": "نسخ الرمز التعريفي للقناة",
"ChannelItem.deleteChannel": "حذف القناة التعليمية",
- "ChannelItem.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.",
- "ChannelItem.deleteTitle": "حذف هذه القناة",
"ChannelItem.details": "التفاصيل",
"ChannelItem.editChannel": "تعديل تفاصيل القناة",
"ChannelItem.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني",
"ChannelItem.lastPublished": "منشور {last_published}",
"ChannelItem.lastUpdated": "{updated} تم تحديثها",
- "ChannelItem.removeBtn": "إزالة",
- "ChannelItem.removeChannel": "إزالة من قائمة القنوات",
- "ChannelItem.removePrompt": "الوصول المتاح لك يمكنك من عرض هذه القناة فقط. يرجى تأكيد أنك تريد إزالتها من قائمة القنوات الخاصة بك.",
- "ChannelItem.removeTitle": "إزالة من قائمة القنوات",
+ "ChannelItem.removeChannel": "إزالة القناة",
"ChannelItem.resourceCount": "{count, plural, zero {# مصادر} one {# مصادر} two {# مصدران} few {# مصادر} many {# مصدراً}\n =1 {# مصدر}\n other {# مصادر}}",
"ChannelItem.unpublishedText": "غير منشور",
"ChannelItem.versionText": "نسخة الإصدار {version}",
@@ -292,7 +286,6 @@
"ChannelListIndex.catalog": "مكتبة المحتوى",
"ChannelListIndex.channelSets": "المجموعات",
"ChannelListIndex.frequentlyAskedQuestions": "الأسئلة المتكررة",
- "ChannelListIndex.invitations": "لديكَ {count, plural, zero {# دعوات} one {# دعوات} two {# دعوتان} few {# دعوات} many {# دعوة}\n =1 {# دعوة}\n other {# دعوات}}",
"ChannelListIndex.libraryTitle": "دليل مكتبة محتوى كوليبري",
"ChannelModal.APIText": "القنوات التي يتم إنشاؤها تلقائياً غير قابلة للتحرير.",
"ChannelModal.changesSaved": "تم حفظ التغييرات",
@@ -316,25 +309,6 @@
"ChannelNotFoundError.channelNotFoundHeader": "لم يتم إيجاد القناة التعليمية",
"ChannelSelectionList.noChannelsFound": "لم يتم العثور على أية قنوات",
"ChannelSelectionList.searchText": "البحث عن قناة",
- "ChannelSetItem.cancel": "إلغاء",
- "ChannelSetItem.delete": "حذف المجموعة",
- "ChannelSetItem.deleteChannelSetText": "هل أنت متأكد من أنك تريد حذف هذه المجموعة؟",
- "ChannelSetItem.deleteChannelSetTitle": "حذف المجموعة",
- "ChannelSetItem.edit": "تحرير المجموعة",
- "ChannelSetItem.options": "الخيارات",
- "ChannelSetItem.saving": "جاري الحفظ",
- "ChannelSetList.aboutChannelSets": "حول المجموعات",
- "ChannelSetList.aboutChannelSetsLink": "تعرّف على المزيد حول المجموعات",
- "ChannelSetList.addChannelSetTitle": "مجموعة جديدة",
- "ChannelSetList.cancelButtonLabel": "إغلاق",
- "ChannelSetList.channelNumber": "عدد القنوات",
- "ChannelSetList.channelSetsDescriptionText": "تحتوي المجموعة على العديد من قنوات استوديو كوليبري التي يمكن استيرادها في دفعة واحدة إلى كوليبري برمز تعريفي واحد.",
- "ChannelSetList.channelSetsDisclaimer": "ستحتاج إلى إصدار كوليبري 0.12.0 أو ما بعده لاستيراد مجموعات القنوات",
- "ChannelSetList.channelSetsInstructionsText": "يمكنك إنشاء مجموعة عن طريق تحديد القنوات التي تريد استيرادها معاً.",
- "ChannelSetList.noChannelSetsFound": "يمكنك تجميع قنوات متعددة لإنشاء مجموعة. ثم يمكن استيراد المجموعة بأكملها إلى كوليبري دفعة واحدة باستخدام رمز المجموعة التعريفي.",
- "ChannelSetList.options": "الخيارات",
- "ChannelSetList.title": "اسم المجموعة",
- "ChannelSetList.token": "معرّف الرمز التعريفي",
"ChannelSetModal.bookmark": "تم التمييز بنجمة",
"ChannelSetModal.channelAdded": "تمت إضافة القناة",
"ChannelSetModal.channelCountText": "{channelCount, plural, zero {# قنوات} one {# قنوات} two {# قناتان} few {# قنوات} many {# قناة} =0 {لا توجد قنوات منشورة في المجموعة الخاصة بك} =1 {# قناة} other {# قنوات}}",
@@ -533,6 +507,163 @@
"CommonMetadataStrings.webDesign": "تصميم مواقع الإنترنت",
"CommonMetadataStrings.work": "عمل",
"CommonMetadataStrings.writing": "الكتابة",
+ "CommonStrings.backAction": "رجوع",
+ "CommonStrings.channelDetailsLabel": "تفاصيل القناة",
+ "CommonStrings.clearAction": "إزالة",
+ "CommonStrings.closeAction": "إغلاق",
+ "CommonStrings.copyChannelTokenAction": "نسخ معرّف القناة",
+ "CommonStrings.dismissAction": "رفض",
+ "CommonStrings.genericErrorMessage": "عذرًا لقد حدث خطأ ما، يُرجى المحاولة مرة أخرى.",
+ "CommonStrings.previewAction": "معاينة",
+ "CommonStrings.seeAllAction": "عرض الكل",
+ "CommonStrings.seeLessAction": "عرض أقل",
+ "CommunityChannelsStrings.aboutCommunityLibraryDescription": "تحتوي مكتبة المجتمع على قنوات قدّمها المجتمع وقُبلت لتُصبح متاحة للاكتشاف في الاستوديو.",
+ "CommunityChannelsStrings.aboutCommunityLibraryTitle": "حول مكتبة المجتمع",
+ "CommunityChannelsStrings.activityHistoryLabel": "تاريخ الأنشطة",
+ "CommunityChannelsStrings.adminLabel": "المشرف",
+ "CommunityChannelsStrings.allLicensesCompatible": "جميع التراخيص متوافقة مع مكتبة المجتمع.",
+ "CommunityChannelsStrings.allNotificationsLabel": "جميع الإشعارات",
+ "CommunityChannelsStrings.alreadySubmittedWarningDescription": "يرجى الانتظار حتى تخضع القناة للمُراجعة. سترى إشعارًا في حسابك على الاستوديو بعد اكتمالها.",
+ "CommunityChannelsStrings.alreadySubmittedWarningTitle": "تم بالفعل تقديم هذا الإصدار من القناة إلى مكتبة المجتمع.",
+ "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) اعتمَدَ {channelVersion}",
+ "CommunityChannelsStrings.approvedPrimaryInfo": "هناك إصدار سابق مُتاح حاليًا في مكتبة المجتمع. سيرى المراجعون أحدث نسخة مُقدَّمة أولًا.",
+ "CommunityChannelsStrings.approvedStatus": "مُعتمَد",
+ "CommunityChannelsStrings.availableStatus": "متاح في مكتبة المجتمع",
+ "CommunityChannelsStrings.cancelAction": "إلغاء",
+ "CommunityChannelsStrings.categoriesLabel": "الفئات",
+ "CommunityChannelsStrings.channelCannotBeDistributed": "لا يمكن توزيع هذه القناة عبر كوليبري.",
+ "CommunityChannelsStrings.channelFitAttributionLabel": "ذكر المصدر",
+ "CommunityChannelsStrings.channelFitChannelInfoLabel": "معلومات القناة",
+ "CommunityChannelsStrings.channelFitChecklistAttribution": "هل لكل مصدر لديه مؤلف مُدرج؟",
+ "CommunityChannelsStrings.channelFitChecklistChannelInfo": "هل أدخلت المعلومات الأساسية لقناتك مثل العنوان والوصف والصورة المصغّرة واللغة والفئة والمستوى؟",
+ "CommunityChannelsStrings.channelFitChecklistIntro": "معايير التقديم إلى مكتبة المجتمع",
+ "CommunityChannelsStrings.channelFitChecklistLicense": "هل المصادر الموجودة في قناتك تتبع لرخصة مفتوحة أو تعتبر من الملكية العامة؟",
+ "CommunityChannelsStrings.channelFitChecklistOfflineUse": "هل تعمل المصادر في قناتك دون اتصال بالإنترنت؟",
+ "CommunityChannelsStrings.channelFitChecklistQuality": "هل قام أي شخص في فريقك أو في مؤسستك بمراجعة القناة؟",
+ "CommunityChannelsStrings.channelFitLicenseLabel": "الترخيص",
+ "CommunityChannelsStrings.channelFitOfflineUseLabel": "استخدام دون اتصال بالإنترنت",
+ "CommunityChannelsStrings.channelFitQualityLabel": "الجودة",
+ "CommunityChannelsStrings.channelTokenDescription": "يمكنك استخدام هذا الرمز لاستيراد القناة التجريبية ومعاينتها في كوليبري. يرجى ملاحظة أن الرمز الخاص بالقناة المنشورة نهائيًا سيكون مختلفًا.",
+ "CommunityChannelsStrings.channelVersion": "{name} v{version}",
+ "CommunityChannelsStrings.channelVersionTokenLabel": "رمز إصدار القناة",
+ "CommunityChannelsStrings.clearAll": "مسح الكل",
+ "CommunityChannelsStrings.clearAllAction": "مسح الكل",
+ "CommunityChannelsStrings.communityLibraryCTADescription": "هل لديك قناة تستحق المشاركة مع المعلّمين والمتعلمين الآخرين؟ قدّمها للمراجعة في قائمة \"مشاركة\" لتصبح متاحة في الاستوديو.",
+ "CommunityChannelsStrings.communityLibraryCTATitle": "ساعد في تنمية مكتبة المجتمع",
+ "CommunityChannelsStrings.communityLibraryDescription": "تصفّح القنوات التي قدّمها المجتمع وتمت الموافقة عليها لتكون متاحة في الاستوديو. انسخ رمزًا لاستخدام القناة في كوليبري.",
+ "CommunityChannelsStrings.communityLibraryLabel": "مكتبة المجتمع",
+ "CommunityChannelsStrings.communityLibrarySubmissionLabel": "تقديم إلى مكتبة المجتمع",
+ "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - جميع التراخيص متوافقة مع مكتبة المجتمع.",
+ "CommunityChannelsStrings.confirmDistributionRights": "يرجى تأكيد أن لديك الحق في توزيع هذه الموارد عبر كوليبري.",
+ "CommunityChannelsStrings.confirmReplacementText": "أفهم أن هذا سيستبدل ما قدمته سابقًا في قائمة المراجعة",
+ "CommunityChannelsStrings.countriesInfoText": "اختر دولة واحدة أو أكثر لوضع وسم على ما تقدمه في القناة. على سبيل المثال، إذا كانت قناتك تحتوي على مواد متوافقة مع منهج وطني أو محتوى خاص بمنطقة معيّنة، سيساعد اختيار الدول المناسبة المستخدمين في العثور عليها. اترك هذا الحقل فارغًا خلاف ذلك.",
+ "CommunityChannelsStrings.countryLabel": "البلدان",
+ "CommunityChannelsStrings.descriptionLabel": "صِف ما يتضمّنه هذا التقديم",
+ "CommunityChannelsStrings.dismissAction": "رفض",
+ "CommunityChannelsStrings.draftBeingPublishedNotice": "جارٍ نشر النسخة التجريبية",
+ "CommunityChannelsStrings.draftPublishedNotice": "نُشرت النسخة التجريبية بنجاح",
+ "CommunityChannelsStrings.draftTokenLabel": "رمز النسخة التجريبية",
+ "CommunityChannelsStrings.editorLabel": "المحرر",
+ "CommunityChannelsStrings.emptyNotificationsNotice": "ليس لديك أي إشعارات حاليًا.",
+ "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "لا توجد إشعارات تطابق عوامل التصفية المحددة.",
+ "CommunityChannelsStrings.errorLoadingVersions": "تعذّر تحميل سجل الإصدارات",
+ "CommunityChannelsStrings.errorSnackbar": "حدث خطأ أثناء إرسال القناة",
+ "CommunityChannelsStrings.feedbackNotesLabel": "ملاحظات المُراجع",
+ "CommunityChannelsStrings.filterByDateLabel": "فرز حسب التاريخ",
+ "CommunityChannelsStrings.filterByStatusLabel": "فرز حسب الحالة",
+ "CommunityChannelsStrings.filterLabel": "فلترة",
+ "CommunityChannelsStrings.fixLicensingBeforeSubmission": "يرجى تصحيح الترخيص قبل إرسال نسخة جديدة.",
+ "CommunityChannelsStrings.flaggedNotification": "التغييرات المطلوبة للإصدار {channelVersion}",
+ "CommunityChannelsStrings.flaggedStatus": "يحتاج إجراء تغييرات",
+ "CommunityChannelsStrings.getDraftTokenAction": "نسخ رمز القناة التجريبية",
+ "CommunityChannelsStrings.goToMyChannelsAction": "الانتقال إلى قنواتي",
+ "CommunityChannelsStrings.hideCriteriaAction": "إخفاء المعايير",
+ "CommunityChannelsStrings.hideVersions": "إخفاء الإصدارات",
+ "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - لا يمكن توزيع هذه القناة عبر كوليبري. إذا لم تتمكن من تغيير الترخيص، قم بإزالة جميع المصادر التي تحمل اسم \"{licenseNames}\" قبل إعادة الإرسال.",
+ "CommunityChannelsStrings.incompatibleLicensesDetected": "تم اكتشاف تراخيص غير متوافقة.",
+ "CommunityChannelsStrings.incompleteResourcesDescription1": "لن يتم نشر المصادر غير المكتملة وإتاحتها للتنزيل في \"كوليبري\".",
+ "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {مصدر غير متوافق} other {مصادر غير متوافقة}}",
+ "CommunityChannelsStrings.internalNotesLabel": "ملاحظات المشرف (للاستخدام الداخلي فقط)",
+ "CommunityChannelsStrings.invalidLicensingReason": "تم رفض القناة بسبب تراخيص غير صالحة أو غير متوافقة مع المتطلبات",
+ "CommunityChannelsStrings.invalidMetadataReason": "بيانات وصفية غير صالحة أو مفقودة",
+ "CommunityChannelsStrings.languageLabel": "اللغة",
+ "CommunityChannelsStrings.languagesLabel": "اللغات",
+ "CommunityChannelsStrings.lessDetailsButton": "إخفاء التفاصيل",
+ "CommunityChannelsStrings.licenseCheckPassed": "تم اجتياز فحص التراخيص بنجاح.",
+ "CommunityChannelsStrings.licensesLabel": "التراخيص",
+ "CommunityChannelsStrings.loadError": "حدث خطأ أثناء تحميل القنوات.",
+ "CommunityChannelsStrings.loadingVersionHistory": "جاري تحميل سجلّ الإصدارات",
+ "CommunityChannelsStrings.moreDetails": "بعد الموافقة على تقديمك، ستصبح القناة متاحة لمستخدمي استوديو كوليبري الآخرين في صفحة \"مكتبة المجتمع\".",
+ "CommunityChannelsStrings.moreDetailsButton": "تفاصيل أكثر",
+ "CommunityChannelsStrings.needKolibriVersionToImport": "ستحتاج إلى كوليبري بالإصدار 0.19.4 أو أحدث لاستيراد القنوات من مكتبة المجتمع.",
+ "CommunityChannelsStrings.needsChangesPrimaryInfo": "تحتاج النسخة التي سبق إرسالها إلى تعديلات. تأكّد من معالجة جميع الملاحظات قبل إعادة الإرسال.",
+ "CommunityChannelsStrings.newLabel": "جديد",
+ "CommunityChannelsStrings.newNotificationsNotice": "إشعارات جديدة متوفرة.",
+ "CommunityChannelsStrings.nextPageAction": "التالي",
+ "CommunityChannelsStrings.noCommunityChannels": "لم تُنشر أي قنوات في مكتبة المجتمع حتى الآن.",
+ "CommunityChannelsStrings.noResultsWithFilters": "لا توجد قنوات تطابق عوامل التصفية المحددة.",
+ "CommunityChannelsStrings.noVersionsAvailable": "لا يتوفر سجلّ للإصدارات",
+ "CommunityChannelsStrings.nonePrimaryInfo": "ندعو أعضاء مجتمع كوليبري إلى تقديم القنوات التي أنشؤوها للتعلّم دون اتصال بالإنترنت في البيئات محدودة المصادر. ",
+ "CommunityChannelsStrings.notPublishedWarningDescription": "انشر في الاستوديو أولًا، ثم أرسل إلى مكتبة المجتمع.",
+ "CommunityChannelsStrings.notPublishedWarningTitle": "لم تُنشر هذه القناة بعد في استوديو كوليبري",
+ "CommunityChannelsStrings.notificationsLabel": "الإشعارات",
+ "CommunityChannelsStrings.otherIssuesReason": "مشاكل أخرى",
+ "CommunityChannelsStrings.pageIndicator": "{currentPage} من {totalPages}",
+ "CommunityChannelsStrings.pendingStatus": "تم التقديم",
+ "CommunityChannelsStrings.portabilityIssuesReason": "مشكلات حول قابلية النقل",
+ "CommunityChannelsStrings.previewYourDraftTitle": "معاينة القناة التجريبية في كوليبري",
+ "CommunityChannelsStrings.previousPageAction": "السؤال السابق",
+ "CommunityChannelsStrings.publicWarningDescription": "لا يمكن تقديم القنوات العامة إلى مكتبة المجتمع.",
+ "CommunityChannelsStrings.publicWarningTitle": "هذه القناة متاحة حاليًا للعموم في مكتبة كوليبري.",
+ "CommunityChannelsStrings.publishAction": "نشر",
+ "CommunityChannelsStrings.publishChannel": "نشر القناة",
+ "CommunityChannelsStrings.publishChannelDescription": "لعرض قناتك في كوليبري، قم باستيرادها باستخدام رمز القناة.",
+ "CommunityChannelsStrings.publishChannelMode": "نشر القناة",
+ "CommunityChannelsStrings.publishDraftDescription": "ستُحفظ قناتك كنسخة تجريبية، مما يتيح لك مراجعتها وإجراء فحوصات الجودة عليها دون التأثير على النسخة الحالية المتاحة لمستخدمي كوليبري. لعرض هذه القناة التجريبية في كوليبري، قم باستيرادها باستخدام رمز القناة التجريبية.",
+ "CommunityChannelsStrings.publishDraftMode": "نشر القناة التجريبية",
+ "CommunityChannelsStrings.publishedVersionLabel": "الإصدار المنشور:",
+ "CommunityChannelsStrings.publishingInfo": "أنت تقوم بنشر: الإصدار {version}",
+ "CommunityChannelsStrings.publishingMessage": "يجري الآن نشر القناة",
+ "CommunityChannelsStrings.qualityAssuranceReason": "مشكلات تتعلق بضمان الجودة",
+ "CommunityChannelsStrings.reasonLabel": "السبب: {reason}",
+ "CommunityChannelsStrings.resubmitAction": "إعادة الإرسال",
+ "CommunityChannelsStrings.resubmitModalBodyFirst": "تم نشر {channelName} v{version} أيضًا في مكتبة المجتمع.",
+ "CommunityChannelsStrings.resubmitModalBodySecond": "هل ترغب في إعادة إرسال هذه النسخة بعد التعديلات للمراجعة في مكتبة المجتمع؟",
+ "CommunityChannelsStrings.resubmitModalTitle": "هل تريد إعادة إرسال القناة للمراجعة في مكتبة المجتمع؟",
+ "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# نتيجة تم العثور عليها} other {# نتيجة تم العثور عليها}}",
+ "CommunityChannelsStrings.retry": "إعادة المحاولة",
+ "CommunityChannelsStrings.reviewAction": "مراجعة",
+ "CommunityChannelsStrings.saveDraft": "حفظ المسوّدة",
+ "CommunityChannelsStrings.searchLabel": "بحث",
+ "CommunityChannelsStrings.searchNotificationsLabel": "البحث في الإشعارات",
+ "CommunityChannelsStrings.seeAllVersions": "مشاهدة جميع الإصدارات",
+ "CommunityChannelsStrings.showMore": "عرض المزيد",
+ "CommunityChannelsStrings.showOlderAction": "عرض الأقدم",
+ "CommunityChannelsStrings.specialPermissionsDetected": "تم اكتشاف تراخيص ذات أذونات خاصة",
+ "CommunityChannelsStrings.submissionCreationNotification": "أُرسلت قناتك إلى مكتبة المجتمع بنجاح وهي الآن قيد المراجعة.",
+ "CommunityChannelsStrings.submissionNotesLabel": "ملاحظات التسليم",
+ "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) قدّم {channelVersion}",
+ "CommunityChannelsStrings.submitButton": "تقديم للمراجعة",
+ "CommunityChannelsStrings.submitToCommunityLibrary": "تقديم إلى مكتبة المجتمع",
+ "CommunityChannelsStrings.submittedPrimaryInfo": "توجد نسخة سابقة لا تزال قيد المراجعة. سيرى المراجعون افتراضيًا أحدث نسخة مُقدَّمة.",
+ "CommunityChannelsStrings.submittedSnackbar": "تم تقديم القناة إلى مكتبة المجتمع",
+ "CommunityChannelsStrings.submittingSnackbar": "جارٍ تقديم القناة إلى مكتبة المجتمع...",
+ "CommunityChannelsStrings.supersededStatus": "تم استبداله",
+ "CommunityChannelsStrings.thisMonthLabel": "هذا الشهر",
+ "CommunityChannelsStrings.thisWeekLabel": "هذا الأسبوع",
+ "CommunityChannelsStrings.thisYearLabel": "هذه السنة",
+ "CommunityChannelsStrings.todayLabel": "اليوم",
+ "CommunityChannelsStrings.unreadNotificationsLabel": "غير مقروء",
+ "CommunityChannelsStrings.versionDescriptionLabel": "وصف الإصدار",
+ "CommunityChannelsStrings.versionLabel": "نسخة الإصدار {version}",
+ "CommunityChannelsStrings.versionNotesLabel": "قدّم وصفاً لما هو جديد في هذا الإصدار من القناة",
+ "CommunityChannelsStrings.viewCriteriaAction": "عرض المعايير",
+ "CommunityChannelsStrings.viewMoreAction": "عرض المزيد",
+ "CommunityChannelsStrings.whatCanYouDoHere": "ما الذي يمكنك أن تفعله هنا",
+ "CommunityChannelsStrings.whatCanYouDoHereItem1": "تصفح القنوات حسب البلد والفئة واللغة",
+ "CommunityChannelsStrings.whatCanYouDoHereItem2": "نسخ رمز قناة للاستخدام في كوليبري",
+ "CommunityChannelsStrings.whatCanYouDoHereItem3": "عرض تفاصيل القناة بما في ذلك الوصف والبيانات الوصفية",
+ "CommunityChannelsStrings.whatIsCommunityLibrary": "ما هي مكتبة المجتمع؟",
"CommunityStandardsModal.communityStandardsHeader": "معايير المجتمع",
"CommunityStandardsModal.coreValuesLink": "تعرف على المزيد حول القيم الأساسية داخل منظمة Learning Equality",
"CommunityStandardsModal.description": "Learning Equality هي منظمة غير ربحية تعمل على تمكين الوصول العادل إلى التجارب التعليمية عالية الجودة. وبالإضافة إلى بيان القيم الأساسية لدينا، تهدف معايير المجتمع هذه إلى تعزيز بيئة داعمة وشاملة لمستخدمينا.",
@@ -819,40 +950,11 @@
"DeleteAccountForm.emailInvalidText": "البريد الإلكتروني لا يتطابق مع البريد الإلكتروني للحساب الخاص بك",
"DeleteAccountForm.fieldRequired": "هذا الحقل مطلوب",
"DeleteAccountForm.ok": "موافقة",
- "DetailsPanel.AVERAGE": "متوسط",
- "DetailsPanel.LARGE": "كبير",
- "DetailsPanel.SMALL": "صغير",
- "DetailsPanel.VERY_LARGE": "كبير جداً",
- "DetailsPanel.VERY_SMALL": "صغير جداً",
- "DetailsPanel.aggregatorToolTip": "الموقع الإلكتروني أو المؤسسة التي تستضيف مجموعة المحتوى ولكن ليس بالضرورة أن تكون هي منشئ المحتوى أو مالك حقوق النشر",
- "DetailsPanel.aggregatorsLabel": "جامعو المحتوى",
- "DetailsPanel.assessmentsIncludedText": "التقييمات",
- "DetailsPanel.authorToolTip": "الشخص أو المنظمة التي أنشأت هذا المحتوى",
- "DetailsPanel.authorsLabel": "المؤلفون",
- "DetailsPanel.categoriesHeading": "الفئات",
- "DetailsPanel.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري",
- "DetailsPanel.coachHeading": "مصادر للمدرِّبين",
- "DetailsPanel.containsContentHeading": "يتضمن محتوى من",
- "DetailsPanel.containsHeading": "يتضمن",
- "DetailsPanel.copyrightHoldersLabel": "مالكو حقوق النشر",
- "DetailsPanel.creationHeading": "تم الإنشاء في",
- "DetailsPanel.currentVersionHeading": "الإصدار المنشور",
- "DetailsPanel.languagesHeading": "اللغات",
- "DetailsPanel.levelsHeading": "المستويات",
- "DetailsPanel.licensesLabel": "التراخيص",
- "DetailsPanel.primaryLanguageHeading": "اللغة الأساسية",
- "DetailsPanel.providerToolTip": "المنظمة التي كلَّفت بإنشاء المحتوى أو تقوم بنشر المحتوى",
- "DetailsPanel.providersLabel": "مقدمو المحتوى",
- "DetailsPanel.publishedHeading": "تم النشر في",
- "DetailsPanel.resourceHeading": "إجمالي عدد المصادر",
- "DetailsPanel.sampleFromChannelHeading": "محتوى كعينة من هذه القناة",
- "DetailsPanel.sampleFromTopicHeading": "محتوى كعينة من هذا الموضوع",
- "DetailsPanel.sizeHeading": "حجم القناة",
- "DetailsPanel.sizeText": "{text} ({size})",
- "DetailsPanel.subtitlesHeading": "عناوين الصور والنصوص السمعية",
- "DetailsPanel.tagsHeading": "وسوم شائعة",
- "DetailsPanel.tokenHeading": "الرمز التعريفي للقناة",
- "DetailsPanel.unpublishedText": "لم يتم النشر",
+ "DeleteChannelModal.cancel": "إلغاء",
+ "DeleteChannelModal.channelDeletedSnackbar": "تم حذف القناة",
+ "DeleteChannelModal.deleteChannel": "حذف القناة التعليمية",
+ "DeleteChannelModal.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.",
+ "DeleteChannelModal.deleteTitle": "حذف هذه القناة",
"DetailsTabView.aggregatorLabel": "جامع المحتوى",
"DetailsTabView.aggregatorToolTip": "الموقع الإلكتروني أو المنظمة التي تستضيف مجموعة المحتوى ولكن ليس بالضرورة أن تكون هي منشئ المحتوى أو مالك حقوق النشر",
"DetailsTabView.assessmentOptionsLabel": "خيارات التقييم",
@@ -1008,10 +1110,8 @@
"ForgotPassword.forgotPasswordPrompt": "يرجى إدخال عنوان البريد الإلكتروني الخاص بك لتلقي تعليمات إعادة تعيين كلمة المرور الخاصة بك",
"ForgotPassword.forgotPasswordTitle": "إعادة تعيين كلمة المرور الخاصة بك",
"ForgotPassword.submitButton": "تقديم",
- "FormulasMenu.btnLabelInsert": "إدراج",
- "FormulasMenu.formulasMenuTitle": "أحرف خاصة",
"FormulasStrings.addition": "Addition",
- "FormulasStrings.advancedCategory": "Advanced",
+ "FormulasStrings.advancedCategory": "خيارات متقدّمة",
"FormulasStrings.alpha": "alpha",
"FormulasStrings.and": "And",
"FormulasStrings.angle": "Angle",
@@ -1019,7 +1119,7 @@
"FormulasStrings.bar": "Bar",
"FormulasStrings.basicCategory": "Basic",
"FormulasStrings.because": "Because",
- "FormulasStrings.beta": "beta",
+ "FormulasStrings.beta": "الإصدار بيتا",
"FormulasStrings.binomialCoefficient": "Binomial coefficient",
"FormulasStrings.cardinality": "Cardinality",
"FormulasStrings.charactersCategory": "Characters",
@@ -1029,7 +1129,7 @@
"FormulasStrings.congruentTo": "Congruent to",
"FormulasStrings.conjugate": "Conjugate",
"FormulasStrings.conjugateTranspose": "Conjugate transpose",
- "FormulasStrings.contains": "Contains",
+ "FormulasStrings.contains": "يتضمن",
"FormulasStrings.contourIntegral": "Contour integral",
"FormulasStrings.coproduct": "Coproduct",
"FormulasStrings.definition": "Definition",
@@ -1061,7 +1161,7 @@
"FormulasStrings.fraction": "Fraction",
"FormulasStrings.gamma": "gamma",
"FormulasStrings.gammaCapital": "Gamma",
- "FormulasStrings.geometryCategory": "Geometry",
+ "FormulasStrings.geometryCategory": "علم الهندسة",
"FormulasStrings.givenThat": "Given that/Such that",
"FormulasStrings.greaterThan": "Greater than",
"FormulasStrings.greaterThanOrEqual": "Greater than or equal to",
@@ -1078,7 +1178,7 @@
"FormulasStrings.lambda": "lambda",
"FormulasStrings.lambdaCapital": "Lambda",
"FormulasStrings.left": "Left",
- "FormulasStrings.leftArrow": "Left arrow",
+ "FormulasStrings.leftArrow": "سهم أيسر",
"FormulasStrings.leftCeiling": "Left ceiling",
"FormulasStrings.leftDouble": "Left (double)",
"FormulasStrings.leftFloor": "Left floor",
@@ -1122,8 +1222,8 @@
"FormulasStrings.perpendicular": "Perpendicular",
"FormulasStrings.phi": "phi",
"FormulasStrings.phiCapital": "Phi",
- "FormulasStrings.pi": "pi",
- "FormulasStrings.piCapital": "Pi",
+ "FormulasStrings.pi": "الثابت باي",
+ "FormulasStrings.piCapital": "الثابت باي",
"FormulasStrings.planckConstant": "Planck's constant",
"FormulasStrings.plusMinus": "Plus-minus",
"FormulasStrings.product": "Product",
@@ -1134,7 +1234,7 @@
"FormulasStrings.reducibleTo": "Reducible to",
"FormulasStrings.rho": "rho",
"FormulasStrings.right": "Right",
- "FormulasStrings.rightArrow": "Right arrow",
+ "FormulasStrings.rightArrow": "سهم أيمن",
"FormulasStrings.rightCeiling": "Right ceiling",
"FormulasStrings.rightDouble": "Right (double)",
"FormulasStrings.rightFloor": "Right floor",
@@ -1155,21 +1255,21 @@
"FormulasStrings.southeast": "Southeast",
"FormulasStrings.southwest": "Southwest",
"FormulasStrings.spade": "Spade",
- "FormulasStrings.squareRoot": "Square root",
+ "FormulasStrings.squareRoot": "جذر تربيعي",
"FormulasStrings.subscript": "Subscript",
"FormulasStrings.subset": "Subset",
"FormulasStrings.subsetOrEqual": "Subset or equal",
"FormulasStrings.subtraction": "Subtraction",
"FormulasStrings.sum": "Sum",
- "FormulasStrings.superscript": "Superscript",
+ "FormulasStrings.superscript": "أحرف علوية",
"FormulasStrings.superset": "Superset",
"FormulasStrings.supersetOrEqual": "Superset or equal",
"FormulasStrings.symmetricDifference": "Symmetric difference",
"FormulasStrings.tau": "tau",
"FormulasStrings.tensorProduct": "Tensor product",
"FormulasStrings.therefore": "Therefore",
- "FormulasStrings.theta": "theta",
- "FormulasStrings.thetaCapital": "Theta",
+ "FormulasStrings.theta": "ثيتا",
+ "FormulasStrings.thetaCapital": "ثيتا",
"FormulasStrings.topElement": "Top element",
"FormulasStrings.triangleDown": "Triangle down",
"FormulasStrings.triangleUp": "Triangle up",
@@ -1204,16 +1304,6 @@
"HintsEditor.newHintBtnLabel": "تلميح جديد",
"HintsEditor.noHintsPlaceholder": "لا يوجد تلميحات للسؤال",
"ImageOnlyThumbnail.thumbnail": "{title} صورة مصغرة",
- "ImagesMenu.acceptsText": "أنواع الملفات المدعومة: {acceptedFormats}",
- "ImagesMenu.altTextHint": "إن وصف الصورة ضروري لتمكين المتعلمين ضعاف البصر من الإجابة على الأسئلة، إضافة إلى عرضه في حال فشل تحميل الصورة",
- "ImagesMenu.altTextLabel": "وصف الصورة",
- "ImagesMenu.btnLabelCancel": "إلغاء",
- "ImagesMenu.btnLabelInsert": "إدراج",
- "ImagesMenu.currentImageDefaultText": "الصورة الحالية",
- "ImagesMenu.defaultDropText": "سحب وإفلات صورة هنا، أو رفعها يدوياً",
- "ImagesMenu.imageHeader": "تحميل صُورة",
- "ImagesMenu.selectFile": "حدد ملفاً",
- "ImagesMenu.selectFileButton": "حدد ملفاً",
"ImportFromChannelsModal.addButton": "إضافة",
"ImportFromChannelsModal.addedText": "تمّت الإضافة",
"ImportFromChannelsModal.importAction": "الاستيراد",
@@ -1223,6 +1313,7 @@
"ImportFromChannelsModal.reviewAction": "مراجعة",
"ImportFromChannelsModal.reviewTitle": "تحديد المصدر",
"InfoModal.close": "إغلاق",
+ "InfoModal.open": "معلومات إضافية عن التراخيص",
"InheritAncestorMetadataModal.applyResourceDetailsTitle": "تطبيق التفاصيل من المجلد '{folder}'",
"InheritAncestorMetadataModal.cancelAction": "إلغاء",
"InheritAncestorMetadataModal.categories": "الفئات: {categories}",
@@ -1255,17 +1346,48 @@
"MainNavigationDrawer.helpLink": "المساعدة والدعم",
"MainNavigationDrawer.logoutLink": "تسجيل الخروج",
"MainNavigationDrawer.settingsLink": "الإعدادات",
- "MarkdownEditor.bold": "خط عريض (Ctrl+B)",
- "MarkdownEditor.formulas": "إدراج صيغة (Ctrl+F)",
- "MarkdownEditor.image": "إدراج صورة (Ctrl+P)",
- "MarkdownEditor.italic": "خط مائل (Ctrl+I)\n",
- "MarkdownEditor.minimize": "تصغير (Ctrl+M)",
- "MarkdownImageField.editImageOption": "تعديل",
- "MarkdownImageField.removeImageOption": "إزالة",
- "MarkdownImageField.resizeImageOption": "تغيير الحجم",
"MasteryCriteriaGoal.labelText": "الهدف",
"MasteryCriteriaMofNFields.mHint": "الإجابات الصحيحة مطلوبة",
"MasteryCriteriaMofNFields.nHint": "الإجابات الأخيرة",
+ "MathLiveA11yStrings.accented": "رمز رياضي بعلامة فوقه",
+ "MathLiveA11yStrings.array": "المصفوفة",
+ "MathLiveA11yStrings.box": "عنصر داخل مربع",
+ "MathLiveA11yStrings.chemicalFormula": "الصيغة الكيميائية",
+ "MathLiveA11yStrings.crossOut": "شطب",
+ "MathLiveA11yStrings.deleted": "تمّ الحذف: ",
+ "MathLiveA11yStrings.delimiter": "رمز فاصل",
+ "MathLiveA11yStrings.denominator": "المقام",
+ "MathLiveA11yStrings.endOf": "{spokenText}؛ نهاية {relationName}",
+ "MathLiveA11yStrings.endOfMathfield": "{spokenText}؛ نهاية حقل الرياضيات",
+ "MathLiveA11yStrings.error": "خطأ",
+ "MathLiveA11yStrings.extensibleSymbol": "رمز قابل للتوسّع",
+ "MathLiveA11yStrings.first": "الأول",
+ "MathLiveA11yStrings.fraction": "جزء",
+ "MathLiveA11yStrings.group": "مجموعة",
+ "MathLiveA11yStrings.index": "دليل الجذر",
+ "MathLiveA11yStrings.latex": "عنصر بلغة لاتك",
+ "MathLiveA11yStrings.line": "السطر",
+ "MathLiveA11yStrings.mathField": "حقل رياضيات",
+ "MathLiveA11yStrings.mathfield": "حقل الرياضيات",
+ "MathLiveA11yStrings.numerator": "البسط",
+ "MathLiveA11yStrings.operator": "عامل رياضي",
+ "MathLiveA11yStrings.outOf": "خارج {relationName}؛",
+ "MathLiveA11yStrings.overUnder": "فوق-تحت",
+ "MathLiveA11yStrings.parent": "العنصر الأب",
+ "MathLiveA11yStrings.placeholder": "عنصر نائب",
+ "MathLiveA11yStrings.prompt": "موجّه",
+ "MathLiveA11yStrings.radicand": "المحتوى داخل الجذر",
+ "MathLiveA11yStrings.rule": "خط",
+ "MathLiveA11yStrings.selected": "تم التحديد: ",
+ "MathLiveA11yStrings.space": "مسافة",
+ "MathLiveA11yStrings.spacing": "تباعد",
+ "MathLiveA11yStrings.squareRoot": "جذر تربيعي",
+ "MathLiveA11yStrings.startOf": "بداية {relationName}: ",
+ "MathLiveA11yStrings.subscript": "رموز سفلية",
+ "MathLiveA11yStrings.subscriptSuperscript": "رموز سفلية وعلوية",
+ "MathLiveA11yStrings.superscript": "أحرف علوية",
+ "MathLiveA11yStrings.superscriptAndSubscript": "رموز سفلية وعلوية",
+ "MathLiveA11yStrings.text": "النص",
"MessageLayout.backToLogin": "المتابعة إلى صفحة تسجيل الدخول",
"MoveModal.addTopic": "إضافة مجلد جديد",
"MoveModal.cancel": "إلغاء",
@@ -1296,7 +1418,7 @@
"PasswordField.passwordLabel": "كلمة المرور",
"PasswordInstructionsSent.passwordInstructionsHeader": "تم إرسال التعليمات. شكراً لك!",
"PasswordInstructionsSent.passwordInstructionsText": "في حال كان هناك بالفعل حساب مرتبط بعنوان البريد الإلكتروني المرسل، فستصلك التعليمات قريباً. في حال لم تتلقّ بريداً إلكترونياً من طرفنا، فيرجى التحقق من صندوق البريد العشوائي.",
- "PermissionsError.goToHomePageAction": "انتقل إلى الصفحة الرئيسية",
+ "PermissionsError.backToHome": "العودة إلى الصفحة الرئيسية",
"PermissionsError.permissionDeniedHeader": "هل نسيت تسجيل الدخول؟",
"PoliciesModal.checkboxText": "لقد وافقت على الشروط الواردة أعلاه",
"PoliciesModal.closeButton": "إغلاق",
@@ -1306,6 +1428,7 @@
"PrivacyPolicyModal.updatedPrivacyHeader": "سياسة الخصوصية المحدثة",
"ProgressBar.progressText": "{percent}%",
"ProgressModal.defaultErrorText": "فشلت آخر محاولة للنشر",
+ "ProgressModal.draftHeader": "جارٍ حفظ النسخة التجريبية...",
"ProgressModal.lastPublished": "منشور {last_published}",
"ProgressModal.publishHeader": "نشر القناة",
"ProgressModal.syncError": "فشلت آخر محاولة للمزامنة",
@@ -1346,6 +1469,19 @@
"RelatedResourcesTab.showPreviewBtnLabel": "عرض",
"RelatedResourcesTab.tooManyNextStepsWarning": "قم بالحد من عدد الخطوات التالية لإنشاء تجربة تعلم أكثر توجيهاً",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "قم بالحد من عدد الخطوات السابقة لإنشاء تجربة تعلم أكثر توجيهاً",
+ "RemoveChannelFromListModal.cancel": "إلغاء",
+ "RemoveChannelFromListModal.channelRemovedSnackbar": "تمت إزالة القناة",
+ "RemoveChannelFromListModal.removeBtn": "إزالة",
+ "RemoveChannelFromListModal.removePrompt": "الوصول المتاح لك يمكنك من عرض هذه القناة فقط. يرجى تأكيد أنك تريد إزالتها من قائمة القنوات الخاصة بك.",
+ "RemoveChannelFromListModal.removeTitle": "إزالة من قائمة القنوات",
+ "RemoveChannelModal.cancel": "إلغاء",
+ "RemoveChannelModal.deleteChannel": "حذف القناة التعليمية",
+ "RemoveChannelModal.deleteChannelWithCLWarning": "تمت مشاركة هذه القناة مع مكتبة المجتمع. لن يؤدي حذفها هنا إلى إزالتها من مكتبة المجتمع وقد تظل بانتظار الاعتماد أو تبقى متاحة هناك.",
+ "RemoveChannelModal.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.",
+ "RemoveChannelModal.deleteTitle": "حذف هذه القناة",
+ "RemoveChannelModal.removeBtn": "إزالة",
+ "RemoveChannelModal.removePrompt": "الوصول المتاح لك يمكنك من عرض هذه القناة فقط. يرجى تأكيد أنك تريد إزالتها من قائمة القنوات الخاصة بك.",
+ "RemoveChannelModal.removeTitle": "إزالة من قائمة القنوات",
"ReportErrorModal.closeAction": "إغلاق",
"ReportErrorModal.emailDescription": "اتصل بفريق الدعم الفني لإرسال تفاصيل الخطأ، وسوف نبذل قصارى جهدنا للمساعدة.",
"ReportErrorModal.emailPrompt": "أرسل بريدًا إلكترونيًا إلى الفريق الفني",
@@ -1533,8 +1669,11 @@
"SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "مُتقدم جدًا بالنسبة لمستوى معرفة المتعلمين الذي أبحث عنه",
"SearchRecommendationsStrings.tooBasicForLearnersLabel": "بسيط جدًا بالنسبة لمستوى معرفة المتعلمين الذي أبحث عنه",
"SearchRecommendationsStrings.tryAgainLink": "حاول مرة أخرى",
+ "SearchRecommendationsStrings.trySearchRecommendationsHeader": "جرّب ميزة \"التوصيات\" الجديدة!",
+ "SearchRecommendationsStrings.trySearchRecommendationsText": "استنادًا إلى عنوان ووصف المجلد الذي تعمل عليه، سنقترح مصادر ذات صلة من مكتبة كوليبري. اختر \"استيراد من القنوات\" داخل أي مجلد في قنواتك لعرض التوصيات.",
"SearchRecommendationsStrings.undoAction": "تراجع",
"SearchRecommendationsStrings.viewMoreLink": "عرض المزيد",
+ "SearchRecommendationsStrings.viewRecommendationsButton": "عرض التوصيات",
"SearchResultsList.failedToLoad": "يتعذر تحميل نتائج البحث",
"SearchResultsList.resultsPerPageLabel": "النتائج لكل صفحة",
"SearchResultsList.saveSearchAction": "حفظ عملية البحث",
@@ -1588,10 +1727,121 @@
"Storage.spaceUsedOfMax": "{qty} من {max}",
"Storage.storagePercentageUsed": "{qty}% مساحة تخزين مستخدمة",
"Storage.videoFiles": "يجب ضغط ملفات الفيديو لزيادة مساحة التخزين وضمان سلاسة التوزيع والتشغيل دون اتصال. وبمجرد ضغطها، قد يكون مجموع التخزين المطلوب أقل مما توقعته في البداية.",
+ "StudioChannelCard.channelLanguageNotSetIndicator": "لم يتم تعيين لغة",
+ "StudioChannelCard.details": "التفاصيل",
+ "StudioChannelCard.lastPublished": "منشور {last_published}",
+ "StudioChannelCard.lastUpdated": "{updated} تم تحديثها",
+ "StudioChannelCard.multipleCountries": "متعدد البلدان",
+ "StudioChannelCard.resourceCount": "{count, plural, zero {# مصادر} one {# مصادر} two {# مصدران} few {# مصادر} many {# مصدراً}\n =1 {# مصدر}\n other {# مصادر}}",
+ "StudioChannelCard.selectChannel": "اختر {name}",
+ "StudioChannelCard.unpublishedText": "لم يتم النشر",
+ "StudioChannelsPage.invitations": "لديكَ {count, plural, zero {# دعوات} one {# دعوات} two {# دعوتان} few {# دعوات} many {# دعوة}\n =1 {# دعوة}\n other {# دعوات}}",
+ "StudioChannelsPage.noChannelsFound": "لم يتم العثور على أي قناة",
+ "StudioCollectionsTable.aboutChannelSets": "حول المجموعات",
+ "StudioCollectionsTable.aboutChannelSetsLink": "تعرّف على المزيد حول المجموعات",
+ "StudioCollectionsTable.addChannelSetTitle": "مجموعة جديدة",
+ "StudioCollectionsTable.cancel": "إلغاء",
+ "StudioCollectionsTable.cancelButtonLabel": "إغلاق",
+ "StudioCollectionsTable.channelNumber": "عدد القنوات",
+ "StudioCollectionsTable.channelSetsDescriptionText": "تحتوي المجموعة على العديد من قنوات استوديو كوليبري التي يمكن استيرادها في دفعة واحدة إلى كوليبري برمز تعريفي واحد.",
+ "StudioCollectionsTable.channelSetsDisclaimer": "ستحتاج إلى إصدار كوليبري 0.12.0 أو ما بعده لاستيراد مجموعات القنوات",
+ "StudioCollectionsTable.channelSetsInstructionsText": "يمكنك إنشاء مجموعة عن طريق تحديد القنوات التي تريد استيرادها معاً.",
+ "StudioCollectionsTable.collectionDeleted": "تم حذف المجموعة",
+ "StudioCollectionsTable.copiedTokenId": "تم نسخ الرمز التعريفي",
+ "StudioCollectionsTable.copyFailed": "فشل النسخ",
+ "StudioCollectionsTable.copyToken": "نسخ الرمز",
+ "StudioCollectionsTable.delete": "حذف المجموعة",
+ "StudioCollectionsTable.deleteChannelSetText": "هل أنت متأكد من أنك تريد حذف هذه المجموعة؟",
+ "StudioCollectionsTable.deleteChannelSetTitle": "حذف المجموعة",
+ "StudioCollectionsTable.deleteError": "حدث خطأ أثناء حذف المجموعة",
+ "StudioCollectionsTable.edit": "تحرير المجموعة",
+ "StudioCollectionsTable.noChannelSetsFound": "يمكنك تجميع قنوات متعددة لإنشاء مجموعة. ثم يمكن استيراد المجموعة بأكملها إلى كوليبري دفعة واحدة باستخدام رمز المجموعة التعريفي.",
+ "StudioCollectionsTable.options": "الخيارات",
+ "StudioCollectionsTable.pageTitle": "المجموعات",
+ "StudioCollectionsTable.saving": "جاري الحفظ",
+ "StudioCollectionsTable.tableCaption": "قائمة المجموعات",
+ "StudioCollectionsTable.title": "اسم المجموعة",
+ "StudioCollectionsTable.token": "معرّف الرمز التعريفي",
+ "StudioCopyToken.copiedTokenId": "تم نسخ الرمز التعريفي",
+ "StudioCopyToken.copyFailed": "فشل النسخ",
+ "StudioCopyToken.token": "الرمز التعريفي",
+ "StudioCopyToken.tooltipText": "انسخ الرمز التعريفي لاستيراد القناة إلى كوليبري",
+ "StudioDetailsPanel.AVERAGE": "متوسط",
+ "StudioDetailsPanel.LARGE": "كبير",
+ "StudioDetailsPanel.SMALL": "صغير",
+ "StudioDetailsPanel.VERY_LARGE": "كبير جداً",
+ "StudioDetailsPanel.VERY_SMALL": "صغير جداً",
+ "StudioDetailsPanel.aggregatorToolTip": "الموقع الإلكتروني أو المؤسسة التي تستضيف مجموعة المحتوى ولكن ليس بالضرورة أن تكون هي منشئ المحتوى أو مالك حقوق النشر",
+ "StudioDetailsPanel.aggregatorsLabel": "جامعو المحتوى",
+ "StudioDetailsPanel.assessmentsIncludedText": "التقييمات",
+ "StudioDetailsPanel.authorToolTip": "الشخص أو المنظمة التي أنشأت هذا المحتوى",
+ "StudioDetailsPanel.authorsLabel": "المؤلفون",
+ "StudioDetailsPanel.categoriesHeading": "الفئات",
+ "StudioDetailsPanel.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري",
+ "StudioDetailsPanel.coachHeading": "مصادر للمدرِّبين",
+ "StudioDetailsPanel.containsContentHeading": "يتضمن محتوى من",
+ "StudioDetailsPanel.containsHeading": "يتضمن",
+ "StudioDetailsPanel.copyrightHoldersLabel": "مالكو حقوق النشر",
+ "StudioDetailsPanel.creationHeading": "تم الإنشاء في",
+ "StudioDetailsPanel.currentVersionHeading": "الإصدار المنشور",
+ "StudioDetailsPanel.languagesHeading": "اللغات",
+ "StudioDetailsPanel.levelsHeading": "المستويات",
+ "StudioDetailsPanel.licensesLabel": "التراخيص",
+ "StudioDetailsPanel.primaryLanguageHeading": "اللغة الأساسية",
+ "StudioDetailsPanel.providerToolTip": "المنظمة التي كلَّفت بإنشاء المحتوى أو تقوم بنشر المحتوى",
+ "StudioDetailsPanel.providersLabel": "مقدمو المحتوى",
+ "StudioDetailsPanel.publishedHeading": "تم النشر في",
+ "StudioDetailsPanel.resourceHeading": "إجمالي عدد المصادر",
+ "StudioDetailsPanel.sampleFromChannelHeading": "محتوى كعينة من هذه القناة",
+ "StudioDetailsPanel.sizeHeading": "حجم القناة",
+ "StudioDetailsPanel.sizeText": "{text} ({size})",
+ "StudioDetailsPanel.subtitlesHeading": "عناوين الصور والنصوص السمعية",
+ "StudioDetailsPanel.tagsHeading": "وسوم شائعة",
+ "StudioDetailsPanel.tokenHeading": "الرمز التعريفي للقناة",
+ "StudioDetailsPanel.unpublishedText": "لم يتم النشر",
+ "StudioImmersiveModal.close": "إغلاق",
+ "StudioMessageLayout.backToLogin": "المتابعة إلى صفحة تسجيل الدخول",
+ "StudioMyChannels.copyToken": "نسخ معرّف القناة",
+ "StudioMyChannels.deleteChannel": "حذف القناة التعليمية",
+ "StudioMyChannels.editChannel": "تعديل تفاصيل القناة",
+ "StudioMyChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني",
+ "StudioMyChannels.moreOptions": "خيارات إضافية",
+ "StudioMyChannels.newChannel": "قناة جديدة",
+ "StudioMyChannels.title": "قنواتي",
+ "StudioMyChannels.viewContent": "عرض القناة على كوليبري",
"StudioOfflineAlert.offlineText": "يبدو أنك غير متصل بالانترنت. سيتم حفظ التغييرات الخاصة بك بمجرد عودة الاتصال الخاص بك.",
"StudioOfflineAlert.onlineText": "عاد الاتصال.",
+ "StudioStarredChannels.copyToken": "نسخ معرّف القناة",
+ "StudioStarredChannels.deleteChannel": "حذف القناة التعليمية",
+ "StudioStarredChannels.editChannel": "تعديل تفاصيل القناة",
+ "StudioStarredChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني",
+ "StudioStarredChannels.moreOptions": "خيارات إضافية",
+ "StudioStarredChannels.removeChannel": "إزالة القناة",
+ "StudioStarredChannels.title": "القنوات المفضّلة",
+ "StudioStarredChannels.viewContent": "عرض القناة على كوليبري",
"StudioTree.missingTitle": "عنوان مفقود",
"StudioTree.optionsTooltip": "الخيارات",
+ "StudioViewOnlyChannels.copyToken": "نسخ معرّف القناة",
+ "StudioViewOnlyChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني",
+ "StudioViewOnlyChannels.moreOptions": "خيارات أخرى",
+ "StudioViewOnlyChannels.removeChannel": "إزالة القناة",
+ "StudioViewOnlyChannels.title": "قنوات العرض فقط",
+ "StudioViewOnlyChannels.viewContent": "عرض القناة على كوليبري",
+ "SubscriptionCard.annualPrice": "${price, number}/سنة",
+ "SubscriptionCard.cancelNotice": "ستنتهي صلاحية اشتراكك في {date, date, medium}. سيتم حذف مساحة التخزين بعد ذلك.",
+ "SubscriptionCard.dismiss": "رفض",
+ "SubscriptionCard.genericError": "حدثت مشكلة أثناء الاتصال بمزوّد الدفع. يُرجى المحاولة مرة أخرى.",
+ "SubscriptionCard.instantUpgrade": "ترقية مساحة التخزين الآن",
+ "SubscriptionCard.manageSubscription": "إدارة الإشتراك",
+ "SubscriptionCard.renewalNotice": "سيتم تجديد اشتراكك تلقائيًا في {date, date, medium}.",
+ "SubscriptionCard.storageAmount": "التخزين (جيجابايت)",
+ "SubscriptionCard.storageIncluded": "{size} مشمولة ضمن اشتراكك",
+ "SubscriptionCard.storageRange": "أدخل قيمة بين 1 و 50",
+ "SubscriptionCard.subscriptionActive": "اشتراك مساحة التخزين نشط",
+ "SubscriptionCard.subscriptionCanceling": "تم إلغاء الإشتراك",
+ "SubscriptionCard.upgradeDescription": "اشتر مساحة تخزين إضافية بسعر 15 دولارًا لكل جيجابايت سنويًا.",
+ "SubscriptionCard.upgradeNow": "ترقية الآن",
+ "SubscriptionCard.upgradeSuccess": "توَسّعت مساحة التخزين إلى {size}",
"SubtitlesList.acceptedFormatsTooltip": "الصيغ مدعومة: {extensions}",
"SubtitlesList.addSubtitleText": "إضافة عناوين للصور",
"SubtitlesList.subtitlesHeader": "عناوين الصور والنص السمعي",
@@ -1620,7 +1870,7 @@
"TechnicalTextBlock.copiedToClipboardConfirmation": "تمّ النّسخ إلى الحافظة",
"TechnicalTextBlock.copiedToClipboardFailure": "فشلت عملية النسخ إلى الحافظة",
"TechnicalTextBlock.copyToClipboardButtonPrompt": "نسخ إلى الحافظة",
- "TermsOfServiceModal.ToSHeader": "Terms of Service",
+ "TermsOfServiceModal.ToSHeader": "شروط الخدمة",
"TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions",
"TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;",
"TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and",
@@ -1651,7 +1901,7 @@
"TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.",
"TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality",
"TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.",
- "TermsOfServiceModal.communityStandardsHeader": "Community Standards",
+ "TermsOfServiceModal.communityStandardsHeader": "معايير المجتمع",
"TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards",
"TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.",
"TermsOfServiceModal.definitionsHeader": "Definitions",
@@ -1690,7 +1940,7 @@
"TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.",
"TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights",
"TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.",
- "TermsOfServiceModal.updatedToSHeader": "Updated terms of service",
+ "TermsOfServiceModal.updatedToSHeader": "شروط الخدمة المحدثة",
"TermsOfServiceModal.userContentHeader": "User-Generated Content",
"TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.",
"TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.",
@@ -1715,82 +1965,101 @@
"TextArea.fieldRequiredMessage": "هذا الحقل مطلوب",
"TextField.fieldRequiredMessage": "هذا الحقل مطلوب",
"Thumbnail.thumbnail": "{title} صورة مصغرة",
+ "ThumbnailGenerator.closeButtonLabel": "موافقة",
"ThumbnailGenerator.generatedDefaultFilename": "صورة مصغرة تم إنشاؤها",
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "تعذّر إنشاء صورة مصغرة",
"ThumbnailGenerator.thumbnailGenerationFailedText": "حدثت مشكلة أثناء إنشاء صورة مصغرة",
- "TipTapEditorStrings.addLink": "Add link",
- "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
- "TipTapEditorStrings.altTextLabel": "Alt text (Optional)",
- "TipTapEditorStrings.altTextPlaceholder": "Describe your image...",
- "TipTapEditorStrings.bold": "Strong",
- "TipTapEditorStrings.bulletList": "Bullet list",
- "TipTapEditorStrings.cancel": "Cancel",
- "TipTapEditorStrings.cancelLoading": "Cancel loading",
- "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.",
- "TipTapEditorStrings.close": "Close",
- "TipTapEditorStrings.closeModal": "Close modal",
- "TipTapEditorStrings.codeBlock": "Code block",
- "TipTapEditorStrings.copy": "Copy",
- "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions",
- "TipTapEditorStrings.copyLink": "Copy link",
- "TipTapEditorStrings.defaultImageName": "Image",
- "TipTapEditorStrings.edit": "Edit",
- "TipTapEditorStrings.editImage": "Edit image",
- "TipTapEditorStrings.editLink": "Edit link",
- "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.",
- "TipTapEditorStrings.fileSizeUnit": "MB.",
- "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ",
- "TipTapEditorStrings.formatHeader1": "Header 1",
- "TipTapEditorStrings.formatHeader2": "Header 2",
- "TipTapEditorStrings.formatHeader3": "Header 3",
- "TipTapEditorStrings.formatNormal": "Normal",
- "TipTapEditorStrings.formatOptions": "Format options",
- "TipTapEditorStrings.formatSmall": "Small",
- "TipTapEditorStrings.formulasMenuTitle": "Special Characters",
- "TipTapEditorStrings.goToLink": "Go to link",
- "TipTapEditorStrings.historyActions": "History actions",
- "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually",
- "TipTapEditorStrings.imagePreview": "Image preview",
- "TipTapEditorStrings.insert": "Insert",
- "TipTapEditorStrings.insertImage": "Insert image",
- "TipTapEditorStrings.insertLink": "Insert link",
- "TipTapEditorStrings.insertTools": "Insert tools",
- "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ",
- "TipTapEditorStrings.italic": "Italic",
- "TipTapEditorStrings.link": "Link",
- "TipTapEditorStrings.linkActions": "Link actions",
- "TipTapEditorStrings.listFormatting": "List formatting",
- "TipTapEditorStrings.mathFormula": "Math formula",
- "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.",
- "TipTapEditorStrings.noFileProvided": "No file provided.",
- "TipTapEditorStrings.numberedList": "Numbered list",
- "TipTapEditorStrings.opensInNewTab": "(opens in new tab)",
- "TipTapEditorStrings.paste": "Paste",
- "TipTapEditorStrings.pasteOptions": "Paste options",
- "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu",
- "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting",
- "TipTapEditorStrings.redo": "Redo",
- "TipTapEditorStrings.remove": "Remove",
- "TipTapEditorStrings.removeImage": "Remove image",
- "TipTapEditorStrings.removeLink": "Remove link",
- "TipTapEditorStrings.replaceFile": "Replace file",
- "TipTapEditorStrings.save": "Save",
- "TipTapEditorStrings.saveChanges": "Save changes",
- "TipTapEditorStrings.scriptFormatting": "Script formatting",
- "TipTapEditorStrings.selectFile": "Select file",
- "TipTapEditorStrings.selectFileToUpload": "Select file to upload",
- "TipTapEditorStrings.strikethrough": "Strikethrough",
- "TipTapEditorStrings.subscript": "Subscript",
- "TipTapEditorStrings.superscript": "Superscript",
- "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp",
- "TipTapEditorStrings.text": "Text",
- "TipTapEditorStrings.textFormatOptions": "Text format options",
- "TipTapEditorStrings.textFormattingOptions": "Text formatting options",
- "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar",
- "TipTapEditorStrings.textStyleFormatting": "Text style formatting",
- "TipTapEditorStrings.underline": "Underline",
- "TipTapEditorStrings.undo": "Undo",
- "TipTapEditorStrings.uploadImage": "Upload image",
+ "TipTapEditorStrings.TipTapEditorLabel": "محرر النص - اضغط Enter لبدء التحرير",
+ "TipTapEditorStrings.TipTapViewerLabel": "محتوى محرر النص",
+ "TipTapEditorStrings.addLink": "إضافة رابط",
+ "TipTapEditorStrings.alignLeft": "محاذاة إلى اليسار",
+ "TipTapEditorStrings.alignRight": "محاذاة إلى اليمين",
+ "TipTapEditorStrings.altTextDescription": "النص البديل ضروري لتمكين المتعلمين من ذوي الإعاقة البصرية من الإجابة على الأسئلة، كما يظهر أيضًا عند تعذّر تحميل الصورة",
+ "TipTapEditorStrings.altTextLabel": "النص البديل (اختياري)",
+ "TipTapEditorStrings.altTextPlaceholder": "صف صورتك...",
+ "TipTapEditorStrings.bold": "عريض",
+ "TipTapEditorStrings.bulletList": "قائمة نقطية",
+ "TipTapEditorStrings.cancel": "إلغاء",
+ "TipTapEditorStrings.cancelLoading": "إلغاء التحميل",
+ "TipTapEditorStrings.clearFormatting": "مسح التنسيق",
+ "TipTapEditorStrings.clipboardAccessFailed": "فشل الوصول إلى الحافظة. حاول النسخ مرة أخرى.",
+ "TipTapEditorStrings.close": "إغلاق",
+ "TipTapEditorStrings.closeModal": "إغلاق النافذة المنبثقة",
+ "TipTapEditorStrings.codeBlock": "كتلة تعليمات برمجية",
+ "TipTapEditorStrings.collapseFormattingBar": "إخفاء شريط التنسيق",
+ "TipTapEditorStrings.copy": "نسخ",
+ "TipTapEditorStrings.copyAndPasteActions": "نسخ ولصق الإجراءات",
+ "TipTapEditorStrings.copyLink": "نسخ الرابط",
+ "TipTapEditorStrings.decreaseFormatSize": "تقليل حجم التنسيق",
+ "TipTapEditorStrings.defaultImageName": "صورة",
+ "TipTapEditorStrings.edit": "تعديل",
+ "TipTapEditorStrings.editImage": "تحرير صورة",
+ "TipTapEditorStrings.editLink": "تعديل الرابط",
+ "TipTapEditorStrings.editorControls": "عناصر تحكّم المحرّر",
+ "TipTapEditorStrings.errorUploadingImage": "خطأ في تحميل الصورة",
+ "TipTapEditorStrings.expandFormattingBar": "توسيع شريط التنسيق",
+ "TipTapEditorStrings.failedToProcessImage": "فشل في معالجة ملف الصورة.",
+ "TipTapEditorStrings.fileSizeUnit": "ميغا بايت.",
+ "TipTapEditorStrings.fileTooLarge": "الملف كبير جداً. أقصى حجم خط هو ",
+ "TipTapEditorStrings.formatHeader1": "ترويسة 1",
+ "TipTapEditorStrings.formatHeader2": "ترويسة 2",
+ "TipTapEditorStrings.formatHeader3": "ترويسة 3",
+ "TipTapEditorStrings.formatNormal": "عادي",
+ "TipTapEditorStrings.formatOptions": "خيارات التنسيق",
+ "TipTapEditorStrings.formatSize": "حجم التنسيق",
+ "TipTapEditorStrings.formatSmall": "صغير",
+ "TipTapEditorStrings.formulasMenuTitle": "أحرف خاصة",
+ "TipTapEditorStrings.goToLink": "الانتقال إلى الرابط",
+ "TipTapEditorStrings.historyActions": "إجراءات السجل",
+ "TipTapEditorStrings.imageDropZoneText": "اسحب وأفلت صورة هنا أو قم برفعها يدويًا",
+ "TipTapEditorStrings.imagePreview": "معاينة الصورة",
+ "TipTapEditorStrings.increaseFormatSize": "زيادة حجم التنسيق",
+ "TipTapEditorStrings.insert": "إدراج",
+ "TipTapEditorStrings.insertContent": "إدراج المحتوى",
+ "TipTapEditorStrings.insertContentMenu": "إدراج قائمة المحتوى",
+ "TipTapEditorStrings.insertContentOption": "إدراج خيار المحتوى",
+ "TipTapEditorStrings.insertImage": "إدراج صورة",
+ "TipTapEditorStrings.insertLink": "إدراج رابط",
+ "TipTapEditorStrings.insertTools": "إدراج أدوات",
+ "TipTapEditorStrings.invalidFileType": "نوع الملف غير صالح. يُرجى استخدام: ",
+ "TipTapEditorStrings.italic": "خط مائل",
+ "TipTapEditorStrings.link": "الرابط",
+ "TipTapEditorStrings.linkActions": "إجراءات الرابط",
+ "TipTapEditorStrings.listFormatting": "تنسيق القائمة",
+ "TipTapEditorStrings.loadingFormulas": "تحميل محرر الرياضيات",
+ "TipTapEditorStrings.mathFormula": "المعادلة الرياضية",
+ "TipTapEditorStrings.moreButtonText": "المزيد",
+ "TipTapEditorStrings.multipleFilesDroppedWarning": "تم إسقاط عدة ملفات. تم اختيار الملف الأول فقط.",
+ "TipTapEditorStrings.noEnoughStorageSpace": "لا توجد مساحة تخزين كافية. يتجاوز حجم الملف مساحة التخزين المتبقية.",
+ "TipTapEditorStrings.noFileProvided": "لم يتم توفير ملف.",
+ "TipTapEditorStrings.numberedList": "قائمة مرقّمة",
+ "TipTapEditorStrings.opensInNewTab": "(يفتح في علامة تبويب جديدة)",
+ "TipTapEditorStrings.paste": "لصق",
+ "TipTapEditorStrings.pasteOptions": "خيارات اللصق",
+ "TipTapEditorStrings.pasteOptionsMenu": "قائمة خيارات اللصق",
+ "TipTapEditorStrings.pasteWithoutFormatting": "لصق بدون تنسيق",
+ "TipTapEditorStrings.redo": "إعادة",
+ "TipTapEditorStrings.remove": "إزالة",
+ "TipTapEditorStrings.removeImage": "إزالة الصورة",
+ "TipTapEditorStrings.removeLink": "حذف الرابط",
+ "TipTapEditorStrings.replaceFile": "استبدال ملف",
+ "TipTapEditorStrings.save": "حفظ",
+ "TipTapEditorStrings.saveChanges": "حفظ التغييرات",
+ "TipTapEditorStrings.scriptFormatting": "تنسيق النص البرمجي",
+ "TipTapEditorStrings.selectFile": "تحديد ملف",
+ "TipTapEditorStrings.selectFileToUpload": "اختر ملف لرفعه",
+ "TipTapEditorStrings.strikethrough": "شطب",
+ "TipTapEditorStrings.subscript": "رموز سفلية",
+ "TipTapEditorStrings.superscript": "أحرف علوية",
+ "TipTapEditorStrings.supportedFileTypes": "أنواع الملفات المدعومة: { extensions }",
+ "TipTapEditorStrings.text": "النص",
+ "TipTapEditorStrings.textFormatOptions": "خيارات تنسيق النص",
+ "TipTapEditorStrings.textFormattingOptions": "خيارات تنسيق النص",
+ "TipTapEditorStrings.textFormattingToolbar": "شريط أدوات تنسيق النص",
+ "TipTapEditorStrings.textStyleFormatting": "تنسيق نمط النص",
+ "TipTapEditorStrings.underline": "تسطير",
+ "TipTapEditorStrings.undo": "تراجع",
+ "TipTapEditorStrings.uploadImage": "رفع صورة",
"TitleStrings.catalogTitle": "دليل مكتبة محتوى كوليبري",
"TitleStrings.defaultTitle": "استوديو كوليبري",
"TitleStrings.tabTitle": "{title} - {site}",
@@ -1814,26 +2083,27 @@
"TreeView.showSidebar": "إظهار الشريط الجانبي",
"TreeView.updatedResourcesReadyForReview": "المصادر المحدثة جاهزة للمعاينة",
"TreeViewBase.apiGenerated": "تم توليدها/ ها من API",
- "TreeViewBase.cancel": "إلغاء",
"TreeViewBase.channelDeletedSnackbar": "تم حذف القناة",
"TreeViewBase.channelDetails": "عرض تفاصيل القناة",
"TreeViewBase.deleteChannel": "حذف القناة التعليمية",
- "TreeViewBase.deleteChannelButton": "حذف القناة التعليمية",
- "TreeViewBase.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.",
- "TreeViewBase.deleteTitle": "حذف هذه القناة",
"TreeViewBase.editChannel": "تعديل تفاصيل القناة",
"TreeViewBase.emptyChannelTooltip": "لا يمكنك نشر قناة فارغة",
"TreeViewBase.getToken": "احصل على رمز تعريف",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, zero {مصادر لم تكتمل ولا يمكن نشرها} one {مصدر لم يكتمل ولا يمكن نشره} two {مصدران لم يكتملا ولا يمكن نشرهما} few {مصادر لم تكتمل ولا يمكن نشرها} many {مصدراً لم يكتمل ولا يمكن نشره} other {مصادر لم تكتمل ولا يمكن نشرها}}",
+ "TreeViewBase.inviteCollaborators": "دعوة المتعاونين",
"TreeViewBase.noChangesText": "لم يتم العثور على تغييرات في القناة",
"TreeViewBase.noLanguageSetError": "لغة القناة مطلوبة",
"TreeViewBase.openTrash": "فتح سلة المهملات",
"TreeViewBase.publishButton": "نشر",
"TreeViewBase.publishButtonTitle": "جعل هذه القناة متاحة للاستيراد إلى كوليبري",
"TreeViewBase.shareChannel": "شارك القناة",
+ "TreeViewBase.shareMenuButton": "مشاركة",
+ "TreeViewBase.shareToken": "مشاركة الرمز",
+ "TreeViewBase.submitToCommunityLibrary": "تقديم إلى مكتبة المجتمع",
"TreeViewBase.syncChannel": "مزامنة المصادر",
"TreeViewBase.viewOnly": "قنوات للعرض فقط",
- "Uploader.listDelimiter": ", ",
+ "Uploader.closeButtonLabel": "موافقة",
+ "Uploader.listDelimiter": ",",
"Uploader.maxFileSizeText": "{count, plural, zero {# ملفات لن يتم تحميلها.} one {# ملف لن يتم تحميله.} two {# ملفان لن يتم تحميلهما.} few {# ملفات لن يتم تحميلها.} many {# ملفاً لن يتم تحميلها.}\n =1 {# ملف لن يتم تحميله.}\n other {# ملفات لن يتم تحميلها.}} حجم الملف يجب أن يكون أقل من {size}",
"Uploader.noStorageHeader": "لا توجد سعة كافية",
"Uploader.remainingStorage": "مساحة التخزين المتبقية: {size}",
@@ -1906,4 +2176,4 @@
"sharedVue.masteryModelRequired": "الإتقان مطلوب",
"sharedVue.shortActivityLteThirty": "يجب أن تكون القيمة مساوية أو أقل من 30",
"sharedVue.titleRequired": "العنوان إلزامي"
-}
+}
\ No newline at end of file
diff --git a/contentcuration/locale/ar/LC_MESSAGES/django.po b/contentcuration/locale/ar/LC_MESSAGES/django.po
index f202da9788..e39e365b39 100644
--- a/contentcuration/locale/ar/LC_MESSAGES/django.po
+++ b/contentcuration/locale/ar/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-09-03 19:45+0000\n"
-"PO-Revision-Date: 2025-09-12 13:59\n"
+"POT-Creation-Date: 2026-04-16 00:57+0000\n"
+"PO-Revision-Date: 2026-04-24 16:20\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
@@ -14,8 +14,8 @@ msgstr ""
"X-Crowdin-Project: kolibri-studio\n"
"X-Crowdin-Project-ID: 286000\n"
"X-Crowdin-Language: ar\n"
-"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n"
-"X-Crowdin-File-ID: 4886\n"
+"X-Crowdin-File: /unstable/django.po\n"
+"X-Crowdin-File-ID: 4322\n"
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
#: contentcuration/settings.py:271
@@ -26,25 +26,25 @@ msgstr "العربيّة"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "الموقع حالياً في وضع القراءة فقط. الرجاء المحاولة مجدداً في وقت لاحق."
-#: contentcuration/models.py:342
+#: contentcuration/models.py:356
msgid "Not enough space. Check your storage under Settings page."
msgstr "لا توجد مساحة كافية. تحقق من مساحة التخزين الخاصة بك تحت صفحة الإعدادات."
-#: contentcuration/models.py:374 contentcuration/models.py:386
+#: contentcuration/models.py:440 contentcuration/models.py:452
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "نفذت مساحة التخزين! قم بطلب المزيد من المساحة من الإعدادات> التخزين."
-#: contentcuration/models.py:2155
+#: contentcuration/models.py:2531
msgid " (Original)"
msgstr " (أصلي)"
-#: contentcuration/models.py:3297
+#: contentcuration/models.py:3848
msgid "Created DateTime"
-msgstr "أنشئ في DateTime"
+msgstr ""
-#: contentcuration/models.py:3299
+#: contentcuration/models.py:3850
msgid "Datetime field when the custom_metadata for task was created in UTC"
-msgstr "حقل Datetime لإنشاء custom_metadata الخاصة بالمهمة بالتوقيت العالمي المنسق (UTC)"
+msgstr ""
#: contentcuration/settings.py:269
msgid "English"
@@ -716,7 +716,7 @@ msgstr "نواجه مشكلات مع خدمة مرتبطة بجهة خارجية
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here"
msgstr " نواجه مشكلات مع مركز البيانات الخاص بنا. هذا يعني أنك قد تواجه مشاكل في الشبكات أثناء استخدام الاستوديو. نقدر صبرك أثناء حل هذه المشكلات. للتحقق من حالة هذه الخدمة قم بزيارة هنا"
-#: contentcuration/utils/publish.py:101
+#: contentcuration/utils/publish.py:103
msgid "Kolibri Studio Channel Published"
msgstr "تم نشر قناة استوديو كوليبري"
@@ -728,14 +728,15 @@ msgstr "الإبلاغ عن مشكلة في استوديو كوليبري"
msgid "Kolibri Studio account deleted"
msgstr "تم حذف حساب استوديو كوليبري"
-#: kolibri_public/views.py:223
+#: kolibri_public/views.py:323
msgid "Resource"
msgstr "مصدر"
-#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94
+#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167
msgid "API version is unavailable"
msgstr "إصدار الـ API غير متوفر"
-#: kolibri_public/views_v1.py:97
+#: kolibri_public/views_v1.py:170
msgid "No channel matching {} found"
msgstr "لم يتم العثور على قناة مطابقة {}"
+
diff --git a/contentcuration/locale/en/LC_MESSAGES/README.md b/contentcuration/locale/en/LC_MESSAGES/README.md
index 0f82b94d50..014a952e9a 100644
--- a/contentcuration/locale/en/LC_MESSAGES/README.md
+++ b/contentcuration/locale/en/LC_MESSAGES/README.md
@@ -1 +1 @@
-The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
+The JSON messages files in this folder were generated by kolibri-i18n csvToJSON.js
diff --git a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json
index 27ddf2ddfc..1983272e37 100644
--- a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json
@@ -67,8 +67,6 @@
"AdministrationAppError.unauthorizedDetails": "You need to be an administrator of Studio to view this page",
"AdministrationIndex.channelsLabel": "Channels",
"AdministrationIndex.usersLabel": "Users",
- "Alert.closeButtonLabel": "OK",
- "Alert.dontShowAgain": "Don't show this message again",
"AnswersEditor.answersLabel": "Answers",
"AnswersEditor.newAnswerBtnLabel": "New answer",
"AnswersEditor.noAnswersPlaceholder": "Question has no answer options",
@@ -115,10 +113,10 @@
"BrowsingCard.resourcesCount": "{count, number} {count, plural, one {resource} other {resources}}",
"BrowsingCard.tagsList": "Tags: {tags}",
"BytesForHumansStrings.fileSizeInBytes": "{n, number, integer} B",
- "BytesForHumansStrings.fileSizeInGigabytes": "{n, number, integer} GB",
+ "BytesForHumansStrings.fileSizeInGigabytes": "{n, number} GB",
"BytesForHumansStrings.fileSizeInKilobytes": "{n, number, integer} KB",
"BytesForHumansStrings.fileSizeInMegabytes": "{n, number, integer} MB",
- "BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB",
+ "BytesForHumansStrings.fileSizeInTerabytes": "{n, number} TB",
"CatalogFAQ.KolibriAnswer": "Kolibri is an open source ed-tech platform designed for low-resource communities, focused on:",
"CatalogFAQ.KolibriAnswerItem1": "Overcoming infrastructural barriers that prevent equitable access to quality education for learners in low-resource and low-connectivity contexts",
"CatalogFAQ.KolibriAnswerItem2": "Increasing the availability of open learning materials suitable for many curricula, learning goals, and situations",
@@ -184,26 +182,31 @@
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "Starred",
"CatalogFilterBar.subtitles": "Subtitles",
- "CatalogFilters.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
- "CatalogFilters.coachLabel": "Resources for coaches",
- "CatalogFilters.copyright": "© {year} Learning Equality",
- "CatalogFilters.formatLabel": "Formats",
- "CatalogFilters.frequentlyAskedQuestionsLink": "Frequently asked questions",
- "CatalogFilters.includesLabel": "Display only channels with",
- "CatalogFilters.licenseLabel": "Licenses",
- "CatalogFilters.searchLabel": "Keywords",
- "CatalogFilters.searchText": "Search",
- "CatalogFilters.starredLabel": "Starred",
- "CatalogFilters.subtitlesLabel": "Captions or subtitles",
+ "CatalogFilterPanelContent.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
+ "CatalogFilterPanelContent.coachLabel": "Resources for coaches",
+ "CatalogFilterPanelContent.copyright": "© {year} Learning Equality",
+ "CatalogFilterPanelContent.formatLabel": "Formats",
+ "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Frequently asked questions",
+ "CatalogFilterPanelContent.includesLabel": "Display only channels with",
+ "CatalogFilterPanelContent.licenseLabel": "Licenses",
+ "CatalogFilterPanelContent.searchLabel": "Keywords",
+ "CatalogFilterPanelContent.starredLabel": "Starred",
+ "CatalogFilterPanelContent.subtitlesLabel": "Captions or subtitles",
+ "CatalogFilters.filterLabel": "Filter",
"CatalogList.cancelButton": "Cancel",
"CatalogList.channelSelectionCount": "{count, plural,\n =1 {# channel selected}\n other {# channels selected}}",
+ "CatalogList.copyToken": "Copy channel token",
"CatalogList.downloadButton": "Download",
"CatalogList.downloadCSV": "Download CSV",
"CatalogList.downloadPDF": "Download PDF",
"CatalogList.downloadingMessage": "Download started",
+ "CatalogList.goToWebsite": "Go to source website",
+ "CatalogList.moreOptions": "More options",
"CatalogList.resultsText": "{count, plural,\n =1 {# result found}\n other {# results found}}",
"CatalogList.selectAll": "Select all",
"CatalogList.selectChannels": "Download a summary of selected channels",
+ "CatalogList.title": "Kolibri library",
+ "CatalogList.viewContent": "View channel on Kolibri",
"CategoryOptions.noCategoryFoundText": "Category not found",
"ChangePasswordForm.cancelAction": "Cancel",
"ChangePasswordForm.changePasswordHeader": "Change password",
@@ -224,9 +227,6 @@
"ChannelCatalogFrontPage.languagesHeading": "Languages",
"ChannelCatalogFrontPage.numberOfChannels": "{ num } channels",
"ChannelCatalogFrontPage.subtitlesIncludedText": "Captions or subtitles",
- "ChannelDeletedError.backToHomeAction": "Back to home",
- "ChannelDeletedError.channelDeletedDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.",
- "ChannelDeletedError.channelDeletedHeader": "Channel not found",
"ChannelDetailsModal.downloadButton": "Download channel summary",
"ChannelDetailsModal.downloadCSV": "Download CSV",
"ChannelDetailsModal.downloadPDF": "Download PDF",
@@ -263,23 +263,17 @@
"ChannelInvitation.editText": "{sender} has invited you to edit {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "Go to channel",
"ChannelInvitation.viewText": "{sender} has invited you to view {channel}",
- "ChannelItem.cancel": "Cancel",
"ChannelItem.channelDeletedSnackbar": "Channel deleted",
"ChannelItem.channelLanguageNotSetIndicator": "No language set",
"ChannelItem.channelRemovedSnackbar": "Channel removed",
"ChannelItem.copyToken": "Copy channel token",
"ChannelItem.deleteChannel": "Delete channel",
- "ChannelItem.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
- "ChannelItem.deleteTitle": "Delete this channel",
"ChannelItem.details": "Details",
"ChannelItem.editChannel": "Edit channel details",
"ChannelItem.goToWebsite": "Go to source website",
"ChannelItem.lastPublished": "Published {last_published}",
"ChannelItem.lastUpdated": "Updated {updated}",
- "ChannelItem.removeBtn": "Remove",
- "ChannelItem.removeChannel": "Remove from channel list",
- "ChannelItem.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.",
- "ChannelItem.removeTitle": "Remove from channel list",
+ "ChannelItem.removeChannel": "Remove channel",
"ChannelItem.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
"ChannelItem.unpublishedText": "Unpublished",
"ChannelItem.versionText": "Version {version}",
@@ -289,10 +283,9 @@
"ChannelList.noChannelsFound": "No channels found",
"ChannelList.noMatchingChannels": "There are no matching channels",
"ChannelListAppError.channelPermissionsErrorDetails": "Sign in or ask the owner of this channel to give you permission to edit or view",
- "ChannelListIndex.catalog": "Content Library",
+ "ChannelListIndex.catalog": "Kolibri Library",
"ChannelListIndex.channelSets": "Collections",
"ChannelListIndex.frequentlyAskedQuestions": "Frequently asked questions",
- "ChannelListIndex.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}",
"ChannelListIndex.libraryTitle": "Kolibri Content Library Catalog",
"ChannelModal.APIText": "Channels generated automatically are not editable.",
"ChannelModal.changesSaved": "Changes saved",
@@ -316,25 +309,6 @@
"ChannelNotFoundError.channelNotFoundHeader": "Channel not found",
"ChannelSelectionList.noChannelsFound": "No channels found",
"ChannelSelectionList.searchText": "Search for a channel",
- "ChannelSetItem.cancel": "Cancel",
- "ChannelSetItem.delete": "Delete collection",
- "ChannelSetItem.deleteChannelSetText": "Are you sure you want to delete this collection?",
- "ChannelSetItem.deleteChannelSetTitle": "Delete collection",
- "ChannelSetItem.edit": "Edit collection",
- "ChannelSetItem.options": "Options",
- "ChannelSetItem.saving": "Saving",
- "ChannelSetList.aboutChannelSets": "About collections",
- "ChannelSetList.aboutChannelSetsLink": "Learn about collections",
- "ChannelSetList.addChannelSetTitle": "New collection",
- "ChannelSetList.cancelButtonLabel": "Close",
- "ChannelSetList.channelNumber": "Number of channels",
- "ChannelSetList.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.",
- "ChannelSetList.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections",
- "ChannelSetList.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.",
- "ChannelSetList.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.",
- "ChannelSetList.options": "Options",
- "ChannelSetList.title": "Collection name",
- "ChannelSetList.token": "Token ID",
"ChannelSetModal.bookmark": "Starred",
"ChannelSetModal.channelAdded": "Channel added",
"ChannelSetModal.channelCountText": "{channelCount, plural, =0 {No published channels in your collection} =1 {# channel} other {# channels}}",
@@ -533,6 +507,163 @@
"CommonMetadataStrings.webDesign": "Web design",
"CommonMetadataStrings.work": "Work",
"CommonMetadataStrings.writing": "Writing",
+ "CommonStrings.backAction": "Back",
+ "CommonStrings.channelDetailsLabel": "Channel Details",
+ "CommonStrings.clearAction": "Clear",
+ "CommonStrings.closeAction": "Close",
+ "CommonStrings.copyChannelTokenAction": "Copy channel token",
+ "CommonStrings.dismissAction": "Dismiss",
+ "CommonStrings.genericErrorMessage": "Sorry! Something went wrong, please try again.",
+ "CommonStrings.previewAction": "Preview",
+ "CommonStrings.seeAllAction": "See all",
+ "CommonStrings.seeLessAction": "See less",
+ "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.",
+ "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library",
+ "CommunityChannelsStrings.activityHistoryLabel": "Activity history",
+ "CommunityChannelsStrings.adminLabel": "Admin",
+ "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.",
+ "CommunityChannelsStrings.allNotificationsLabel": "All Notifications",
+ "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.",
+ "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.",
+ "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}",
+ "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.",
+ "CommunityChannelsStrings.approvedStatus": "Approved",
+ "CommunityChannelsStrings.availableStatus": "Available in Community Library",
+ "CommunityChannelsStrings.cancelAction": "Cancel",
+ "CommunityChannelsStrings.categoriesLabel": "Categories",
+ "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.",
+ "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution",
+ "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information",
+ "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?",
+ "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?",
+ "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library",
+ "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?",
+ "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?",
+ "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?",
+ "CommunityChannelsStrings.channelFitLicenseLabel": "License",
+ "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use",
+ "CommunityChannelsStrings.channelFitQualityLabel": "Quality",
+ "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.",
+ "CommunityChannelsStrings.channelVersion": "{name} v{version}",
+ "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token",
+ "CommunityChannelsStrings.clearAll": "Clear all",
+ "CommunityChannelsStrings.clearAllAction": "Clear all",
+ "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.",
+ "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library",
+ "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.",
+ "CommunityChannelsStrings.communityLibraryLabel": "Community Library",
+ "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission",
+ "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.",
+ "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.",
+ "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue",
+ "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.",
+ "CommunityChannelsStrings.countryLabel": "Countries",
+ "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission",
+ "CommunityChannelsStrings.dismissAction": "Dismiss",
+ "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published",
+ "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully",
+ "CommunityChannelsStrings.draftTokenLabel": "Draft token",
+ "CommunityChannelsStrings.editorLabel": "Editor",
+ "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.",
+ "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.",
+ "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history",
+ "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel",
+ "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer",
+ "CommunityChannelsStrings.filterByDateLabel": "Filter by date",
+ "CommunityChannelsStrings.filterByStatusLabel": "Filter by status",
+ "CommunityChannelsStrings.filterLabel": "Filter",
+ "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.",
+ "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}",
+ "CommunityChannelsStrings.flaggedStatus": "Needs changes",
+ "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel",
+ "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels",
+ "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria",
+ "CommunityChannelsStrings.hideVersions": "Hide versions",
+ "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.",
+ "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.",
+ "CommunityChannelsStrings.incompleteResourcesDescription1": "Incomplete resources will not be published and made available for download in Kolibri.",
+ "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}",
+ "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)",
+ "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses",
+ "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata",
+ "CommunityChannelsStrings.languageLabel": "Language",
+ "CommunityChannelsStrings.languagesLabel": "Languages",
+ "CommunityChannelsStrings.lessDetailsButton": "Hide details",
+ "CommunityChannelsStrings.licenseCheckPassed": "License check passed.",
+ "CommunityChannelsStrings.licensesLabel": "Licenses",
+ "CommunityChannelsStrings.loadError": "There was an error loading channels.",
+ "CommunityChannelsStrings.loadingVersionHistory": "Loading version history",
+ "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.",
+ "CommunityChannelsStrings.moreDetailsButton": "More details",
+ "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.",
+ "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.",
+ "CommunityChannelsStrings.newLabel": "New",
+ "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.",
+ "CommunityChannelsStrings.nextPageAction": "Next",
+ "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.",
+ "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.",
+ "CommunityChannelsStrings.noVersionsAvailable": "No version history available",
+ "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ",
+ "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.",
+ "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet",
+ "CommunityChannelsStrings.notificationsLabel": "Notifications",
+ "CommunityChannelsStrings.otherIssuesReason": "Other issues",
+ "CommunityChannelsStrings.pageIndicator": "{currentPage} of {totalPages}",
+ "CommunityChannelsStrings.pendingStatus": "Submitted",
+ "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems",
+ "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri",
+ "CommunityChannelsStrings.previousPageAction": "Previous",
+ "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.",
+ "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.",
+ "CommunityChannelsStrings.publishAction": "Publish",
+ "CommunityChannelsStrings.publishChannel": "Publish channel",
+ "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.",
+ "CommunityChannelsStrings.publishChannelMode": "Publish channel",
+ "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.",
+ "CommunityChannelsStrings.publishDraftMode": "Publish draft channel",
+ "CommunityChannelsStrings.publishedVersionLabel": "Published version:",
+ "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}",
+ "CommunityChannelsStrings.publishingMessage": "Channel is being published",
+ "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues",
+ "CommunityChannelsStrings.reasonLabel": "Reason: {reason}",
+ "CommunityChannelsStrings.resubmitAction": "Resubmit",
+ "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.",
+ "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?",
+ "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?",
+ "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}",
+ "CommunityChannelsStrings.retry": "Retry",
+ "CommunityChannelsStrings.reviewAction": "Review",
+ "CommunityChannelsStrings.saveDraft": "Save draft",
+ "CommunityChannelsStrings.searchLabel": "Search",
+ "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications",
+ "CommunityChannelsStrings.seeAllVersions": "See all versions",
+ "CommunityChannelsStrings.showMore": "Show more",
+ "CommunityChannelsStrings.showOlderAction": "Show older",
+ "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected",
+ "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.",
+ "CommunityChannelsStrings.submissionNotesLabel": "Submission notes",
+ "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}",
+ "CommunityChannelsStrings.submitButton": "Submit for review",
+ "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library",
+ "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.",
+ "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library",
+ "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...",
+ "CommunityChannelsStrings.supersededStatus": "Superseded",
+ "CommunityChannelsStrings.thisMonthLabel": "This month",
+ "CommunityChannelsStrings.thisWeekLabel": "This week",
+ "CommunityChannelsStrings.thisYearLabel": "This year",
+ "CommunityChannelsStrings.todayLabel": "Today",
+ "CommunityChannelsStrings.unreadNotificationsLabel": "Unread",
+ "CommunityChannelsStrings.versionDescriptionLabel": "Version description",
+ "CommunityChannelsStrings.versionLabel": "Version {version}",
+ "CommunityChannelsStrings.versionNotesLabel": "Describe what's included in this channel version",
+ "CommunityChannelsStrings.viewCriteriaAction": "View criteria",
+ "CommunityChannelsStrings.viewMoreAction": "View more",
+ "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here",
+ "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language",
+ "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri",
+ "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata",
+ "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?",
"CommunityStandardsModal.communityStandardsHeader": "Community Standards",
"CommunityStandardsModal.coreValuesLink": "Learn more about Learning Equality's core values",
"CommunityStandardsModal.description": "Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.",
@@ -613,7 +744,7 @@
"ConstantStrings.perseus": "Perseus Exercise",
"ConstantStrings.perseus_question": "Khan Academy question",
"ConstantStrings.png": "PNG image",
- "ConstantStrings.public": "Content library",
+ "ConstantStrings.public": "Kolibri library",
"ConstantStrings.single_selection": "Single choice",
"ConstantStrings.slideshow": "Slideshow",
"ConstantStrings.svg": "SVG image",
@@ -819,40 +950,11 @@
"DeleteAccountForm.emailInvalidText": "Email does not match your account email",
"DeleteAccountForm.fieldRequired": "Field is required",
"DeleteAccountForm.ok": "OK",
- "DetailsPanel.AVERAGE": "Average",
- "DetailsPanel.LARGE": "Large",
- "DetailsPanel.SMALL": "Small",
- "DetailsPanel.VERY_LARGE": "Very large",
- "DetailsPanel.VERY_SMALL": "Very small",
- "DetailsPanel.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder",
- "DetailsPanel.aggregatorsLabel": "Aggregators",
- "DetailsPanel.assessmentsIncludedText": "Assessments",
- "DetailsPanel.authorToolTip": "Person or organization who created this content",
- "DetailsPanel.authorsLabel": "Authors",
- "DetailsPanel.categoriesHeading": "Categories",
- "DetailsPanel.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
- "DetailsPanel.coachHeading": "Resources for coaches",
- "DetailsPanel.containsContentHeading": "Contains content from",
- "DetailsPanel.containsHeading": "Contains",
- "DetailsPanel.copyrightHoldersLabel": "Copyright holders",
- "DetailsPanel.creationHeading": "Created on",
- "DetailsPanel.currentVersionHeading": "Published version",
- "DetailsPanel.languagesHeading": "Languages",
- "DetailsPanel.levelsHeading": "Levels",
- "DetailsPanel.licensesLabel": "Licenses",
- "DetailsPanel.primaryLanguageHeading": "Primary language",
- "DetailsPanel.providerToolTip": "Organization that commissioned or is distributing the content",
- "DetailsPanel.providersLabel": "Providers",
- "DetailsPanel.publishedHeading": "Published on",
- "DetailsPanel.resourceHeading": "Total resources",
- "DetailsPanel.sampleFromChannelHeading": "Sample content from this channel",
- "DetailsPanel.sampleFromTopicHeading": "Sample content from this topic",
- "DetailsPanel.sizeHeading": "Channel size",
- "DetailsPanel.sizeText": "{text} ({size})",
- "DetailsPanel.subtitlesHeading": "Captions and subtitles",
- "DetailsPanel.tagsHeading": "Common tags",
- "DetailsPanel.tokenHeading": "Channel token",
- "DetailsPanel.unpublishedText": "Unpublished",
+ "DeleteChannelModal.cancel": "Cancel",
+ "DeleteChannelModal.channelDeletedSnackbar": "Channel deleted",
+ "DeleteChannelModal.deleteChannel": "Delete channel",
+ "DeleteChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
+ "DeleteChannelModal.deleteTitle": "Delete this channel",
"DetailsTabView.aggregatorLabel": "Aggregator",
"DetailsTabView.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder",
"DetailsTabView.assessmentOptionsLabel": "Assessment options",
@@ -1008,8 +1110,6 @@
"ForgotPassword.forgotPasswordPrompt": "Please enter your email address to receive instructions for resetting your password",
"ForgotPassword.forgotPasswordTitle": "Reset your password",
"ForgotPassword.submitButton": "Submit",
- "FormulasMenu.btnLabelInsert": "Insert",
- "FormulasMenu.formulasMenuTitle": "Special characters",
"FormulasStrings.addition": "Addition",
"FormulasStrings.advancedCategory": "Advanced",
"FormulasStrings.alpha": "alpha",
@@ -1204,16 +1304,6 @@
"HintsEditor.newHintBtnLabel": "New hint",
"HintsEditor.noHintsPlaceholder": "Question has no hints",
"ImageOnlyThumbnail.thumbnail": "{title} thumbnail",
- "ImagesMenu.acceptsText": "Supported file types: {acceptedFormats}",
- "ImagesMenu.altTextHint": "The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
- "ImagesMenu.altTextLabel": "Image description",
- "ImagesMenu.btnLabelCancel": "Cancel",
- "ImagesMenu.btnLabelInsert": "Insert",
- "ImagesMenu.currentImageDefaultText": "Current image",
- "ImagesMenu.defaultDropText": "Drag and drop an image here, or upload manually",
- "ImagesMenu.imageHeader": "Upload image",
- "ImagesMenu.selectFile": "Select file",
- "ImagesMenu.selectFileButton": "Select file",
"ImportFromChannelsModal.addButton": "Add",
"ImportFromChannelsModal.addedText": "Added",
"ImportFromChannelsModal.importAction": "Import",
@@ -1223,6 +1313,7 @@
"ImportFromChannelsModal.reviewAction": "Review",
"ImportFromChannelsModal.reviewTitle": "Resource selection",
"InfoModal.close": "Close",
+ "InfoModal.open": "More information about licenses",
"InheritAncestorMetadataModal.applyResourceDetailsTitle": "Apply details from the folder '{folder}'",
"InheritAncestorMetadataModal.cancelAction": "Cancel",
"InheritAncestorMetadataModal.categories": "Categories: {categories}",
@@ -1255,17 +1346,48 @@
"MainNavigationDrawer.helpLink": "Help and support",
"MainNavigationDrawer.logoutLink": "Sign out",
"MainNavigationDrawer.settingsLink": "Settings",
- "MarkdownEditor.bold": "Bold (Ctrl+B)",
- "MarkdownEditor.formulas": "Insert formula (Ctrl+F)",
- "MarkdownEditor.image": "Insert image (Ctrl+P)",
- "MarkdownEditor.italic": "Italic (Ctrl+I)",
- "MarkdownEditor.minimize": "Minimize (Ctrl+M)",
- "MarkdownImageField.editImageOption": "Edit",
- "MarkdownImageField.removeImageOption": "Remove",
- "MarkdownImageField.resizeImageOption": "Resize",
"MasteryCriteriaGoal.labelText": "Goal",
"MasteryCriteriaMofNFields.mHint": "Correct answers needed",
"MasteryCriteriaMofNFields.nHint": "Recent answers",
+ "MathLiveA11yStrings.accented": "accented",
+ "MathLiveA11yStrings.array": "array",
+ "MathLiveA11yStrings.box": "box",
+ "MathLiveA11yStrings.chemicalFormula": "chemical formula",
+ "MathLiveA11yStrings.crossOut": "cross out",
+ "MathLiveA11yStrings.deleted": "deleted: ",
+ "MathLiveA11yStrings.delimiter": "delimiter",
+ "MathLiveA11yStrings.denominator": "denominator",
+ "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}",
+ "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield",
+ "MathLiveA11yStrings.error": "error",
+ "MathLiveA11yStrings.extensibleSymbol": "extensible symbol",
+ "MathLiveA11yStrings.first": "first",
+ "MathLiveA11yStrings.fraction": "fraction",
+ "MathLiveA11yStrings.group": "group",
+ "MathLiveA11yStrings.index": "index",
+ "MathLiveA11yStrings.latex": "LaTeX",
+ "MathLiveA11yStrings.line": "line",
+ "MathLiveA11yStrings.mathField": "math field",
+ "MathLiveA11yStrings.mathfield": "math field",
+ "MathLiveA11yStrings.numerator": "numerator",
+ "MathLiveA11yStrings.operator": "operator",
+ "MathLiveA11yStrings.outOf": "out of {relationName};",
+ "MathLiveA11yStrings.overUnder": "over-under",
+ "MathLiveA11yStrings.parent": "parent",
+ "MathLiveA11yStrings.placeholder": "placeholder",
+ "MathLiveA11yStrings.prompt": "prompt",
+ "MathLiveA11yStrings.radicand": "radicand",
+ "MathLiveA11yStrings.rule": "rule",
+ "MathLiveA11yStrings.selected": "selected: ",
+ "MathLiveA11yStrings.space": "space",
+ "MathLiveA11yStrings.spacing": "spacing",
+ "MathLiveA11yStrings.squareRoot": "square root",
+ "MathLiveA11yStrings.startOf": "start of {relationName}: ",
+ "MathLiveA11yStrings.subscript": "subscript",
+ "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript",
+ "MathLiveA11yStrings.superscript": "superscript",
+ "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript",
+ "MathLiveA11yStrings.text": "text",
"MessageLayout.backToLogin": "Continue to sign-in page",
"MoveModal.addTopic": "Add new folder",
"MoveModal.cancel": "Cancel",
@@ -1296,7 +1418,7 @@
"PasswordField.passwordLabel": "Password",
"PasswordInstructionsSent.passwordInstructionsHeader": "Instructions sent. Thank you!",
"PasswordInstructionsSent.passwordInstructionsText": "If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.",
- "PermissionsError.goToHomePageAction": "Go to home page",
+ "PermissionsError.backToHome": "Back to home",
"PermissionsError.permissionDeniedHeader": "Did you forget to sign in?",
"PoliciesModal.checkboxText": "I have agreed to the above terms",
"PoliciesModal.closeButton": "Close",
@@ -1306,6 +1428,7 @@
"PrivacyPolicyModal.updatedPrivacyHeader": "Updated privacy policy",
"ProgressBar.progressText": "{percent}%",
"ProgressModal.defaultErrorText": "Last attempt to publish failed",
+ "ProgressModal.draftHeader": "Saving draft...",
"ProgressModal.lastPublished": "Published {last_published}",
"ProgressModal.publishHeader": "Publishing channel",
"ProgressModal.syncError": "Last attempt to sync failed",
@@ -1346,6 +1469,19 @@
"RelatedResourcesTab.showPreviewBtnLabel": "Show me",
"RelatedResourcesTab.tooManyNextStepsWarning": "Limit the number of next steps to create a more guided learning experience",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "Limit the number of previous steps to create a more guided learning experience",
+ "RemoveChannelFromListModal.cancel": "Cancel",
+ "RemoveChannelFromListModal.channelRemovedSnackbar": "Channel removed",
+ "RemoveChannelFromListModal.removeBtn": "Remove",
+ "RemoveChannelFromListModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.",
+ "RemoveChannelFromListModal.removeTitle": "Remove from channel list",
+ "RemoveChannelModal.cancel": "Cancel",
+ "RemoveChannelModal.deleteChannel": "Delete channel",
+ "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.",
+ "RemoveChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
+ "RemoveChannelModal.deleteTitle": "Delete this channel",
+ "RemoveChannelModal.removeBtn": "Remove",
+ "RemoveChannelModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.",
+ "RemoveChannelModal.removeTitle": "Remove from channel list",
"ReportErrorModal.closeAction": "Close",
"ReportErrorModal.emailDescription": "Contact the support team with your error details and we’ll do our best to help.",
"ReportErrorModal.emailPrompt": "Send an email to the developers",
@@ -1533,8 +1669,11 @@
"SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Too advanced for the knowledge level of learners I'm looking for",
"SearchRecommendationsStrings.tooBasicForLearnersLabel": "Too basic for the knowledge level of learners I'm looking for",
"SearchRecommendationsStrings.tryAgainLink": "Try again",
+ "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!",
+ "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.",
"SearchRecommendationsStrings.undoAction": "Undo",
"SearchRecommendationsStrings.viewMoreLink": "View more",
+ "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations",
"SearchResultsList.failedToLoad": "Failed to load search results",
"SearchResultsList.resultsPerPageLabel": "Results per page",
"SearchResultsList.saveSearchAction": "Save search",
@@ -1588,10 +1727,121 @@
"Storage.spaceUsedOfMax": "{qty} of {max}",
"Storage.storagePercentageUsed": "{qty}% storage used",
"Storage.videoFiles": "Video files should be compressed to maximize storage space and ensure smooth offline distribution and playback. Once compressed, the total storage required may be less than what you originally estimated.",
+ "StudioChannelCard.channelLanguageNotSetIndicator": "No language set",
+ "StudioChannelCard.details": "Details",
+ "StudioChannelCard.lastPublished": "Published {last_published}",
+ "StudioChannelCard.lastUpdated": "Updated {updated}",
+ "StudioChannelCard.multipleCountries": "Multiple countries",
+ "StudioChannelCard.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
+ "StudioChannelCard.selectChannel": "Select {name}",
+ "StudioChannelCard.unpublishedText": "Unpublished",
+ "StudioChannelsPage.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}",
+ "StudioChannelsPage.noChannelsFound": "No channels found",
+ "StudioCollectionsTable.aboutChannelSets": "About collections",
+ "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections",
+ "StudioCollectionsTable.addChannelSetTitle": "New collection",
+ "StudioCollectionsTable.cancel": "Cancel",
+ "StudioCollectionsTable.cancelButtonLabel": "Close",
+ "StudioCollectionsTable.channelNumber": "Number of channels",
+ "StudioCollectionsTable.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.",
+ "StudioCollectionsTable.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections",
+ "StudioCollectionsTable.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.",
+ "StudioCollectionsTable.collectionDeleted": "Collection deleted",
+ "StudioCollectionsTable.copiedTokenId": "Token copied",
+ "StudioCollectionsTable.copyFailed": "Copy failed",
+ "StudioCollectionsTable.copyToken": "Copy token",
+ "StudioCollectionsTable.delete": "Delete collection",
+ "StudioCollectionsTable.deleteChannelSetText": "Are you sure you want to delete this collection?",
+ "StudioCollectionsTable.deleteChannelSetTitle": "Delete collection",
+ "StudioCollectionsTable.deleteError": "Error deleting collection",
+ "StudioCollectionsTable.edit": "Edit collection",
+ "StudioCollectionsTable.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.",
+ "StudioCollectionsTable.options": "Options",
+ "StudioCollectionsTable.pageTitle": "Collections",
+ "StudioCollectionsTable.saving": "Saving",
+ "StudioCollectionsTable.tableCaption": "List of collections",
+ "StudioCollectionsTable.title": "Collection name",
+ "StudioCollectionsTable.token": "Token ID",
+ "StudioCopyToken.copiedTokenId": "Token copied",
+ "StudioCopyToken.copyFailed": "Copy failed",
+ "StudioCopyToken.token": "Token",
+ "StudioCopyToken.tooltipText": "Copy token to import channel into Kolibri",
+ "StudioDetailsPanel.AVERAGE": "Average",
+ "StudioDetailsPanel.LARGE": "Large",
+ "StudioDetailsPanel.SMALL": "Small",
+ "StudioDetailsPanel.VERY_LARGE": "Very large",
+ "StudioDetailsPanel.VERY_SMALL": "Very small",
+ "StudioDetailsPanel.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder",
+ "StudioDetailsPanel.aggregatorsLabel": "Aggregators",
+ "StudioDetailsPanel.assessmentsIncludedText": "Assessments",
+ "StudioDetailsPanel.authorToolTip": "Person or organization who created this content",
+ "StudioDetailsPanel.authorsLabel": "Authors",
+ "StudioDetailsPanel.categoriesHeading": "Categories",
+ "StudioDetailsPanel.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
+ "StudioDetailsPanel.coachHeading": "Resources for coaches",
+ "StudioDetailsPanel.containsContentHeading": "Contains content from",
+ "StudioDetailsPanel.containsHeading": "Contains",
+ "StudioDetailsPanel.copyrightHoldersLabel": "Copyright holders",
+ "StudioDetailsPanel.creationHeading": "Created on",
+ "StudioDetailsPanel.currentVersionHeading": "Published version",
+ "StudioDetailsPanel.languagesHeading": "Languages",
+ "StudioDetailsPanel.levelsHeading": "Levels",
+ "StudioDetailsPanel.licensesLabel": "Licenses",
+ "StudioDetailsPanel.primaryLanguageHeading": "Primary language",
+ "StudioDetailsPanel.providerToolTip": "Organization that commissioned or is distributing the content",
+ "StudioDetailsPanel.providersLabel": "Providers",
+ "StudioDetailsPanel.publishedHeading": "Published on",
+ "StudioDetailsPanel.resourceHeading": "Total resources",
+ "StudioDetailsPanel.sampleFromChannelHeading": "Sample content from this channel",
+ "StudioDetailsPanel.sizeHeading": "Channel size",
+ "StudioDetailsPanel.sizeText": "{text} ({size})",
+ "StudioDetailsPanel.subtitlesHeading": "Captions and subtitles",
+ "StudioDetailsPanel.tagsHeading": "Common tags",
+ "StudioDetailsPanel.tokenHeading": "Channel token",
+ "StudioDetailsPanel.unpublishedText": "Unpublished",
+ "StudioImmersiveModal.close": "Close",
+ "StudioMessageLayout.backToLogin": "Continue to sign-in page",
+ "StudioMyChannels.copyToken": "Copy channel token",
+ "StudioMyChannels.deleteChannel": "Delete channel",
+ "StudioMyChannels.editChannel": "Edit channel details",
+ "StudioMyChannels.goToWebsite": "Go to source website",
+ "StudioMyChannels.moreOptions": "More options",
+ "StudioMyChannels.newChannel": "New channel",
+ "StudioMyChannels.title": "My channels",
+ "StudioMyChannels.viewContent": "View channel on Kolibri",
"StudioOfflineAlert.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.",
"StudioOfflineAlert.onlineText": "You are back online.",
+ "StudioStarredChannels.copyToken": "Copy channel token",
+ "StudioStarredChannels.deleteChannel": "Delete channel",
+ "StudioStarredChannels.editChannel": "Edit channel details",
+ "StudioStarredChannels.goToWebsite": "Go to source website",
+ "StudioStarredChannels.moreOptions": "More options",
+ "StudioStarredChannels.removeChannel": "Remove channel",
+ "StudioStarredChannels.title": "Starred channels",
+ "StudioStarredChannels.viewContent": "View channel on Kolibri",
"StudioTree.missingTitle": "Missing title",
"StudioTree.optionsTooltip": "Options",
+ "StudioViewOnlyChannels.copyToken": "Copy channel token",
+ "StudioViewOnlyChannels.goToWebsite": "Go to source website",
+ "StudioViewOnlyChannels.moreOptions": "More options",
+ "StudioViewOnlyChannels.removeChannel": "Remove channel",
+ "StudioViewOnlyChannels.title": "View-only channels",
+ "StudioViewOnlyChannels.viewContent": "View channel on Kolibri",
+ "SubscriptionCard.annualPrice": "${price, number}/year",
+ "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.",
+ "SubscriptionCard.dismiss": "Dismiss",
+ "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.",
+ "SubscriptionCard.instantUpgrade": "Upgrade storage now",
+ "SubscriptionCard.manageSubscription": "Manage subscription",
+ "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.",
+ "SubscriptionCard.storageAmount": "Storage (GB)",
+ "SubscriptionCard.storageIncluded": "{size} included in your subscription",
+ "SubscriptionCard.storageRange": "Enter a value between 1 and 50",
+ "SubscriptionCard.subscriptionActive": "Storage subscription active",
+ "SubscriptionCard.subscriptionCanceling": "Subscription cancelled",
+ "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.",
+ "SubscriptionCard.upgradeNow": "Upgrade now",
+ "SubscriptionCard.upgradeSuccess": "Storage increased to {size}",
"SubtitlesList.acceptedFormatsTooltip": "Supported formats: {extensions}",
"SubtitlesList.addSubtitleText": "Add captions",
"SubtitlesList.subtitlesHeader": "Captions and subtitles",
@@ -1715,10 +1965,15 @@
"TextArea.fieldRequiredMessage": "Field is required",
"TextField.fieldRequiredMessage": "Field is required",
"Thumbnail.thumbnail": "{title} thumbnail",
+ "ThumbnailGenerator.closeButtonLabel": "OK",
"ThumbnailGenerator.generatedDefaultFilename": "Generated thumbnail",
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "Unable to generate thumbnail",
"ThumbnailGenerator.thumbnailGenerationFailedText": "There was a problem generating a thumbnail",
+ "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing",
+ "TipTapEditorStrings.TipTapViewerLabel": "text editor content",
"TipTapEditorStrings.addLink": "Add link",
+ "TipTapEditorStrings.alignLeft": "Align left",
+ "TipTapEditorStrings.alignRight": "Align right",
"TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
"TipTapEditorStrings.altTextLabel": "Alt text (Optional)",
"TipTapEditorStrings.altTextPlaceholder": "Describe your image...",
@@ -1726,17 +1981,23 @@
"TipTapEditorStrings.bulletList": "Bullet list",
"TipTapEditorStrings.cancel": "Cancel",
"TipTapEditorStrings.cancelLoading": "Cancel loading",
+ "TipTapEditorStrings.clearFormatting": "Clear formatting",
"TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.",
"TipTapEditorStrings.close": "Close",
"TipTapEditorStrings.closeModal": "Close modal",
"TipTapEditorStrings.codeBlock": "Code block",
+ "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar",
"TipTapEditorStrings.copy": "Copy",
"TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions",
"TipTapEditorStrings.copyLink": "Copy link",
+ "TipTapEditorStrings.decreaseFormatSize": "Decrease format size",
"TipTapEditorStrings.defaultImageName": "Image",
"TipTapEditorStrings.edit": "Edit",
"TipTapEditorStrings.editImage": "Edit image",
"TipTapEditorStrings.editLink": "Edit link",
+ "TipTapEditorStrings.editorControls": "Editor controls",
+ "TipTapEditorStrings.errorUploadingImage": "Error uploading image",
+ "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar",
"TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.",
"TipTapEditorStrings.fileSizeUnit": "MB.",
"TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ",
@@ -1745,13 +2006,18 @@
"TipTapEditorStrings.formatHeader3": "Header 3",
"TipTapEditorStrings.formatNormal": "Normal",
"TipTapEditorStrings.formatOptions": "Format options",
+ "TipTapEditorStrings.formatSize": "Format size",
"TipTapEditorStrings.formatSmall": "Small",
"TipTapEditorStrings.formulasMenuTitle": "Special Characters",
"TipTapEditorStrings.goToLink": "Go to link",
"TipTapEditorStrings.historyActions": "History actions",
"TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually",
"TipTapEditorStrings.imagePreview": "Image preview",
+ "TipTapEditorStrings.increaseFormatSize": "Increase format size",
"TipTapEditorStrings.insert": "Insert",
+ "TipTapEditorStrings.insertContent": "Insert content",
+ "TipTapEditorStrings.insertContentMenu": "Insert content menu",
+ "TipTapEditorStrings.insertContentOption": "Insert content option",
"TipTapEditorStrings.insertImage": "Insert image",
"TipTapEditorStrings.insertLink": "Insert link",
"TipTapEditorStrings.insertTools": "Insert tools",
@@ -1760,8 +2026,11 @@
"TipTapEditorStrings.link": "Link",
"TipTapEditorStrings.linkActions": "Link actions",
"TipTapEditorStrings.listFormatting": "List formatting",
+ "TipTapEditorStrings.loadingFormulas": "Loading math editor",
"TipTapEditorStrings.mathFormula": "Math formula",
+ "TipTapEditorStrings.moreButtonText": "More",
"TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.",
+ "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.",
"TipTapEditorStrings.noFileProvided": "No file provided.",
"TipTapEditorStrings.numberedList": "Numbered list",
"TipTapEditorStrings.opensInNewTab": "(opens in new tab)",
@@ -1782,7 +2051,7 @@
"TipTapEditorStrings.strikethrough": "Strikethrough",
"TipTapEditorStrings.subscript": "Subscript",
"TipTapEditorStrings.superscript": "Superscript",
- "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp",
+ "TipTapEditorStrings.supportedFileTypes": "Supported file types: { extensions }",
"TipTapEditorStrings.text": "Text",
"TipTapEditorStrings.textFormatOptions": "Text format options",
"TipTapEditorStrings.textFormattingOptions": "Text formatting options",
@@ -1814,25 +2083,26 @@
"TreeView.showSidebar": "Show sidebar",
"TreeView.updatedResourcesReadyForReview": "Updated resources are ready for review",
"TreeViewBase.apiGenerated": "Generated by API",
- "TreeViewBase.cancel": "Cancel",
"TreeViewBase.channelDeletedSnackbar": "Channel deleted",
"TreeViewBase.channelDetails": "View channel details",
"TreeViewBase.deleteChannel": "Delete channel",
- "TreeViewBase.deleteChannelButton": "Delete channel",
- "TreeViewBase.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
- "TreeViewBase.deleteTitle": "Delete this channel",
"TreeViewBase.editChannel": "Edit channel details",
"TreeViewBase.emptyChannelTooltip": "You cannot publish an empty channel",
"TreeViewBase.getToken": "Get token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}",
+ "TreeViewBase.inviteCollaborators": "Invite collaborators",
"TreeViewBase.noChangesText": "No changes found in channel",
"TreeViewBase.noLanguageSetError": "Channel language is required",
"TreeViewBase.openTrash": "Open trash",
"TreeViewBase.publishButton": "Publish",
"TreeViewBase.publishButtonTitle": "Make this channel available for import into Kolibri",
"TreeViewBase.shareChannel": "Share channel",
+ "TreeViewBase.shareMenuButton": "Share",
+ "TreeViewBase.shareToken": "Share token",
+ "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library",
"TreeViewBase.syncChannel": "Sync resources",
"TreeViewBase.viewOnly": "View-only",
+ "Uploader.closeButtonLabel": "OK",
"Uploader.listDelimiter": ", ",
"Uploader.maxFileSizeText": "{count, plural,\n =1 {# file will not be uploaded.}\n other {# files will not be uploaded.}} File size must be under {size}",
"Uploader.noStorageHeader": "Not enough space",
@@ -1906,4 +2176,4 @@
"sharedVue.masteryModelRequired": "Mastery is required",
"sharedVue.shortActivityLteThirty": "Value must be equal or less than 30",
"sharedVue.titleRequired": "Title is required"
-}
+}
\ No newline at end of file
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/README.md b/contentcuration/locale/es_ES/LC_MESSAGES/README.md
index 0f82b94d50..014a952e9a 100644
--- a/contentcuration/locale/es_ES/LC_MESSAGES/README.md
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/README.md
@@ -1 +1 @@
-The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
+The JSON messages files in this folder were generated by kolibri-i18n csvToJSON.js
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json
index 6edc18959e..a48f7e8d2e 100644
--- a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json
@@ -20,7 +20,7 @@
"Account.exportFailed": "Los datos no se pudieron exportar. Por favor, inténtelo de nuevo.",
"Account.exportStartedHeader": "Exportación de datos iniciada",
"Account.fullNameLabel": "Nombre completo",
- "Account.handleChannelsBeforeAccount": "No se pueden eliminar las cuentas que tienen canales asociados. Primero hay que eliminar estos canales manualmente para poder eliminar la cuenta.",
+ "Account.handleChannelsBeforeAccount": "No se pueden eliminar las cuentas que tienen canales. Primero hay que eliminar estos canales manualmente para poder eliminar la cuenta.",
"Account.passwordLabel": "Contraseña",
"Account.unableToDeleteAdminAccount": "No se puede eliminar una cuenta de administrador",
"Account.usernameLabel": "Nombre de usuario",
@@ -53,9 +53,9 @@
"ActivityDuration.minutesRequired": "Minutos",
"ActivityDuration.notOptionalLabel": "Tiempo requerido para que el recurso se marque como completado. Este valor no se mostrará a los estudiantes.",
"ActivityDuration.optionalLabel": "(Opcional) Tiempo requerido para que el recurso se marque como completado. Este valor no se mostrará a los estudiantes.",
- "AddNextStepsPage.addedNextStepSnackbar": "Se ha añadido el paso siguiente",
+ "AddNextStepsPage.addedNextStepSnackbar": "Paso siguiente añadido",
"AddNextStepsPage.toolbarTitle": "Añadir paso siguiente",
- "AddPreviousStepsPage.addedPreviousStepSnackbar": "Se ha añadido el paso anterior",
+ "AddPreviousStepsPage.addedPreviousStepSnackbar": "Paso anterior añadido",
"AddPreviousStepsPage.toolbarTitle": "Añadir paso anterior",
"AddRelatedResourcesModal.addStepBtnLabel": "Añadir",
"AddRelatedResourcesModal.cancelBtnLabel": "Cancelar",
@@ -67,8 +67,6 @@
"AdministrationAppError.unauthorizedDetails": "Necesita tener permisos de administrador en Studio para ver esta página",
"AdministrationIndex.channelsLabel": "Canales",
"AdministrationIndex.usersLabel": "Usuarios",
- "Alert.closeButtonLabel": "Aceptar",
- "Alert.dontShowAgain": "No volver a mostrar este mensaje",
"AnswersEditor.answersLabel": "Respuestas",
"AnswersEditor.newAnswerBtnLabel": "Nueva respuesta",
"AnswersEditor.noAnswersPlaceholder": "La pregunta no tiene opciones de respuesta",
@@ -96,7 +94,7 @@
"AssessmentItemEditor.questionTypeLabel": "Tipo de respuesta",
"AssessmentItemPreview.answersLabel": "Respuestas",
"AssessmentItemPreview.hintsToggleLabelHide": "Ocultar pistas",
- "AssessmentItemPreview.hintsToggleLabelShow": "Mostrar {hintsCount} {hintsCount, plural, one {pista} other {pistas}}",
+ "AssessmentItemPreview.hintsToggleLabelShow": "Muestra {hintsCount} {hintsCount, plural, one {pista} other {pistas}}",
"AssessmentItemPreview.noAnswersPlaceholder": "La pregunta no tiene opciones de respuesta",
"AssessmentItemToolbar.toolbarLabelAddAbove": "Añadir {itemLabel} arriba",
"AssessmentItemToolbar.toolbarLabelAddBelow": "Añadir {itemLabel} abajo",
@@ -121,7 +119,7 @@
"BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB",
"CatalogFAQ.KolibriAnswer": "Kolibri es una plataforma de código abierto diseñada para las comunidades con bajos recursos, enfocada en:",
"CatalogFAQ.KolibriAnswerItem1": "Superar las barreras infraestructurales que impiden acceso equitativo a una educación de calidad para los estudiantes en contextos de bajos recursos y baja conectividad",
- "CatalogFAQ.KolibriAnswerItem2": "Incrementar la disponibilidad de materiales de aprendizaje abiertos adecuados para una variedad de situaciones, planes y metas de aprendizaje",
+ "CatalogFAQ.KolibriAnswerItem2": "Incrementar la disponibilidad de materiales de aprendizaje abiertos adecuados para una variedad de currículos, metas de aprendizaje y situaciones",
"CatalogFAQ.KolibriAnswerItem3": "Fomentar una pedagogía innovadora y resultados de aprendizaje efectivos",
"CatalogFAQ.KolibriQuestion": "¿Qué es Kolibri?",
"CatalogFAQ.aboutHeader": "¡Bienvenida al Catálogo de la Biblioteca de Contenidos de Kolibri! ",
@@ -166,8 +164,8 @@
"CatalogFAQ.selectionQuestion": "¿De qué manera Learning Equality determina qué es lo que se incluye en esta biblioteca?",
"CatalogFAQ.usingContentAnswer": "¡Genial! Todos estos recursos han sido especialmente ensamblados para su uso en Kolibri, nuestra plataforma de código abierto para aprender sin conexión, así que por favor revise cómo empezar con Kolibri primero, luego siga las instrucciones para importar materiales.",
"CatalogFAQ.usingContentQuestion": "He encontrado algo que me interesa y me gustaría empezar a usar. ¿Qué debo hacer?",
- "CatalogFAQ.usingKolibriAnswerP1": "Aprender más sobre el uso de Kolibri:",
- "CatalogFAQ.usingKolibriAnswerP2": "Consultar la documentación de Kolibri para obtener más información.",
+ "CatalogFAQ.usingKolibriAnswerP1": "Puedes aprender más sobre el uso de Kolibri haciendo cualquiera de los siguientes:",
+ "CatalogFAQ.usingKolibriAnswerP2": "Le invitamos a consultar la documentación de Kolibri para obtener más información.",
"CatalogFAQ.usingKolibriItem1": "Visitar el sitio web de Learning Equality",
"CatalogFAQ.usingKolibriItem2": "Ver una demostración de la plataforma",
"CatalogFAQ.usingKolibriItem3": "Descargar el programa",
@@ -184,26 +182,31 @@
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "Destacados",
"CatalogFilterBar.subtitles": "Subtítulos",
- "CatalogFilters.coachDescription": "Los recursos para tutores son visibles solo a los tutores en Kolibri",
- "CatalogFilters.coachLabel": "Recursos para tutores",
- "CatalogFilters.copyright": "© {year} Learning Equality",
- "CatalogFilters.formatLabel": "Formatos",
- "CatalogFilters.frequentlyAskedQuestionsLink": "Preguntas más frecuentes",
- "CatalogFilters.includesLabel": "Mostrar solo canales con",
- "CatalogFilters.licenseLabel": "Licencias",
- "CatalogFilters.searchLabel": "Palabras clave",
- "CatalogFilters.searchText": "Buscar",
- "CatalogFilters.starredLabel": "Destacados",
- "CatalogFilters.subtitlesLabel": "Subtítulos",
+ "CatalogFilterPanelContent.coachDescription": "Los recursos para tutores son visibles solo a los tutores en Kolibri",
+ "CatalogFilterPanelContent.coachLabel": "Recursos para tutores",
+ "CatalogFilterPanelContent.copyright": "© {year} Learning Equality",
+ "CatalogFilterPanelContent.formatLabel": "Formatos",
+ "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Preguntas más frecuentes",
+ "CatalogFilterPanelContent.includesLabel": "Mostrar solo canales con",
+ "CatalogFilterPanelContent.licenseLabel": "Licencias",
+ "CatalogFilterPanelContent.searchLabel": "Palabras clave",
+ "CatalogFilterPanelContent.starredLabel": "Destacados",
+ "CatalogFilterPanelContent.subtitlesLabel": "Subtítulos",
+ "CatalogFilters.filterLabel": "Filtrar",
"CatalogList.cancelButton": "Cancelar",
"CatalogList.channelSelectionCount": "{count, plural,\n =1 {# canal seleccionado}\n other {# canales seleccionados}}",
+ "CatalogList.copyToken": "Copiar el token del canal",
"CatalogList.downloadButton": "Descargar",
"CatalogList.downloadCSV": "Descargar CSV",
"CatalogList.downloadPDF": "Descargar PDF",
"CatalogList.downloadingMessage": "Descarga iniciada",
+ "CatalogList.goToWebsite": "Ir a la página web de origen",
+ "CatalogList.moreOptions": "Más opciones",
"CatalogList.resultsText": "{count, plural,\n =1 {# resultado encontrado}\n other {# resultados encontrados}}",
"CatalogList.selectAll": "Seleccionar todo",
"CatalogList.selectChannels": "Descargar un resumen de los canales seleccionados",
+ "CatalogList.title": "Biblioteca de Kolibri",
+ "CatalogList.viewContent": "Ver canal en Kolibri",
"CategoryOptions.noCategoryFoundText": "Categoría no encontrada",
"ChangePasswordForm.cancelAction": "Cancelar",
"ChangePasswordForm.changePasswordHeader": "Cambiar contraseña",
@@ -222,11 +225,8 @@
"ChannelCatalogFrontPage.exported": "Exportado",
"ChannelCatalogFrontPage.formatsHeading": "Formatos",
"ChannelCatalogFrontPage.languagesHeading": "Idiomas",
- "ChannelCatalogFrontPage.numberOfChannels": "{ num } Canales",
+ "ChannelCatalogFrontPage.numberOfChannels": "{ num } canales",
"ChannelCatalogFrontPage.subtitlesIncludedText": "Subtítulos",
- "ChannelDeletedError.backToHomeAction": "Volver al inicio",
- "ChannelDeletedError.channelDeletedDetails": "Este canal no existe o puede haber sido eliminado. Por favor, contacte con content@learningequality.org si cree que esto es un error.",
- "ChannelDeletedError.channelDeletedHeader": "Canal no encontrado",
"ChannelDetailsModal.downloadButton": "Descargar resumen del canal",
"ChannelDetailsModal.downloadCSV": "Descargar CSV",
"ChannelDetailsModal.downloadPDF": "Descargar PDF",
@@ -237,7 +237,7 @@
"ChannelExportStrings.containsContentFrom": "Contiene contenidos de",
"ChannelExportStrings.copyrightHolders": "Titulares de derechos de autor",
"ChannelExportStrings.description": "Descripción",
- "ChannelExportStrings.downloadFilename": "{year}_{month}_Biblioteca_Contenido_Kolibri",
+ "ChannelExportStrings.downloadFilename": "{year}_{month}_Libreria_Contenido_Kolibri",
"ChannelExportStrings.id": "ID del canal",
"ChannelExportStrings.language": "Idioma",
"ChannelExportStrings.languages": "Idiomas incluidos",
@@ -263,23 +263,17 @@
"ChannelInvitation.editText": "{sender} le ha invitado a editar {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "Ir al canal",
"ChannelInvitation.viewText": "{sender} le ha invitado a ver {channel}",
- "ChannelItem.cancel": "Cancelar",
"ChannelItem.channelDeletedSnackbar": "Canal eliminado",
"ChannelItem.channelLanguageNotSetIndicator": "Idioma no establecido",
"ChannelItem.channelRemovedSnackbar": "Canal eliminado",
"ChannelItem.copyToken": "Copiar el token del canal",
"ChannelItem.deleteChannel": "Eliminar canal",
- "ChannelItem.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.",
- "ChannelItem.deleteTitle": "Eliminar este canal",
"ChannelItem.details": "Detalles",
"ChannelItem.editChannel": "Editar detalles del canal",
"ChannelItem.goToWebsite": "Ir a la página web de origen",
"ChannelItem.lastPublished": "Publicado {last_published}",
"ChannelItem.lastUpdated": "Actualizado {updated}",
- "ChannelItem.removeBtn": "Eliminar",
- "ChannelItem.removeChannel": "Eliminar de la lista de canales",
- "ChannelItem.removePrompt": "Tiene permisos de solo lectura para este canal. Confirmar que quiere eliminarlo de su lista de canales.",
- "ChannelItem.removeTitle": "Eliminar de la lista de canales",
+ "ChannelItem.removeChannel": "Eliminar canal",
"ChannelItem.resourceCount": "{count, plural,\n =1 {# recurso}\n other {# recursos}}",
"ChannelItem.unpublishedText": "No publicado",
"ChannelItem.versionText": "Versión {version}",
@@ -289,10 +283,9 @@
"ChannelList.noChannelsFound": "No se han encontrado canales",
"ChannelList.noMatchingChannels": "No hay canales que coincidan",
"ChannelListAppError.channelPermissionsErrorDetails": "Inicie sesión o pida al propietario de este canal que le dé permiso para editar o ver",
- "ChannelListIndex.catalog": "Biblioteca de contenido",
+ "ChannelListIndex.catalog": "Biblioteca de Kolibri",
"ChannelListIndex.channelSets": "Colecciones",
"ChannelListIndex.frequentlyAskedQuestions": "Preguntas más frecuentes",
- "ChannelListIndex.invitations": "Tiene {count, plural, one {}\n =1 {# invitación}\n other {# invitaciones}}",
"ChannelListIndex.libraryTitle": "Catálogo de la Biblioteca de Contenidos de Kolibri",
"ChannelModal.APIText": "Los canales generados automáticamente no son editables.",
"ChannelModal.changesSaved": "Cambios guardados",
@@ -316,25 +309,6 @@
"ChannelNotFoundError.channelNotFoundHeader": "Canal no encontrado",
"ChannelSelectionList.noChannelsFound": "No se han encontrado canales",
"ChannelSelectionList.searchText": "Buscar canal",
- "ChannelSetItem.cancel": "Cancelar",
- "ChannelSetItem.delete": "Eliminar colección",
- "ChannelSetItem.deleteChannelSetText": "Por favor, confirme que desea eliminar esta colección.",
- "ChannelSetItem.deleteChannelSetTitle": "Eliminar colección",
- "ChannelSetItem.edit": "Editar colección",
- "ChannelSetItem.options": "Opciones",
- "ChannelSetItem.saving": "Guardando",
- "ChannelSetList.aboutChannelSets": "Acerca de las colecciones",
- "ChannelSetList.aboutChannelSetsLink": "Aprender más sobre las colecciones",
- "ChannelSetList.addChannelSetTitle": "Nueva colección",
- "ChannelSetList.cancelButtonLabel": "Cerrar",
- "ChannelSetList.channelNumber": "Número de canales",
- "ChannelSetList.channelSetsDescriptionText": "Una colección contiene múltiples canales de Kolibri Studio que pueden ser importados a Kolibri con solo el token de la colección.",
- "ChannelSetList.channelSetsDisclaimer": "Se necesita Kolibri con versión 0.12.0 o superior para importar colecciones de canales",
- "ChannelSetList.channelSetsInstructionsText": "Puede hacer una colección seleccionando los canales que desea importar juntos.",
- "ChannelSetList.noChannelSetsFound": "Puede juntar varios canales para crear una colección. La colección completa puede ser importada a Kolibri a la vez usando un token de colección.",
- "ChannelSetList.options": "Opciones",
- "ChannelSetList.title": "Nombre de la colección",
- "ChannelSetList.token": "Token ID",
"ChannelSetModal.bookmark": "Destacados",
"ChannelSetModal.channelAdded": "Canal añadido",
"ChannelSetModal.channelCountText": "{channelCount, plural, one {} =0 {No hay canales publicados en tu colección} =1 {# canal} other {# canales}}",
@@ -348,7 +322,7 @@
"ChannelSetModal.edit": "Mis canales",
"ChannelSetModal.finish": "Finalizar",
"ChannelSetModal.public": "Públicos",
- "ChannelSetModal.publishedChannelsOnlyText": "Solo los canales publicados están disponibles para la selección",
+ "ChannelSetModal.publishedChannelsOnlyText": "Sólo los canales publicados están disponibles para la selección",
"ChannelSetModal.removeText": "Eliminar",
"ChannelSetModal.saveButton": "Guardar y cerrar",
"ChannelSetModal.selectChannelsHeader": "Seleccionar canales",
@@ -357,7 +331,7 @@
"ChannelSetModal.token": "Token de la colección",
"ChannelSetModal.tokenPrompt": "Copiar este token en Kolibri para importar esta colección a su dispositivo.",
"ChannelSetModal.unsavedChangesHeader": "Cambios sin guardar",
- "ChannelSetModal.unsavedChangesText": "Se perderán los cambios sin guardar. Confirmar que desea salir.",
+ "ChannelSetModal.unsavedChangesText": "Se perderán los cambios sin guardar. Por favor, confirme que desea salir.",
"ChannelSetModal.view": "Solo modo de lectura",
"ChannelSharing.alreadyHasAccessError": "El usuario ya tiene acceso a este canal",
"ChannelSharing.alreadyInvitedError": "Usuario ya invitado",
@@ -369,14 +343,14 @@
"ChannelSharing.invitationSentMessage": "Invitación enviada",
"ChannelSharing.inviteButton": "Enviar invitación",
"ChannelSharing.inviteSubheading": "Invitar colaboradores",
- "ChannelSharing.validEmailMessage": "Ingresar un correo electrónico válido",
+ "ChannelSharing.validEmailMessage": "Por favor ingrese un correo electrónico válido",
"ChannelSharingTable.cancelButton": "Cancelar",
"ChannelSharingTable.currentUserText": "{first_name} {last_name} (usted)",
"ChannelSharingTable.deleteInvitation": "Eliminar invitación",
"ChannelSharingTable.deleteInvitationConfirm": "Eliminar invitación",
"ChannelSharingTable.deleteInvitationHeader": "Eliminar invitación",
- "ChannelSharingTable.deleteInvitationText": "Confirmar que desea borrar la invitación para {email}",
- "ChannelSharingTable.editPermissionsGrantedMessage": "Asignados los permisos para editar",
+ "ChannelSharingTable.deleteInvitationText": "Por favor, confirme de que desea borrar la invitación para {email}",
+ "ChannelSharingTable.editPermissionsGrantedMessage": "Permisos para editar concedidos",
"ChannelSharingTable.editorsSubheading": "{count, plural,\n =1 {# usuario que puede editar}\n other {# usuarios que pueden editar}}",
"ChannelSharingTable.guestText": "Invitado",
"ChannelSharingTable.invitationDeletedMessage": "Invitación eliminada",
@@ -386,13 +360,13 @@
"ChannelSharingTable.makeEditor": "Conceder permisos de edición",
"ChannelSharingTable.makeEditorConfirm": "Sí, conceder permisos",
"ChannelSharingTable.makeEditorHeader": "Conceder permisos de edición",
- "ChannelSharingTable.makeEditorText": "Confirmar que desea conceder los permisos de edición a {first_name} {last_name}",
+ "ChannelSharingTable.makeEditorText": "Por favor, confirme de que desea conceder los permisos de edición a {first_name} {last_name}",
"ChannelSharingTable.noUsersText": "No se han encontrado usuarios",
"ChannelSharingTable.optionsDropdown": "Opciones",
"ChannelSharingTable.removeViewer": "Revocar permisos de ver",
"ChannelSharingTable.removeViewerConfirm": "Si, revocar",
"ChannelSharingTable.removeViewerHeader": "Revocar permisos de ver",
- "ChannelSharingTable.removeViewerText": "Confirmar que desea anular los permisos de ver para {first_name} {last_name}",
+ "ChannelSharingTable.removeViewerText": "Por favor, confirme que desea anular los permisos de ver para {first_name} {last_name}",
"ChannelSharingTable.resendInvitation": "Reenviar la invitación",
"ChannelSharingTable.userRemovedMessage": "Usuario eliminado",
"ChannelSharingTable.viewersSubheading": "{count, plural,\n =1 {# usuario que puede ver}\n other {# usuarios que pueden ver}}",
@@ -404,7 +378,7 @@
"ChannelThumbnail.crop": "Recortar",
"ChannelThumbnail.croppingPrompt": "Arrastrar la imagen para colocar",
"ChannelThumbnail.defaultFilename": "Fichero",
- "ChannelThumbnail.noThumbnail": "No hay miniaturas",
+ "ChannelThumbnail.noThumbnail": "No hay miniaturas.",
"ChannelThumbnail.remove": "Eliminar",
"ChannelThumbnail.retryUpload": "Volver a subir",
"ChannelThumbnail.save": "Guardar",
@@ -470,7 +444,7 @@
"CommonMetadataStrings.goal": "Cuando se alcanza el objetivo",
"CommonMetadataStrings.guides": "Guías",
"CommonMetadataStrings.highContrast": "Incluye texto de alto contraste para los estudiantes con baja visión",
- "CommonMetadataStrings.history": "Historia",
+ "CommonMetadataStrings.history": "Historial",
"CommonMetadataStrings.industryAndSectorSpecific": "Industria y sectores específicos",
"CommonMetadataStrings.languageLearning": "Aprendizaje de idiomas",
"CommonMetadataStrings.learningActivity": "Actividad de aprendizaje",
@@ -480,7 +454,7 @@
"CommonMetadataStrings.listen": "Escuchar",
"CommonMetadataStrings.literacy": "Alfabetización",
"CommonMetadataStrings.literature": "Literatura",
- "CommonMetadataStrings.logicAndCriticalThinking": "Lógica y pensamiento crítico",
+ "CommonMetadataStrings.logicAndCriticalThinking": "Pensamiento lógico y crítico",
"CommonMetadataStrings.longActivity": "Actividad larga",
"CommonMetadataStrings.lowerPrimary": "Primaria inferior",
"CommonMetadataStrings.lowerSecondary": "Secundaria inferior",
@@ -533,6 +507,163 @@
"CommonMetadataStrings.webDesign": "Diseño web",
"CommonMetadataStrings.work": "Trabajo",
"CommonMetadataStrings.writing": "Escritura",
+ "CommonStrings.backAction": "Atrás",
+ "CommonStrings.channelDetailsLabel": "Detalles del canal",
+ "CommonStrings.clearAction": "Borrar",
+ "CommonStrings.closeAction": "Cerrar",
+ "CommonStrings.copyChannelTokenAction": "Copiar el token del canal",
+ "CommonStrings.dismissAction": "Descartar",
+ "CommonStrings.genericErrorMessage": "Lo sentimos, algo salió mal. Por favor inténtelo de nuevo.",
+ "CommonStrings.previewAction": "Vista previa",
+ "CommonStrings.seeAllAction": "Ver todo",
+ "CommonStrings.seeLessAction": "Ver menos",
+ "CommunityChannelsStrings.aboutCommunityLibraryDescription": "La Biblioteca comunitaria contiene canales compartidos por la comunidad y aprobados para su descubrimiento en Studio.",
+ "CommunityChannelsStrings.aboutCommunityLibraryTitle": "Acerca de la Biblioteca comunitaria",
+ "CommunityChannelsStrings.activityHistoryLabel": "Historial de actividad",
+ "CommunityChannelsStrings.adminLabel": "Administrador",
+ "CommunityChannelsStrings.allLicensesCompatible": "Todas las licencias son compatibles con Biblioteca comunitaria.",
+ "CommunityChannelsStrings.allNotificationsLabel": "Todas las notificaciones",
+ "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Por favor, espere a que el canal sea revisado. Verá una notificación en su cuenta de Studio después de que la revisión se haya completado.",
+ "CommunityChannelsStrings.alreadySubmittedWarningTitle": "Esta versión del canal ya ha sido enviada a la Biblioteca comunitaria.",
+ "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) ha aprobado {channelVersion}",
+ "CommunityChannelsStrings.approvedPrimaryInfo": "Una versión anterior está ya en la Biblioteca comunitaria. Los revisores verán la última versión primero.",
+ "CommunityChannelsStrings.approvedStatus": "Aprobado",
+ "CommunityChannelsStrings.availableStatus": "Disponible en la Biblioteca comunitaria",
+ "CommunityChannelsStrings.cancelAction": "Cancelar",
+ "CommunityChannelsStrings.categoriesLabel": "Categorías",
+ "CommunityChannelsStrings.channelCannotBeDistributed": "Este canal no puede ser distribuido a través de Kolibri.",
+ "CommunityChannelsStrings.channelFitAttributionLabel": "Atribución",
+ "CommunityChannelsStrings.channelFitChannelInfoLabel": "Información del canal",
+ "CommunityChannelsStrings.channelFitChecklistAttribution": "¿Cada recurso tiene indicado su autor?",
+ "CommunityChannelsStrings.channelFitChecklistChannelInfo": "¿Su canal tiene toda la información básica — título, descripción, miniatura, idioma, categoría y nivel?",
+ "CommunityChannelsStrings.channelFitChecklistIntro": "Criterios para enviar canal a la Biblioteca comunitaria",
+ "CommunityChannelsStrings.channelFitChecklistLicense": "¿Los recursos de su canal tienen la licencia abierta o son de dominio público?",
+ "CommunityChannelsStrings.channelFitChecklistOfflineUse": "¿Todos los recursos en su canal funcionan sin conexión a Internet?",
+ "CommunityChannelsStrings.channelFitChecklistQuality": "¿Alguien de su equipo u organización ha revisado el canal?",
+ "CommunityChannelsStrings.channelFitLicenseLabel": "Licencia",
+ "CommunityChannelsStrings.channelFitOfflineUseLabel": "Uso sin conexión",
+ "CommunityChannelsStrings.channelFitQualityLabel": "Calidad",
+ "CommunityChannelsStrings.channelTokenDescription": "Puede utilizar este token para importar y previsualizar el borrador del canal en Kolibri. Tenga en cuenta que el token para el canal público final será diferente.",
+ "CommunityChannelsStrings.channelVersion": "{name} v{version}",
+ "CommunityChannelsStrings.channelVersionTokenLabel": "Token de la versión de canal",
+ "CommunityChannelsStrings.clearAll": "Eliminar filtros",
+ "CommunityChannelsStrings.clearAllAction": "Eliminar filtros",
+ "CommunityChannelsStrings.communityLibraryCTADescription": "¿Tiene un canal que valga la pena compartir con otros educadores y estudiantes? Envíelo para la revisión a través del menú Compartir, para que pueda ser accesible en Studio.",
+ "CommunityChannelsStrings.communityLibraryCTATitle": "Ayúdanos a hacer crecer la Biblioteca comunitaria",
+ "CommunityChannelsStrings.communityLibraryDescription": "Explorar los canales enviados por la comunidad y aprobados para ser descubiertos en Studio. Copiar los tokens para usarlos en Kolibri.",
+ "CommunityChannelsStrings.communityLibraryLabel": "Biblioteca comunitaria",
+ "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Envío a la Biblioteca comunitaria",
+ "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - Todas las licencias son compatibles con la Biblioteca comunitaria.",
+ "CommunityChannelsStrings.confirmDistributionRights": "Por favor, confirme que tiene derecho a distribuir estos recursos a través de Kolibri.",
+ "CommunityChannelsStrings.confirmReplacementText": "Entiendo que esto reemplazará mi anterior envío en la cola de revisión",
+ "CommunityChannelsStrings.countriesInfoText": "Seleccione uno o más países con los que etiquetar el canal que está enviando. Por ejemplo, si su canal contiene materiales alineados a un currículo nacional o contenido regional específico, la selección de los países pertinentes ayudará a los usuarios a encontrarlo. De lo contrario, déje el campo en blanco.",
+ "CommunityChannelsStrings.countryLabel": "Países",
+ "CommunityChannelsStrings.descriptionLabel": "Describir qué hay de nuevo en este envío",
+ "CommunityChannelsStrings.dismissAction": "Descartar",
+ "CommunityChannelsStrings.draftBeingPublishedNotice": "Se está publicando el borrador de la versión",
+ "CommunityChannelsStrings.draftPublishedNotice": "Borrador publicado correctamente",
+ "CommunityChannelsStrings.draftTokenLabel": "Token del borrador",
+ "CommunityChannelsStrings.editorLabel": "Editor",
+ "CommunityChannelsStrings.emptyNotificationsNotice": "No tiene notificaciones en este momento.",
+ "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No hay notificaciones que coincidan con los filtros seleccionados.",
+ "CommunityChannelsStrings.errorLoadingVersions": "No se puede cargar el historial de versiones",
+ "CommunityChannelsStrings.errorSnackbar": "Hubo un error al enviar el canal",
+ "CommunityChannelsStrings.feedbackNotesLabel": "Notas del revisor",
+ "CommunityChannelsStrings.filterByDateLabel": "Filtrar por fecha",
+ "CommunityChannelsStrings.filterByStatusLabel": "Filtrar por estado",
+ "CommunityChannelsStrings.filterLabel": "Filtrar",
+ "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Por favor, corrija la licencia antes de enviar una nueva versión.",
+ "CommunityChannelsStrings.flaggedNotification": "Versión {channelVersion} necesita cambios",
+ "CommunityChannelsStrings.flaggedStatus": "Necesita cambios",
+ "CommunityChannelsStrings.getDraftTokenAction": "Copiar token para el borrador del canal",
+ "CommunityChannelsStrings.goToMyChannelsAction": "Ir a Mis canales",
+ "CommunityChannelsStrings.hideCriteriaAction": "Ocultar criterios",
+ "CommunityChannelsStrings.hideVersions": "Ocultar versiones",
+ "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - este canal no puede ser distribuido a través de Kolibri. Si no puede cambiar la licencia, elimine todos los recursos con \"{licenseNames}\" antes de volver a enviar.",
+ "CommunityChannelsStrings.incompatibleLicensesDetected": "Se han detectado licencias incompatibles.",
+ "CommunityChannelsStrings.incompleteResourcesDescription1": "Los recursos incompletos no serán publicados ni disponibles para la descarga en Kolibri.",
+ "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {recurso incompleto} other {recursos incompletos}}",
+ "CommunityChannelsStrings.internalNotesLabel": "Notas del administrador (solo uso interno)",
+ "CommunityChannelsStrings.invalidLicensingReason": "Licencias no válidas o incompatibles",
+ "CommunityChannelsStrings.invalidMetadataReason": "Metadatos no válidos o ausentes",
+ "CommunityChannelsStrings.languageLabel": "Idioma",
+ "CommunityChannelsStrings.languagesLabel": "Idiomas",
+ "CommunityChannelsStrings.lessDetailsButton": "Ocultar detalles",
+ "CommunityChannelsStrings.licenseCheckPassed": "Verificación de licencia completada",
+ "CommunityChannelsStrings.licensesLabel": "Licencias",
+ "CommunityChannelsStrings.loadError": "Hubo un error al cargar los canales.",
+ "CommunityChannelsStrings.loadingVersionHistory": "Cargando historial de versiones",
+ "CommunityChannelsStrings.moreDetails": "Después de que su envío sea aprobado, el canal estará disponible para otros usuarios de Kolibri Studio en la página 'Biblioteca comunitaria'.",
+ "CommunityChannelsStrings.moreDetailsButton": "Más detalles",
+ "CommunityChannelsStrings.needKolibriVersionToImport": "Necesitará la versión 0.19.4 o superior de Kolibri para importar canales comunitarios.",
+ "CommunityChannelsStrings.needsChangesPrimaryInfo": "Su versión previamente enviada necesita cambios. Asegúrese de revisar todos los comentarios antes de volver a enviar.",
+ "CommunityChannelsStrings.newLabel": "Nuevo",
+ "CommunityChannelsStrings.newNotificationsNotice": "Nuevas notificaciones disponibles.",
+ "CommunityChannelsStrings.nextPageAction": "Siguiente",
+ "CommunityChannelsStrings.noCommunityChannels": "Todavía no hay canales en la Biblioteca comunitaria.",
+ "CommunityChannelsStrings.noResultsWithFilters": "Ningún canal coincide con los filtros seleccionados.",
+ "CommunityChannelsStrings.noVersionsAvailable": "No hay historial de versiones disponible",
+ "CommunityChannelsStrings.nonePrimaryInfo": "Invitamos a los miembros de la comunidad de Kolibri a enviar canales que han creado para el aprendizaje sin conexión en los contextos de recursos bajos. ",
+ "CommunityChannelsStrings.notPublishedWarningDescription": "Publicar primero en Studio y luego enviar a la Biblioteca comunitaria.",
+ "CommunityChannelsStrings.notPublishedWarningTitle": "Este canal aún no está publicado en Kolibri Studio",
+ "CommunityChannelsStrings.notificationsLabel": "Notificaciones",
+ "CommunityChannelsStrings.otherIssuesReason": "Otras incidencias",
+ "CommunityChannelsStrings.pageIndicator": "{currentPage} de {totalPages}",
+ "CommunityChannelsStrings.pendingStatus": "Enviado",
+ "CommunityChannelsStrings.portabilityIssuesReason": "Problemas de portabilidad",
+ "CommunityChannelsStrings.previewYourDraftTitle": "Ver el borrador de canal en Kolibri",
+ "CommunityChannelsStrings.previousPageAction": "Anterior",
+ "CommunityChannelsStrings.publicWarningDescription": "No es posible enviar canales públicos a la Biblioteca comunitaria.",
+ "CommunityChannelsStrings.publicWarningTitle": "Este canal es actualmente público en la Biblioteca Kolibri.",
+ "CommunityChannelsStrings.publishAction": "Publicar",
+ "CommunityChannelsStrings.publishChannel": "Publicar canal",
+ "CommunityChannelsStrings.publishChannelDescription": "Para ver el canal en Kolibri, usar el token de canal para importarlo.",
+ "CommunityChannelsStrings.publishChannelMode": "Publicar canal",
+ "CommunityChannelsStrings.publishDraftDescription": "Su canal se guardará como el borrador, permitiendo revisar la calidad en el borrador, sin alterar la versión actual pública disponible para los usuarios de Kolibri. Para ver este borrador del canal en Kolibri, hay que importarlo usando su propio token.",
+ "CommunityChannelsStrings.publishDraftMode": "Publicar borrador del canal",
+ "CommunityChannelsStrings.publishedVersionLabel": "Versión publicada:",
+ "CommunityChannelsStrings.publishingInfo": "Está publicando: Versión {version}",
+ "CommunityChannelsStrings.publishingMessage": "El canal se está publicando",
+ "CommunityChannelsStrings.qualityAssuranceReason": "Incidencias de control de calidad",
+ "CommunityChannelsStrings.reasonLabel": "Razón: {reason}",
+ "CommunityChannelsStrings.resubmitAction": "Reenviar",
+ "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} también está publicado en la Biblioteca comunitaria.",
+ "CommunityChannelsStrings.resubmitModalBodySecond": "¿Le gustaría volver a enviar esta versión con sus cambios para la revisión en la Biblioteca comunitaria?",
+ "CommunityChannelsStrings.resubmitModalTitle": "¿Reenviar canal para revisión en la Biblioteca comunitaria?",
+ "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# resultado encontrado} other {# resultados encontrados}}",
+ "CommunityChannelsStrings.retry": "Volver a intentar",
+ "CommunityChannelsStrings.reviewAction": "Revisar",
+ "CommunityChannelsStrings.saveDraft": "Guardar borrador",
+ "CommunityChannelsStrings.searchLabel": "Buscar",
+ "CommunityChannelsStrings.searchNotificationsLabel": "Buscar notificaciones",
+ "CommunityChannelsStrings.seeAllVersions": "Ver todas las versiones",
+ "CommunityChannelsStrings.showMore": "Mostrar más",
+ "CommunityChannelsStrings.showOlderAction": "Ver anteriores",
+ "CommunityChannelsStrings.specialPermissionsDetected": "Licencias de permisos especiales detectadas",
+ "CommunityChannelsStrings.submissionCreationNotification": "Su envío a la Biblioteca comunitaria fue exitoso y ahora está siendo revisado.",
+ "CommunityChannelsStrings.submissionNotesLabel": "Notas de envío",
+ "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) ha enviado {channelVersion}",
+ "CommunityChannelsStrings.submitButton": "Enviar para revisión",
+ "CommunityChannelsStrings.submitToCommunityLibrary": "Enviar a la Biblioteca comunitaria",
+ "CommunityChannelsStrings.submittedPrimaryInfo": "Una versión anterior todavía está pendiente de revisión. Los revisores verán el envío más reciente por defecto.",
+ "CommunityChannelsStrings.submittedSnackbar": "Canal enviado a la Biblioteca comunitaria",
+ "CommunityChannelsStrings.submittingSnackbar": "Enviando canal a la Biblioteca comunitaria...",
+ "CommunityChannelsStrings.supersededStatus": "Reemplazado",
+ "CommunityChannelsStrings.thisMonthLabel": "Este mes",
+ "CommunityChannelsStrings.thisWeekLabel": "Esta semana",
+ "CommunityChannelsStrings.thisYearLabel": "Este año",
+ "CommunityChannelsStrings.todayLabel": "Hoy",
+ "CommunityChannelsStrings.unreadNotificationsLabel": "No leídas",
+ "CommunityChannelsStrings.versionDescriptionLabel": "Descripción de la versión",
+ "CommunityChannelsStrings.versionLabel": "Versión {version}",
+ "CommunityChannelsStrings.versionNotesLabel": "Describir las novedades en esta versión de canal",
+ "CommunityChannelsStrings.viewCriteriaAction": "Ver criterios",
+ "CommunityChannelsStrings.viewMoreAction": "Ver más",
+ "CommunityChannelsStrings.whatCanYouDoHere": "Lo que puede hacer aquí",
+ "CommunityChannelsStrings.whatCanYouDoHereItem1": "Navegar canales por país, categoría e idioma",
+ "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copiar un token de canal para importarlo en Kolibri",
+ "CommunityChannelsStrings.whatCanYouDoHereItem3": "Ver detalles del canal como la descripción y metadatos",
+ "CommunityChannelsStrings.whatIsCommunityLibrary": "¿Qué es la Biblioteca comunitaria?",
"CommunityStandardsModal.communityStandardsHeader": "Normas de la comunidad",
"CommunityStandardsModal.coreValuesLink": "Aprenda más sobre los valores fundamentales de Learning Equality",
"CommunityStandardsModal.description": "Learning Equality es una organización sin fines de lucro dedicada a facilitar un acceso equitativo a experiencias educativas de calidad. Junto con nuestra declaración de valores fundamentales, estas Normas Comunitarias pretenden fomentar un entorno de apoyo e inclusivo para nuestros usuarios.",
@@ -601,20 +732,20 @@
"ConstantStrings.mp4": "Video MP4",
"ConstantStrings.multiple_selection": "Selección múltiple",
"ConstantStrings.nthCopy": "Copia {n, number, integer} de {title}",
- "ConstantStrings.num_correct_in_a_row_10": "Objetivo: 10 aciertos seguidos",
+ "ConstantStrings.num_correct_in_a_row_10": "Objetivo: 10 seguidos",
"ConstantStrings.num_correct_in_a_row_10_description": "Estudiante tiene que responder correctamente a 10 preguntas seguidas",
- "ConstantStrings.num_correct_in_a_row_2": "Objetivo: 2 aciertos seguidos",
+ "ConstantStrings.num_correct_in_a_row_2": "Objetivo: 2 seguidos",
"ConstantStrings.num_correct_in_a_row_2_description": "Estudiante tiene que responder correctamente a 2 preguntas seguidas",
- "ConstantStrings.num_correct_in_a_row_3": "Objetivo: 3 aciertos seguidos",
+ "ConstantStrings.num_correct_in_a_row_3": "Objetivo: 3 seguidos",
"ConstantStrings.num_correct_in_a_row_3_description": "Estudiante tiene que responder correctamente a 3 preguntas seguidas",
- "ConstantStrings.num_correct_in_a_row_5": "Objetivo: 5 aciertos seguidos",
+ "ConstantStrings.num_correct_in_a_row_5": "Objetivo: 5 seguidos",
"ConstantStrings.num_correct_in_a_row_5_description": "Estudiante tiene que responder correctamente a 5 preguntas seguidas",
"ConstantStrings.pdf": "Documento PDF",
"ConstantStrings.perseus": "Ejercicio Perseus",
"ConstantStrings.perseus_question": "Pregunta de Khan Academy",
"ConstantStrings.png": "Imagen PNG",
- "ConstantStrings.public": "Biblioteca de contenido",
- "ConstantStrings.single_selection": "Respuesta única",
+ "ConstantStrings.public": "Biblioteca de Kolibri",
+ "ConstantStrings.single_selection": "Selección única",
"ConstantStrings.slideshow": "Presentación",
"ConstantStrings.svg": "Imagen SVG",
"ConstantStrings.topic": "Carpeta",
@@ -633,7 +764,7 @@
"ContentDefaults.author": "Autor",
"ContentDefaults.copyrightHolder": "Titular de derechos de autor",
"ContentDefaults.defaultsSubTitle": "Los nuevos recursos tendrán estos valores por defecto",
- "ContentDefaults.defaultsTitle": "Configuración por defecto de propiedad intelectual para los nuevos recursos (opcional)",
+ "ContentDefaults.defaultsTitle": "Configuración por defecto de propriedad intelectual para los nuevos recursos (opcional)",
"ContentDefaults.documents": "Documentos",
"ContentDefaults.html5": "Aplicaciones HTML5",
"ContentDefaults.license": "Licencia",
@@ -707,7 +838,7 @@
"ContentNodeThumbnail.defaultFilename": "Fichero",
"ContentNodeThumbnail.generate": "Generar desde fichero",
"ContentNodeThumbnail.generatingThumbnail": "Generando desde fichero",
- "ContentNodeThumbnail.noThumbnail": "No hay miniaturas",
+ "ContentNodeThumbnail.noThumbnail": "No hay miniaturas.",
"ContentNodeThumbnail.remove": "Eliminar",
"ContentNodeThumbnail.retryUpload": "Volver a subir",
"ContentNodeThumbnail.save": "Guardar",
@@ -716,11 +847,11 @@
"ContentNodeThumbnail.uploadingThumbnail": "Cargando",
"ContentNodeThumbnail.zoomIn": "Aumentar tamaño",
"ContentNodeThumbnail.zoomOut": "Reducir tamaño",
- "ContentNodeValidator.allIncompleteDescendantsText": "{count, plural, one {{count, number, integer} recurso está incompleto y no puede ser publicado} other {{count, number, integer} recursos están incompletos y no pueden ser publicados}}",
- "ContentNodeValidator.incompleteDescendantsText": "{count, number, integer} {count, plural, one {recurso está incompleto} other {recursos están incompletos}}",
+ "ContentNodeValidator.allIncompleteDescendantsText": "{count, plural, one {{count, number, integer} el recurso está incompleto y no puede ser publicado} other {todos {count, number, integer} recursos están incompletos y no pueden ser publicados}}",
+ "ContentNodeValidator.incompleteDescendantsText": "{count, number, integer} {count, plural, one {el recurso está incompleto} other {los recursos están incompletos}}",
"ContentNodeValidator.incompleteText": "Incompleto",
"ContentNodeValidator.missingTitle": "Falta el título",
- "ContentRenderer.noFileText": "Seleccionar un fichero para previsualizar",
+ "ContentRenderer.noFileText": "Seleccionar un fichero para la vista previa",
"ContentRenderer.previewNotSupported": "Vista previa no disponible",
"ContentTreeList.allChannelsLabel": "Canales",
"ContentTreeList.noResourcesOrTopics": "No hay recursos ni carpetas aquí",
@@ -728,21 +859,21 @@
"CopyToken.copiedTokenId": "Token copiado",
"CopyToken.copyFailed": "Hubo un fallo al copiar",
"CopyToken.copyPrompt": "Copiar token para importar el canal en Kolibri",
- "CountryField.locationLabel": "Seleccionar todo lo que corresponda",
+ "CountryField.locationLabel": "Seleccione todo lo que corresponda",
"CountryField.locationRequiredMessage": "Este campo es obligatorio",
"CountryField.noCountriesFound": "No se han encontrado países",
- "Create.ToSRequiredMessage": "Por favor, aceptar nuestra política y términos de servicio",
+ "Create.ToSRequiredMessage": "Por favor, acepte nuestra política y términos de servicio",
"Create.agreement": "He leído y estoy de acuerdo con los términos de servicio y la política de privacidad",
"Create.backToLoginButton": "Iniciar sesión",
"Create.basicInformationHeader": "Información básica",
"Create.conferenceSourceOption": "Conferencia",
"Create.conferenceSourcePlaceholder": "Nombre de la conferencia",
"Create.confirmPasswordLabel": "Confirmar la contraseña",
- "Create.contactMessage": "¿Tiene preguntas o inquietudes? Envíenos un correo electrónico a content@learningequality.org",
+ "Create.contactMessage": "¿Tiene preguntas o inquietudes? Por favor envíenos un correo electrónico a content@learningequality.org",
"Create.conversationSourceOption": "Conversación con Learning Equality",
"Create.createAnAccountTitle": "Crear cuenta",
"Create.creatingExercisesUsageOption": "Crear ejercicios",
- "Create.emailExistsMessage": "Ya existe una cuenta asociada a este correo electrónico",
+ "Create.emailExistsMessage": "Ya existe una cuenta asociada a este correo electrónico.",
"Create.errorsMessage": "Por favor, corregir los siguientes errores",
"Create.findingUsageOption": "Buscar y agregar fuentes de contenido adicionales",
"Create.finishButton": "Finalizar",
@@ -763,7 +894,7 @@
"Create.passwordMatchMessage": "Las contraseñas no coinciden",
"Create.passwordValidationMessage": "La contraseña debe tener al menos 8 caracteres",
"Create.personalDemoSourceOption": "Demo personal",
- "Create.registrationFailed": "Hubo un error al crear la cuenta. Por favor, inténtelo nuevamente",
+ "Create.registrationFailed": "Hubo un error al crear la cuenta. Por favor, inténtelo nuevamente.",
"Create.registrationFailedOffline": "Parece que no tiene conexión. Por favor, conéctese a Internet para crear una cuenta.",
"Create.sequencingUsageOption": "Utilizar requisitos previos para poner materiales en una secuencia",
"Create.sharingUsageOption": "Compartir materiales públicamente",
@@ -819,40 +950,11 @@
"DeleteAccountForm.emailInvalidText": "El correo electrónico no coincide con el correo de su cuenta",
"DeleteAccountForm.fieldRequired": "Este campo es obligatorio",
"DeleteAccountForm.ok": "Aceptar",
- "DetailsPanel.AVERAGE": "Medio",
- "DetailsPanel.LARGE": "Grande",
- "DetailsPanel.SMALL": "Pequeño",
- "DetailsPanel.VERY_LARGE": "Muy grande",
- "DetailsPanel.VERY_SMALL": "Muy pequeño",
- "DetailsPanel.aggregatorToolTip": "Sitio web o la organización que aloja la colección de contenido, pero no necesariamente el creador o titular de derechos de autor",
- "DetailsPanel.aggregatorsLabel": "Agregadores",
- "DetailsPanel.assessmentsIncludedText": "Evaluaciones",
- "DetailsPanel.authorToolTip": "Persona u organización que ha creado este contenido",
- "DetailsPanel.authorsLabel": "Autores",
- "DetailsPanel.categoriesHeading": "Categorías",
- "DetailsPanel.coachDescription": "Los recursos para tutores son visibles sólo a los tutores en Kolibri",
- "DetailsPanel.coachHeading": "Recursos para tutores",
- "DetailsPanel.containsContentHeading": "Contiene contenidos de",
- "DetailsPanel.containsHeading": "Contiene",
- "DetailsPanel.copyrightHoldersLabel": "Titulares de derechos de autor",
- "DetailsPanel.creationHeading": "Creado el",
- "DetailsPanel.currentVersionHeading": "Versión publicada",
- "DetailsPanel.languagesHeading": "Idiomas",
- "DetailsPanel.levelsHeading": "Niveles",
- "DetailsPanel.licensesLabel": "Licencias",
- "DetailsPanel.primaryLanguageHeading": "Idioma principal",
- "DetailsPanel.providerToolTip": "Organización que ha encargado o está distribuyendo el contenido",
- "DetailsPanel.providersLabel": "Proveedores",
- "DetailsPanel.publishedHeading": "Publicado el",
- "DetailsPanel.resourceHeading": "El total de recursos",
- "DetailsPanel.sampleFromChannelHeading": "Contenido de ejemplo de este canal",
- "DetailsPanel.sampleFromTopicHeading": "Contenido de ejemplo de este tema",
- "DetailsPanel.sizeHeading": "Tamaño del canal",
- "DetailsPanel.sizeText": "{text} ({size})",
- "DetailsPanel.subtitlesHeading": "Subtítulos",
- "DetailsPanel.tagsHeading": "Etiquetas comunes",
- "DetailsPanel.tokenHeading": "Token del canal",
- "DetailsPanel.unpublishedText": "No publicado",
+ "DeleteChannelModal.cancel": "Cancelar",
+ "DeleteChannelModal.channelDeletedSnackbar": "Canal eliminado",
+ "DeleteChannelModal.deleteChannel": "Eliminar canal",
+ "DeleteChannelModal.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.",
+ "DeleteChannelModal.deleteTitle": "Eliminar este canal",
"DetailsTabView.aggregatorLabel": "Agregador",
"DetailsTabView.aggregatorToolTip": "Sitio web o la organización que aloja la colección de contenido, pero no necesariamente el creador o titular de derechos de autor",
"DetailsTabView.assessmentOptionsLabel": "Opciones de evaluación",
@@ -860,7 +962,7 @@
"DetailsTabView.authorLabel": "Autor",
"DetailsTabView.authorToolTip": "Persona u organización que ha creado este contenido",
"DetailsTabView.basicInfoHeader": "Información básica",
- "DetailsTabView.completionLabel": "Finalización",
+ "DetailsTabView.completionLabel": "Tipo de finalización",
"DetailsTabView.copyrightHolderLabel": "Titular de derechos de autor",
"DetailsTabView.descriptionLabel": "Descripción",
"DetailsTabView.detectedImportText": "{count, plural,\n =1 {# recurso solo tiene permiso de vista}\n other {# recursos solo tienen permiso de vista}}",
@@ -1008,8 +1110,6 @@
"ForgotPassword.forgotPasswordPrompt": "Introduzca su dirección de correo electrónico para recibir instrucciones para restablecer su contraseña",
"ForgotPassword.forgotPasswordTitle": "Restablecer la contraseña",
"ForgotPassword.submitButton": "Enviar",
- "FormulasMenu.btnLabelInsert": "Insertar",
- "FormulasMenu.formulasMenuTitle": "Caracteres especiales",
"FormulasStrings.addition": "Suma",
"FormulasStrings.advancedCategory": "Más detalles",
"FormulasStrings.alpha": "alfa",
@@ -1204,16 +1304,6 @@
"HintsEditor.newHintBtnLabel": "Nueva pista",
"HintsEditor.noHintsPlaceholder": "La pregunta no tiene pistas",
"ImageOnlyThumbnail.thumbnail": "{title} miniatura",
- "ImagesMenu.acceptsText": "Tipos de ficheros permitidos: {acceptedFormats}",
- "ImagesMenu.altTextHint": "La descripción de la imagen es necesaria para permitir que los estudiantes con discapacidad visual respondan a las preguntas, y también se muestra cuando la imagen no se carga",
- "ImagesMenu.altTextLabel": "Descripción de la imagen",
- "ImagesMenu.btnLabelCancel": "Cancelar",
- "ImagesMenu.btnLabelInsert": "Insertar",
- "ImagesMenu.currentImageDefaultText": "Imagen actual",
- "ImagesMenu.defaultDropText": "Arrastrar y soltar una imagen aquí, o subirla manualmente",
- "ImagesMenu.imageHeader": "Subir imagen",
- "ImagesMenu.selectFile": "Seleccionar fichero",
- "ImagesMenu.selectFileButton": "Seleccionar fichero",
"ImportFromChannelsModal.addButton": "Añadir",
"ImportFromChannelsModal.addedText": "Añadido",
"ImportFromChannelsModal.importAction": "Importar",
@@ -1223,6 +1313,7 @@
"ImportFromChannelsModal.reviewAction": "Revisar",
"ImportFromChannelsModal.reviewTitle": "Selección de recursos",
"InfoModal.close": "Cerrar",
+ "InfoModal.open": "Más información sobre licencias",
"InheritAncestorMetadataModal.applyResourceDetailsTitle": "Aplicar detalles desde la carpeta '{folder}'",
"InheritAncestorMetadataModal.cancelAction": "Cancelar",
"InheritAncestorMetadataModal.categories": "Categorías: {categories}",
@@ -1255,17 +1346,48 @@
"MainNavigationDrawer.helpLink": "Ayuda y soporte",
"MainNavigationDrawer.logoutLink": "Cerrar sesión",
"MainNavigationDrawer.settingsLink": "Configuración",
- "MarkdownEditor.bold": "Negrita (Ctrl+B)",
- "MarkdownEditor.formulas": "Insertar fórmula (Ctrl+F)",
- "MarkdownEditor.image": "Insertar imagen (Ctrl+P)",
- "MarkdownEditor.italic": "Cursiva (Ctrl+I)",
- "MarkdownEditor.minimize": "Minimizar (Ctrl+M)",
- "MarkdownImageField.editImageOption": "Editar",
- "MarkdownImageField.removeImageOption": "Eliminar",
- "MarkdownImageField.resizeImageOption": "Cambiar tamaño",
"MasteryCriteriaGoal.labelText": "Objetivo",
"MasteryCriteriaMofNFields.mHint": "Hay que especificar respuestas correctas",
"MasteryCriteriaMofNFields.nHint": "Respuestas recientes",
+ "MathLiveA11yStrings.accented": "acentuado",
+ "MathLiveA11yStrings.array": "matriz",
+ "MathLiveA11yStrings.box": "caja",
+ "MathLiveA11yStrings.chemicalFormula": "fórmula química",
+ "MathLiveA11yStrings.crossOut": "tachar",
+ "MathLiveA11yStrings.deleted": "eliminada: ",
+ "MathLiveA11yStrings.delimiter": "delimitador",
+ "MathLiveA11yStrings.denominator": "denominador",
+ "MathLiveA11yStrings.endOf": "{spokenText}; fin de {relationName}",
+ "MathLiveA11yStrings.endOfMathfield": "{spokenText}; fin de la entrada de matemáticas",
+ "MathLiveA11yStrings.error": "error",
+ "MathLiveA11yStrings.extensibleSymbol": "símbolo extensible",
+ "MathLiveA11yStrings.first": "primero",
+ "MathLiveA11yStrings.fraction": "fracción",
+ "MathLiveA11yStrings.group": "grupo",
+ "MathLiveA11yStrings.index": "índice",
+ "MathLiveA11yStrings.latex": "LaTeX",
+ "MathLiveA11yStrings.line": "línea",
+ "MathLiveA11yStrings.mathField": "campo matemático",
+ "MathLiveA11yStrings.mathfield": "campo matemático",
+ "MathLiveA11yStrings.numerator": "numerador",
+ "MathLiveA11yStrings.operator": "operador",
+ "MathLiveA11yStrings.outOf": "fin de {relationName};",
+ "MathLiveA11yStrings.overUnder": "sobre-debajo",
+ "MathLiveA11yStrings.parent": "padre",
+ "MathLiveA11yStrings.placeholder": "marcador de posición",
+ "MathLiveA11yStrings.prompt": "prompt",
+ "MathLiveA11yStrings.radicand": "radicando",
+ "MathLiveA11yStrings.rule": "regla",
+ "MathLiveA11yStrings.selected": "seleccionado: ",
+ "MathLiveA11yStrings.space": "espacio",
+ "MathLiveA11yStrings.spacing": "espaciado",
+ "MathLiveA11yStrings.squareRoot": "raíz cuadrada",
+ "MathLiveA11yStrings.startOf": "inicio de {relationName}: ",
+ "MathLiveA11yStrings.subscript": "subíndice",
+ "MathLiveA11yStrings.subscriptSuperscript": "subíndice-superíndice",
+ "MathLiveA11yStrings.superscript": "superíndice",
+ "MathLiveA11yStrings.superscriptAndSubscript": "superíndice y subíndice",
+ "MathLiveA11yStrings.text": "texto",
"MessageLayout.backToLogin": "Continuar a la página de inicio de sesión",
"MoveModal.addTopic": "Añadir nueva carpeta",
"MoveModal.cancel": "Cancelar",
@@ -1296,7 +1418,7 @@
"PasswordField.passwordLabel": "Contraseña",
"PasswordInstructionsSent.passwordInstructionsHeader": "Instrucciones enviadas. ¡Gracias!",
"PasswordInstructionsSent.passwordInstructionsText": "Si ya existe una cuenta con la dirección de correo electrónico proporcionada, recibirá las instrucciones en breve. Si no ve un correo electrónico nuestro, por favor revise su carpeta de correo basura.",
- "PermissionsError.goToHomePageAction": "Ir a la página de inicio",
+ "PermissionsError.backToHome": "Volver al inicio",
"PermissionsError.permissionDeniedHeader": "¿Ha olvidado iniciar la sesión?",
"PoliciesModal.checkboxText": "He leído y estoy de acuerdo con los términos anteriores",
"PoliciesModal.closeButton": "Cerrar",
@@ -1306,6 +1428,7 @@
"PrivacyPolicyModal.updatedPrivacyHeader": "Política de privacidad actualizada",
"ProgressBar.progressText": "{percent}%",
"ProgressModal.defaultErrorText": "Último intento de publicación fallido",
+ "ProgressModal.draftHeader": "Guardando borrador…",
"ProgressModal.lastPublished": "Publicado {last_published}",
"ProgressModal.publishHeader": "Publicando el canal",
"ProgressModal.syncError": "Último intento de sincronización fallido",
@@ -1346,6 +1469,19 @@
"RelatedResourcesTab.showPreviewBtnLabel": "Mostrar",
"RelatedResourcesTab.tooManyNextStepsWarning": "Limita el número de pasos siguientes para crear una experiencia de aprendizaje más guiada",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "Limita el número de pasos anteriores para crear una experiencia de aprendizaje más guiada",
+ "RemoveChannelFromListModal.cancel": "Cancelar",
+ "RemoveChannelFromListModal.channelRemovedSnackbar": "Canal eliminado",
+ "RemoveChannelFromListModal.removeBtn": "Eliminar",
+ "RemoveChannelFromListModal.removePrompt": "Tiene permisos de solo lectura para este canal. Confirmar que quiere eliminarlo de su lista de canales.",
+ "RemoveChannelFromListModal.removeTitle": "Eliminar de la lista de canales",
+ "RemoveChannelModal.cancel": "Cancelar",
+ "RemoveChannelModal.deleteChannel": "Eliminar canal",
+ "RemoveChannelModal.deleteChannelWithCLWarning": "Este canal se ha compartido en la Biblioteca comunitaria. Si se elimina aquí no verá eliminado de la Biblioteca comunitaria - todavía puede ser aprobado o seguir estando disponible allí.",
+ "RemoveChannelModal.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.",
+ "RemoveChannelModal.deleteTitle": "Eliminar este canal",
+ "RemoveChannelModal.removeBtn": "Eliminar",
+ "RemoveChannelModal.removePrompt": "Tiene permisos de solo lectura para este canal. Confirmar que quiere eliminarlo de su lista de canales.",
+ "RemoveChannelModal.removeTitle": "Eliminar de la lista de canales",
"ReportErrorModal.closeAction": "Cerrar",
"ReportErrorModal.emailDescription": "Contactar el equipo de soporte con los datos del error y haremos lo posible para ayudar.",
"ReportErrorModal.emailPrompt": "Enviar un correo electrónico al equipo técnico",
@@ -1427,7 +1563,7 @@
"ResourcePanel.description": "Descripción",
"ResourcePanel.details": "Detalles",
"ResourcePanel.fileSize": "Tamaño",
- "ResourcePanel.files": "Ficheros",
+ "ResourcePanel.files": "Archivos",
"ResourcePanel.incompleteQuestionError": "{count, plural, one {# pregunta incompleta} other {# preguntas incompletas}}",
"ResourcePanel.language": "Idioma",
"ResourcePanel.license": "Licencia",
@@ -1435,7 +1571,7 @@
"ResourcePanel.noCompletionCriteriaError": "Los criterios de finalización son obligatorios",
"ResourcePanel.noCopyrightHolderError": "Hay que especificar el titular de los derechos de autor",
"ResourcePanel.noDurationError": "Este campo es obligatorio",
- "ResourcePanel.noFilesError": "El fichero es requerido",
+ "ResourcePanel.noFilesError": "El archivo es requerido",
"ResourcePanel.noLearningActivityError": "Este campo es obligatorio",
"ResourcePanel.noLicenseDescriptionError": "La descripción de la licencia es obligatoria",
"ResourcePanel.noLicenseError": "Se requiere licencia",
@@ -1485,7 +1621,7 @@
"SearchFilters.channelTypeLabel": "Tipo de canal",
"SearchFilters.channelsHeader": "Canales",
"SearchFilters.coachContentLabel": "Mostrar recursos para tutores",
- "SearchFilters.filtersHeader": "Opciones de filtro",
+ "SearchFilters.filtersHeader": "Opciones de filtro ",
"SearchFilters.hideTopicsLabel": "Ocultar carpetas",
"SearchFilters.kindLabel": "Formato",
"SearchFilters.licensesLabel": "Licencia",
@@ -1533,8 +1669,11 @@
"SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Demasiado básico para el nivel de conocimiento de los estudiantes que estoy buscando",
"SearchRecommendationsStrings.tooBasicForLearnersLabel": "Demasiado básico para el nivel de conocimiento de los estudiantes que estoy buscando",
"SearchRecommendationsStrings.tryAgainLink": "Intenta de nuevo",
+ "SearchRecommendationsStrings.trySearchRecommendationsHeader": "¡Prueba la nueva opción de \"Recomendaciones\"!",
+ "SearchRecommendationsStrings.trySearchRecommendationsText": "Basándonos en el título y la descripción de la carpeta en la que está trabajando, le sugeriremos recursos relevantes de la Biblioteca Kolibri. Seleccione 'Importar de los canales' en cualquier carpeta de sus canales para ver las recomendaciones.",
"SearchRecommendationsStrings.undoAction": "Deshacer",
"SearchRecommendationsStrings.viewMoreLink": "Ver más",
+ "SearchRecommendationsStrings.viewRecommendationsButton": "Ver recomendaciones",
"SearchResultsList.failedToLoad": "Error al cargar los resultados de búsqueda",
"SearchResultsList.resultsPerPageLabel": "Resultados por página",
"SearchResultsList.saveSearchAction": "Guardar búsqueda",
@@ -1544,7 +1683,7 @@
"SettingsIndex.settingsTitle": "Configuración",
"SettingsIndex.storageLabel": "Almacenamiento",
"SettingsIndex.usingStudioLabel": "Acerca de Studio",
- "StagingTreePage.backToViewing": "Volver a navegar",
+ "StagingTreePage.backToViewing": "Volver a la navegación",
"StagingTreePage.cancelDeployBtn": "Cancelar",
"StagingTreePage.cancelPublishDraftBtn": "Cancelar",
"StagingTreePage.channelDeployed": "El canal ha sido desplegado",
@@ -1588,10 +1727,121 @@
"Storage.spaceUsedOfMax": "{qty} de {max}",
"Storage.storagePercentageUsed": "{qty}% de almacenamiento usado",
"Storage.videoFiles": "Los ficheros de vídeo deben ser comprimidos para maximizar el espacio de almacenamiento y asegurar una distribución y reproducción sin conexión. Una vez comprimidos, el almacenamiento total requerido puede ser inferior a lo que originalmente estimaba.",
+ "StudioChannelCard.channelLanguageNotSetIndicator": "Idioma no establecido",
+ "StudioChannelCard.details": "Detalles",
+ "StudioChannelCard.lastPublished": "Publicado {last_published}",
+ "StudioChannelCard.lastUpdated": "Actualizado {updated}",
+ "StudioChannelCard.multipleCountries": "Múltiples países",
+ "StudioChannelCard.resourceCount": "{count, plural,\n =1 {# recurso}\n other {# recursos}}",
+ "StudioChannelCard.selectChannel": "Seleccionar {name}",
+ "StudioChannelCard.unpublishedText": "No publicado",
+ "StudioChannelsPage.invitations": "Tiene {count, plural, one {}\n =1 {# invitación}\n other {# invitaciones}}",
+ "StudioChannelsPage.noChannelsFound": "No se han encontrado canales",
+ "StudioCollectionsTable.aboutChannelSets": "Acerca de las colecciones",
+ "StudioCollectionsTable.aboutChannelSetsLink": "Más información sobre colecciones",
+ "StudioCollectionsTable.addChannelSetTitle": "Nueva colección",
+ "StudioCollectionsTable.cancel": "Cancelar",
+ "StudioCollectionsTable.cancelButtonLabel": "Cerrar",
+ "StudioCollectionsTable.channelNumber": "Número de canales",
+ "StudioCollectionsTable.channelSetsDescriptionText": "Una colección contiene múltiples canales de Kolibri Studio que pueden ser importados a Kolibri con solo el token de la colección.",
+ "StudioCollectionsTable.channelSetsDisclaimer": "Se necesita Kolibri con versión 0.12.0 o superior para importar colecciones de canales",
+ "StudioCollectionsTable.channelSetsInstructionsText": "Puede hacer una colección seleccionando los canales que desea importar juntos.",
+ "StudioCollectionsTable.collectionDeleted": "Colección eliminada",
+ "StudioCollectionsTable.copiedTokenId": "Token copiado",
+ "StudioCollectionsTable.copyFailed": "Hubo un fallo al copiar",
+ "StudioCollectionsTable.copyToken": "Copiar token",
+ "StudioCollectionsTable.delete": "Eliminar colección",
+ "StudioCollectionsTable.deleteChannelSetText": "Por favor, confirme que desea eliminar esta colección.",
+ "StudioCollectionsTable.deleteChannelSetTitle": "Eliminar colección",
+ "StudioCollectionsTable.deleteError": "Error al eliminar la colección",
+ "StudioCollectionsTable.edit": "Editar colección",
+ "StudioCollectionsTable.noChannelSetsFound": "Puede juntar varios canales para crear una colección. La colección completa puede ser importada a Kolibri a la vez usando un token de colección.",
+ "StudioCollectionsTable.options": "Opciones",
+ "StudioCollectionsTable.pageTitle": "Colecciones",
+ "StudioCollectionsTable.saving": "Guardando",
+ "StudioCollectionsTable.tableCaption": "Lista de colecciones",
+ "StudioCollectionsTable.title": "Nombre de la colección",
+ "StudioCollectionsTable.token": "Token ID",
+ "StudioCopyToken.copiedTokenId": "Token copiado",
+ "StudioCopyToken.copyFailed": "Hubo un fallo al copiar",
+ "StudioCopyToken.token": "Token",
+ "StudioCopyToken.tooltipText": "Copiar token para importar el canal en Kolibri",
+ "StudioDetailsPanel.AVERAGE": "Medio",
+ "StudioDetailsPanel.LARGE": "Grande",
+ "StudioDetailsPanel.SMALL": "Pequeño",
+ "StudioDetailsPanel.VERY_LARGE": "Muy grande",
+ "StudioDetailsPanel.VERY_SMALL": "Muy pequeño",
+ "StudioDetailsPanel.aggregatorToolTip": "Sitio web o la organización que aloja la colección de contenido, pero no necesariamente el creador o titular de derechos de autor",
+ "StudioDetailsPanel.aggregatorsLabel": "Agregadores",
+ "StudioDetailsPanel.assessmentsIncludedText": "Evaluaciones",
+ "StudioDetailsPanel.authorToolTip": "Persona u organización que ha creado este contenido",
+ "StudioDetailsPanel.authorsLabel": "Autores",
+ "StudioDetailsPanel.categoriesHeading": "Categorías",
+ "StudioDetailsPanel.coachDescription": "Los recursos para tutores son visibles solo a los tutores en Kolibri",
+ "StudioDetailsPanel.coachHeading": "Recursos para tutores",
+ "StudioDetailsPanel.containsContentHeading": "Contiene contenidos de",
+ "StudioDetailsPanel.containsHeading": "Contiene",
+ "StudioDetailsPanel.copyrightHoldersLabel": "Titulares de derechos de autor",
+ "StudioDetailsPanel.creationHeading": "Creado el",
+ "StudioDetailsPanel.currentVersionHeading": "Versión publicada",
+ "StudioDetailsPanel.languagesHeading": "Idiomas",
+ "StudioDetailsPanel.levelsHeading": "Niveles",
+ "StudioDetailsPanel.licensesLabel": "Licencias",
+ "StudioDetailsPanel.primaryLanguageHeading": "Idioma principal",
+ "StudioDetailsPanel.providerToolTip": "Organización que ha encargado o está distribuyendo el contenido",
+ "StudioDetailsPanel.providersLabel": "Proveedores",
+ "StudioDetailsPanel.publishedHeading": "Publicado el",
+ "StudioDetailsPanel.resourceHeading": "El total de recursos",
+ "StudioDetailsPanel.sampleFromChannelHeading": "Contenido de ejemplo de este canal",
+ "StudioDetailsPanel.sizeHeading": "Tamaño del canal",
+ "StudioDetailsPanel.sizeText": "{text} ({size})",
+ "StudioDetailsPanel.subtitlesHeading": "Subtítulos",
+ "StudioDetailsPanel.tagsHeading": "Etiquetas comunes",
+ "StudioDetailsPanel.tokenHeading": "Token del canal",
+ "StudioDetailsPanel.unpublishedText": "No publicado",
+ "StudioImmersiveModal.close": "Cerrar",
+ "StudioMessageLayout.backToLogin": "Continuar a la página de inicio de sesión",
+ "StudioMyChannels.copyToken": "Copiar el token del canal",
+ "StudioMyChannels.deleteChannel": "Eliminar canal",
+ "StudioMyChannels.editChannel": "Editar detalles del canal",
+ "StudioMyChannels.goToWebsite": "Ir a la página web de origen",
+ "StudioMyChannels.moreOptions": "Más opciones",
+ "StudioMyChannels.newChannel": "Nuevo canal",
+ "StudioMyChannels.title": "Mis canales",
+ "StudioMyChannels.viewContent": "Ver canal en Kolibri",
"StudioOfflineAlert.offlineText": "Parece que no tiene conexión. Los cambios se guardarán una vez que la conexión se haya restablecido.",
"StudioOfflineAlert.onlineText": "Vuelve a estar en línea.",
+ "StudioStarredChannels.copyToken": "Copiar el token del canal",
+ "StudioStarredChannels.deleteChannel": "Eliminar canal",
+ "StudioStarredChannels.editChannel": "Editar detalles del canal",
+ "StudioStarredChannels.goToWebsite": "Ir a la página web de origen",
+ "StudioStarredChannels.moreOptions": "Más opciones",
+ "StudioStarredChannels.removeChannel": "Eliminar canal",
+ "StudioStarredChannels.title": "Canales favoritos",
+ "StudioStarredChannels.viewContent": "Ver canal en Kolibri",
"StudioTree.missingTitle": "Falta el título",
"StudioTree.optionsTooltip": "Opciones",
+ "StudioViewOnlyChannels.copyToken": "Copiar el token del canal",
+ "StudioViewOnlyChannels.goToWebsite": "Ir a la página web de origen",
+ "StudioViewOnlyChannels.moreOptions": "Más opciones",
+ "StudioViewOnlyChannels.removeChannel": "Eliminar canal",
+ "StudioViewOnlyChannels.title": "Solo modo de lectura",
+ "StudioViewOnlyChannels.viewContent": "Ver canal en Kolibri",
+ "SubscriptionCard.annualPrice": "${price, number}/año",
+ "SubscriptionCard.cancelNotice": "La suscripción expirará el {date, date, medium}. El almacenamiento se eliminará después de esa fecha.",
+ "SubscriptionCard.dismiss": "Descartar",
+ "SubscriptionCard.genericError": "Hubo un problema al conectar con el proveedor de pagos. Por favor, inténtelo de nuevo.",
+ "SubscriptionCard.instantUpgrade": "Aumentar el almacenamiento (inmediato)",
+ "SubscriptionCard.manageSubscription": "Gestionar suscripción",
+ "SubscriptionCard.renewalNotice": "La suscripción se renovará automáticamente el {date, date, medium}.",
+ "SubscriptionCard.storageAmount": "Almacenamiento (GB)",
+ "SubscriptionCard.storageIncluded": "{size} incluidos con su suscripción",
+ "SubscriptionCard.storageRange": "Introduzca un valor entre 1 y 50",
+ "SubscriptionCard.subscriptionActive": "Suscripción de almacenamiento activa",
+ "SubscriptionCard.subscriptionCanceling": "Suscripción cancelada",
+ "SubscriptionCard.upgradeDescription": "Comprar almacenamiento adicional a $15/GB por año.",
+ "SubscriptionCard.upgradeNow": "Actualizar ahora",
+ "SubscriptionCard.upgradeSuccess": "Almacenamiento aumentado a {size}",
"SubtitlesList.acceptedFormatsTooltip": "Formatos soportados: {extensions}",
"SubtitlesList.addSubtitleText": "Añadir subtítulos",
"SubtitlesList.subtitlesHeader": "Subtítulos",
@@ -1608,8 +1858,8 @@
"SyncResourcesModal.syncButtonLabel": "Sincronizar",
"SyncResourcesModal.syncExercisesExplainer": "Actualizar preguntas, respuestas y sugerencias en ejercicios y cuestionarios",
"SyncResourcesModal.syncExercisesTitle": "Detalles de los ejercicios",
- "SyncResourcesModal.syncFilesExplainer": "Actualizar todos los ficheros, incluyendo miniaturas y subtítulos",
- "SyncResourcesModal.syncFilesTitle": "Ficheros",
+ "SyncResourcesModal.syncFilesExplainer": "Actualizar todos los archivos, incluyendo miniaturas y subtítulos",
+ "SyncResourcesModal.syncFilesTitle": "Archivos",
"SyncResourcesModal.syncModalExplainer": "Sincronizar recursos en Kolibri Studio actualiza los recursos copiados o importados en este canal con cualquier cambio en los ficheros de recursos originales.",
"SyncResourcesModal.syncModalSelectAttributes": "Seleccionar lo que se va a sincronizar:",
"SyncResourcesModal.syncModalTitle": "Sincronizar recursos",
@@ -1653,11 +1903,11 @@
"TermsOfServiceModal.communicationsP1": "Para propósitos contractuales, usted (1) da su consentimiento para recibir comunicaciones de nosotros en forma electrónica a través de la dirección de correo electrónico que ha regristrado o a través del Servicio; y (2) aceptar que todos los Términos de Servicio, acuerdos, avisos, revelaciones, y otras comunicaciones que le proporcionamos electrónicamente satisfacen cualquier requisito legal que dichas comunicaciones satisfagan si estuvieran en papel. Esta sección no afecta a sus derechos no renunciables.",
"TermsOfServiceModal.communityStandardsHeader": "Normas de la comunidad",
"TermsOfServiceModal.communityStandardsLink": "Aprende más sobre los estándares comunitarios de Studio",
- "TermsOfServiceModal.communityStandardsP1": "Para obtener más información sobre el uso previsto del Servicio y las normas en torno a los Contenidos, consulte nuestra página de Normas Comunitarias.",
+ "TermsOfServiceModal.communityStandardsP1": "Para obtener más información sobre el uso previsto del Servicio y las normas en torno a los Contenidos, consulte nuestra página de Normas Comunitarias .",
"TermsOfServiceModal.definitionsHeader": "Definiciones",
"TermsOfServiceModal.definitionsP1": "Estos son los Términos de la aplicación web alojados en https://studio.learningequality.org/, junto con cualquier API u otras interfaces que proporciona (el \"Servicio\"), controlada y operada por Learning Equality (\"Learning Equality\", \"nosotros\", \"nosotros\" y \"nuestro\"). Estamos registrados como una organización sin fines de lucro en California, Estados Unidos bajo EIN 46-2676188, y tener nuestra sede social en 9700 Gilman Dr, PMB 323, La Jolla, CA 92093.",
"TermsOfServiceModal.definitionsP2": "Estos Términos describen nuestros compromisos con usted, y sus derechos y responsabilidades al usar el Servicio. Si viola cualquiera de estos Términos, su derecho de acceso y uso del Servicio se rescindirá. Por favor, léalos detenidamente y comuníquenos si tiene alguna pregunta.",
- "TermsOfServiceModal.definitionsP3": "\"Contenido\" se refiere a ficheros multimedia (como vídeos, archivos de audio, contenido HTML5, u otros materiales) que estén alojados en el Servicio, junto con sus metadatos descriptivos asociados.",
+ "TermsOfServiceModal.definitionsP3": "\"Content\" refers to media files (such as videos, audio files, HTML5 content, or other materials) that are hosted on the Service, along with their associated descriptive metadata.",
"TermsOfServiceModal.definitionsP4": "A lo largo de estos términos, \"usted\" se aplica tanto a individuos como a entidades que acceden o utilicen el Servicio. Si usted es una persona que utiliza el Servicio en nombre de una entidad, usted representa y garantiza que usted tiene la autoridad para vincular a esa entidad al Acuerdo y eso mediante nuestro Servicio, acepta el Acuerdo en nombre de dicha entidad.",
"TermsOfServiceModal.disclaimerP1": "Antes de utilizar este sitio web, debe leer la siguiente información importante relacionada con el mismo. Estos Términos de Servicio (\"Términos\") rigen su uso de este sitio web y forman un acuerdo legalmente vinculante entre usted y nosotros con respecto a su uso de nuestro sitio web.",
"TermsOfServiceModal.disclaimerP2": "Si, por alguna razón, no puede o no desea aceptar todos estos términos, por favor interrumpa inmediatamente el uso del servicio.",
@@ -1672,13 +1922,13 @@
"TermsOfServiceModal.jurisdictionHeader": "Jurisdicción y Ley Aplicable",
"TermsOfServiceModal.jurisdictionP1": "Excepto en la medida en que cualquier ley aplicable establece lo contrario, el Acuerdo y cualquier acceso o uso del Servicio se regirán por las leyes del estado de California, E.U.A., excluyendo su conflicto de disposiciones legales. El lugar adecuado para cualquier controversia que surja o esté relacionado con el Acuerdo y cualquier acceso o uso del Servicio será los tribunales estatales y federales ubicados en el Condado de San Diego, California.",
"TermsOfServiceModal.liabilityHeader": "Limitación de responsabilidad",
- "TermsOfServiceModal.liabilityP1": "En la medida en que legalmente lo permita la ley aplicable, Learning Equality no será responsable de ninguna pérdida o daño para usted, sus clientes o terceros causados por la falta de funcionamiento del sitio web. En ningún caso Learning Equality será responsable de cualquier daño especial, consecuente, incidental o indirecto (incluyendo, sin limitación, los resultantes de la pérdida de beneficios, coste de bienes o servicios sustitutivos pérdida de datos o interrupción del negocio) en relación con el uso del sitio web o del Servicio en relación con cualquier otra reclamación que surja de estas Condiciones de Servicio. La responsabilidad agregada de Learning Equality surge de o relacionada con estos Términos y el Servicio, independientemente de la forma de acción o reclamación (contrato, o de otro tipo) e incluso si se le ha avisado de la posibilidad de tales daños no excederá de la cantidad pagada por usted durante el periodo de doce (12) meses previo a la causa de la acción. Nada de lo dispuesto en estas Condiciones limitará o excluirá la responsabilidad de Learning Equality por negligencia grave o por muerte o lesiones personales. La ley aplicable puede no permitir la exclusión o limitación de daños incidentales o consecuentes, por lo que la limitación o exclusión anterior puede no aplicarse a usted.",
+ "TermsOfServiceModal.liabilityP1": "To the extent legally permitted under the applicable law, Learning Equality shall not be responsible for any loss or damage to you, your customers or third parties caused by failure of the website to function. In no event will Learning Equality be liable for any special, consequential, incidental, or indirect damages (including, without limitation, those resulting from lost profits, cost of substitute goods or services, lost data or business interruption) in connection with the use of the website or Service of in connection with any other claim arising from these Terms of Service. The aggregate liability of Learning Equality arising from or relating to these Terms and the Service, regardless of the form of action or claim (contract, tort or otherwise) and even if you have been advised of the possibility of such damages shall not exceed the amount paid by you during the twelve (12) month period prior to the cause of action. Nothing in these Terms shall limit or exclude Learning Equality liability for gross negligence or for death or personal injury. Applicable law may not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you.",
"TermsOfServiceModal.licensingHeader": "Licencias y derechos de autor",
"TermsOfServiceModal.licensingList1Item1": "La propiedad de derechos de autor sobre el contenido es conservada por el titular original del copyright y debe ser indicada, y la información de la licencia debe ser marcada para reflejar con precisión las intenciones del titular de los derechos de autor acerca de la distribución y el uso de dicho Contenido.",
"TermsOfServiceModal.licensingList1Item2": "Si no eres el titular de los derechos de autor, debes tener los derechos de distribuir el contenido subido, ya sea a través de un permiso escrito explícito del titular del derecho de autor, o según lo permitido por los términos de la licencia bajo la cual se ha liberado el Contenido.",
"TermsOfServiceModal.licensingList1Item3": "Si usted es el titular de los derechos de autor del contenido subido, entonces marcando el contenido que carga con una licencia en particular, usted está de acuerdo en que el Contenido se distribuya y se utilice bajo los términos de esa licencia en autoridad.",
- "TermsOfServiceModal.licensingList2Item1": "Metadatos descriptivos: Esto incluye metadatos principales asociados con una sola pieza de contenido, por ejemplo, títulos, descripciones, así como otros elementos que constituyen una parte definitiva del Contenido, independientemente del sistema en el que aparezca. Estos elementos de metadatos estarán bajo los mismos derechos de autor y licencias que el propio Contenido.",
- "TermsOfServiceModal.licensingList2Item2": "Metadatos de organización: Definen cómo se puede utilizar un elemento de contenido, ayudan su descubrimiento, y lo coloca dentro de una estructura más amplia de las relaciones en el Servicio, por ejemplo, etiquetas, curación de carpetas (incluyendo los títulos de estas carpetas), y otros elementos pertenecientes a la visualización y ordenación de Contenido en el propio sistema. Al utilizar el Servicio, usted acepta que el trabajo que realiza para generar elementos de metadatos organizacionales se publique como el dominio público, y puede ser puestos a disposición de otros para su uso, sin ninguna reclamación sobre derechos de autor o licencias restringidas. También podemos compartir, aprovechar y distribuir estos metadatos organizacionales. Esto se establece para que podamos beneficiar a otros y mejorar el impacto de nuestras plataformas.",
+ "TermsOfServiceModal.licensingList2Item1": "Metadatos descriptivos: Esto incluye metadatos principales asociados con una sola pieza de contenido, por ejemplo, títulos, , así como otros elementos que constituyen una parte definitiva del Contenido, independientemente del sistema en el que aparezca. Estos elementos de metadatos estarán bajo los mismos derechos de autor y licencias que el propio Contenido.",
+ "TermsOfServiceModal.licensingList2Item2": "Organizational metadata: This defines how a piece of content may be used, aids with discovery, and places it within some broader structure of relations on the Service, for example, tags, curation into folders (including the titles of those folders), and other elements pertaining to the display and ordering of Content on the system itself. By using the Service, you agree that work you do to generate organizational metadata elements are released into the Public Domain, and may be made available for others to use, without any claim to copyright or restricted licensing. We may also share, leverage and distribute this organizational metadata. This is so that we can benefit others and improve the impact of our platforms.",
"TermsOfServiceModal.licensingP1": "El servicio le permite cargar y distribuir contenidos. Cuando lo haga, se aplican los siguientes términos:",
"TermsOfServiceModal.licensingP2": "Seguimos una política de hacer que el contenido, incluyendo sus metadatos asociados, sea lo más abierto posible mientras seguimos las leyes de derechos de autor apropiadas. Con esto en mente, distinguimos entre el:",
"TermsOfServiceModal.miscellaneousHeader": "Miscellaneous",
@@ -1707,7 +1957,7 @@
"TermsOfServiceModal.userContentP2": "Tampoco hemos revisado, ni podemos revisar, todo el material disponible a través de los sitios web y páginas web que enlazan o están enlazadas desde el Servicio. Por ejemplo:",
"TermsOfServiceModal.userContentP3": "Nos reservamos el derecho de eliminar cualquier Contenido que viole nuestros Términos o por cualquier otra razón.",
"TermsOfServiceModal.userContentP4": "Tenga en cuenta que no podemos:",
- "TermsOfServiceModal.warrantyHeader": "Exención de garantías",
+ "TermsOfServiceModal.warrantyHeader": "Exención de garantías ",
"TermsOfServiceModal.warrantyHeaderP1": "Usted reconoce que el sitio web y el Servicio se proporcionan \"tal cual\" y \"según disponibilidad\", con todos los defectos y sin garantía de ningún tipo, y aquí declinamos todas las garantías y condiciones con respecto al sitio web y al Servicio, ya sea expresamente, implícito o estatutario, incluyendo, pero no limitado a, cualquier garantía implícita y/o condiciones de comerciabilidad, de calidad satisfactoria, de aptitud para un fin particular, de exactitud, y de no violación de los derechos de terceros. Cualquier uso del Servicio y del sitio web es bajo su propio riesgo. Algunas jurisdicciones no permiten la exclusión de garantías implícitas, por lo que es posible que las limitaciones anteriores no se apliquen a usted.",
"TermsOfServiceModal.yourPrivacyHeader": "Su privacidad",
"TermsOfServiceModal.yourPrivacyLink": "Más información sobre la política de privacidad de Studio",
@@ -1715,79 +1965,98 @@
"TextArea.fieldRequiredMessage": "Este campo es obligatorio",
"TextField.fieldRequiredMessage": "Este campo es obligatorio",
"Thumbnail.thumbnail": "{title} miniatura",
+ "ThumbnailGenerator.closeButtonLabel": "Aceptar",
"ThumbnailGenerator.generatedDefaultFilename": "Miniatura generada",
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "No se puede generar la miniatura",
"ThumbnailGenerator.thumbnailGenerationFailedText": "Hubo un problema al generar una miniatura",
- "TipTapEditorStrings.addLink": "Add link",
- "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
- "TipTapEditorStrings.altTextLabel": "Alt text (Optional)",
- "TipTapEditorStrings.altTextPlaceholder": "Describe your image...",
- "TipTapEditorStrings.bold": "Strong",
- "TipTapEditorStrings.bulletList": "Bullet list",
+ "TipTapEditorStrings.TipTapEditorLabel": "editor de texto - Pulse Enter para empezar a editar",
+ "TipTapEditorStrings.TipTapViewerLabel": "contenido del editor de texto",
+ "TipTapEditorStrings.addLink": "Añadir enlace",
+ "TipTapEditorStrings.alignLeft": "Alinear a la izquierda",
+ "TipTapEditorStrings.alignRight": "Alinear a la derecha",
+ "TipTapEditorStrings.altTextDescription": "La descripción textual (Alt text - texto alternativo) es necesario para permitir que los estudiantes con discapacidad visual respondan a las preguntas, y también se muestra cuando la imagen no se carga",
+ "TipTapEditorStrings.altTextLabel": "Descripción textual (opcional)",
+ "TipTapEditorStrings.altTextPlaceholder": "Describir la imagen...",
+ "TipTapEditorStrings.bold": "Negrita",
+ "TipTapEditorStrings.bulletList": "Lista sin ordenar",
"TipTapEditorStrings.cancel": "Cancelar",
- "TipTapEditorStrings.cancelLoading": "Cancel loading",
- "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.",
+ "TipTapEditorStrings.cancelLoading": "Cancelar la carga",
+ "TipTapEditorStrings.clearFormatting": "Borrar formato",
+ "TipTapEditorStrings.clipboardAccessFailed": "Error al acceder al portapapeles. Intente copiarlo de nuevo.",
"TipTapEditorStrings.close": "Cerrar",
- "TipTapEditorStrings.closeModal": "Close modal",
- "TipTapEditorStrings.codeBlock": "Code block",
+ "TipTapEditorStrings.closeModal": "Cerrar ventana",
+ "TipTapEditorStrings.codeBlock": "Bloque de código",
+ "TipTapEditorStrings.collapseFormattingBar": "Colapsar barra de formato",
"TipTapEditorStrings.copy": "Copiar",
- "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions",
- "TipTapEditorStrings.copyLink": "Copy link",
+ "TipTapEditorStrings.copyAndPasteActions": "Copiar y pegar",
+ "TipTapEditorStrings.copyLink": "Copiar enlace",
+ "TipTapEditorStrings.decreaseFormatSize": "Reducir tamaño",
"TipTapEditorStrings.defaultImageName": "Imagen",
"TipTapEditorStrings.edit": "Editar",
- "TipTapEditorStrings.editImage": "Edit image",
- "TipTapEditorStrings.editLink": "Edit link",
- "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.",
+ "TipTapEditorStrings.editImage": "Editar imagen",
+ "TipTapEditorStrings.editLink": "Editar enlace",
+ "TipTapEditorStrings.editorControls": "Controles del editor",
+ "TipTapEditorStrings.errorUploadingImage": "Error al subir la imagen",
+ "TipTapEditorStrings.expandFormattingBar": "Expandir barra de formato",
+ "TipTapEditorStrings.failedToProcessImage": "Error al procesar el archivo de imagen.",
"TipTapEditorStrings.fileSizeUnit": "MB.",
- "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ",
- "TipTapEditorStrings.formatHeader1": "Header 1",
- "TipTapEditorStrings.formatHeader2": "Header 2",
- "TipTapEditorStrings.formatHeader3": "Header 3",
+ "TipTapEditorStrings.fileTooLarge": "El archivo es demasiado grande. El tamaño máximo es ",
+ "TipTapEditorStrings.formatHeader1": "Encabezado nivel 1",
+ "TipTapEditorStrings.formatHeader2": "Encabezado nivel 2",
+ "TipTapEditorStrings.formatHeader3": "Encabezado nivel 3",
"TipTapEditorStrings.formatNormal": "Normal",
- "TipTapEditorStrings.formatOptions": "Format options",
+ "TipTapEditorStrings.formatOptions": "Opciones de formato",
+ "TipTapEditorStrings.formatSize": "Tamaño",
"TipTapEditorStrings.formatSmall": "Pequeño",
- "TipTapEditorStrings.formulasMenuTitle": "Special Characters",
- "TipTapEditorStrings.goToLink": "Go to link",
- "TipTapEditorStrings.historyActions": "History actions",
- "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually",
- "TipTapEditorStrings.imagePreview": "Image preview",
+ "TipTapEditorStrings.formulasMenuTitle": "Caracteres especiales",
+ "TipTapEditorStrings.goToLink": "Ir al enlace",
+ "TipTapEditorStrings.historyActions": "Historial de acciones",
+ "TipTapEditorStrings.imageDropZoneText": "Arrastrar y soltar una imagen aquí, o subirla manualmente",
+ "TipTapEditorStrings.imagePreview": "Vista previa de imagen",
+ "TipTapEditorStrings.increaseFormatSize": "Aumentar tamaño",
"TipTapEditorStrings.insert": "Insertar",
- "TipTapEditorStrings.insertImage": "Insert image",
- "TipTapEditorStrings.insertLink": "Insert link",
- "TipTapEditorStrings.insertTools": "Insert tools",
- "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ",
+ "TipTapEditorStrings.insertContent": "Insertar",
+ "TipTapEditorStrings.insertContentMenu": "Menú insertar",
+ "TipTapEditorStrings.insertContentOption": "Insertar",
+ "TipTapEditorStrings.insertImage": "Insertar imagen",
+ "TipTapEditorStrings.insertLink": "Insertar enlace",
+ "TipTapEditorStrings.insertTools": "Herramientas para insertar",
+ "TipTapEditorStrings.invalidFileType": "Tipo de archivo no válido. Por favor usar: ",
"TipTapEditorStrings.italic": "Cursiva",
- "TipTapEditorStrings.link": "Link",
- "TipTapEditorStrings.linkActions": "Link actions",
- "TipTapEditorStrings.listFormatting": "List formatting",
- "TipTapEditorStrings.mathFormula": "Math formula",
- "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.",
- "TipTapEditorStrings.noFileProvided": "No file provided.",
- "TipTapEditorStrings.numberedList": "Numbered list",
- "TipTapEditorStrings.opensInNewTab": "(opens in new tab)",
- "TipTapEditorStrings.paste": "Paste",
- "TipTapEditorStrings.pasteOptions": "Paste options",
- "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu",
- "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting",
+ "TipTapEditorStrings.link": "Enlace",
+ "TipTapEditorStrings.linkActions": "Acciones de enlaces",
+ "TipTapEditorStrings.listFormatting": "Formato de lista",
+ "TipTapEditorStrings.loadingFormulas": "Cargando editor de matemáticas",
+ "TipTapEditorStrings.mathFormula": "Fórmula matemática",
+ "TipTapEditorStrings.moreButtonText": "Más",
+ "TipTapEditorStrings.multipleFilesDroppedWarning": "Múltiples archivos fueron eliminados. Solo el primer archivo ha sido seleccionado.",
+ "TipTapEditorStrings.noEnoughStorageSpace": "No hay suficiente espacio de almacenamiento disponible. El tamaño del archivo excede el límite de almacenamiento.",
+ "TipTapEditorStrings.noFileProvided": "No se ha proporcionado ningún archivo",
+ "TipTapEditorStrings.numberedList": "Lista numerada",
+ "TipTapEditorStrings.opensInNewTab": "(se abre en una nueva pestaña)",
+ "TipTapEditorStrings.paste": "Pegar",
+ "TipTapEditorStrings.pasteOptions": "Opciones de pegar",
+ "TipTapEditorStrings.pasteOptionsMenu": "Menú de opciones de pegado",
+ "TipTapEditorStrings.pasteWithoutFormatting": "Pegar sin formato",
"TipTapEditorStrings.redo": "Rehacer",
"TipTapEditorStrings.remove": "Eliminar",
- "TipTapEditorStrings.removeImage": "Remove image",
- "TipTapEditorStrings.removeLink": "Remove link",
- "TipTapEditorStrings.replaceFile": "Replace file",
+ "TipTapEditorStrings.removeImage": "Eliminar imagen",
+ "TipTapEditorStrings.removeLink": "Eliminar enlace",
+ "TipTapEditorStrings.replaceFile": "Sustituir fichero",
"TipTapEditorStrings.save": "Guardar",
"TipTapEditorStrings.saveChanges": "Guardar cambios",
- "TipTapEditorStrings.scriptFormatting": "Script formatting",
+ "TipTapEditorStrings.scriptFormatting": "Formato de script",
"TipTapEditorStrings.selectFile": "Seleccionar fichero",
- "TipTapEditorStrings.selectFileToUpload": "Select file to upload",
- "TipTapEditorStrings.strikethrough": "Strikethrough",
+ "TipTapEditorStrings.selectFileToUpload": "Seleccionar archivo a cargar",
+ "TipTapEditorStrings.strikethrough": "Tachado",
"TipTapEditorStrings.subscript": "Subíndice",
"TipTapEditorStrings.superscript": "Superíndice",
- "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp",
- "TipTapEditorStrings.text": "Text",
- "TipTapEditorStrings.textFormatOptions": "Text format options",
- "TipTapEditorStrings.textFormattingOptions": "Text formatting options",
- "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar",
- "TipTapEditorStrings.textStyleFormatting": "Text style formatting",
+ "TipTapEditorStrings.supportedFileTypes": "Tipos de ficheros permitidos: { extensions }",
+ "TipTapEditorStrings.text": "Texto",
+ "TipTapEditorStrings.textFormatOptions": "Opciones de formato de texto",
+ "TipTapEditorStrings.textFormattingOptions": "Opciones de formato de texto",
+ "TipTapEditorStrings.textFormattingToolbar": "Barra de herramientas de formato de texto",
+ "TipTapEditorStrings.textStyleFormatting": "Formato de estilo de texto",
"TipTapEditorStrings.underline": "Subrayar",
"TipTapEditorStrings.undo": "Deshacer",
"TipTapEditorStrings.uploadImage": "Subir imagen",
@@ -1814,25 +2083,26 @@
"TreeView.showSidebar": "Show sidebar",
"TreeView.updatedResourcesReadyForReview": "Los recursos actualizados están listos para su revisión",
"TreeViewBase.apiGenerated": "Generado con la API",
- "TreeViewBase.cancel": "Cancelar",
"TreeViewBase.channelDeletedSnackbar": "Canal eliminado",
"TreeViewBase.channelDetails": "Ver detalles del canal",
"TreeViewBase.deleteChannel": "Eliminar canal",
- "TreeViewBase.deleteChannelButton": "Eliminar canal",
- "TreeViewBase.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.",
- "TreeViewBase.deleteTitle": "Eliminar este canal",
"TreeViewBase.editChannel": "Editar detalles del canal",
"TreeViewBase.emptyChannelTooltip": "No se puede publicar un canal vacío",
"TreeViewBase.getToken": "Obtener el token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {el recurso está incompleto y no se puede publicar} other {los recursos están incompletos y no se pueden publicar}}",
+ "TreeViewBase.inviteCollaborators": "Invitar colaboradores",
"TreeViewBase.noChangesText": "No se encontraron cambios en el canal",
"TreeViewBase.noLanguageSetError": "El idioma del canal es obligatorio",
"TreeViewBase.openTrash": "Abrir papelera",
"TreeViewBase.publishButton": "Publicar",
"TreeViewBase.publishButtonTitle": "Hacer este canal disponible para importar en Kolibri",
"TreeViewBase.shareChannel": "Compartir canal",
+ "TreeViewBase.shareMenuButton": "Compartir",
+ "TreeViewBase.shareToken": "Compartir token",
+ "TreeViewBase.submitToCommunityLibrary": "Enviar a la Biblioteca comunitaria",
"TreeViewBase.syncChannel": "Sincronizar recursos",
"TreeViewBase.viewOnly": "Solo modo de lectura",
+ "Uploader.closeButtonLabel": "Aceptar",
"Uploader.listDelimiter": ", ",
"Uploader.maxFileSizeText": "{count, plural,\n =1 {# fichero no será subido.}\n other {# fichero no serán subidos.}} El tamaño del fichero debe ser inferior a {size}",
"Uploader.noStorageHeader": "No hay suficiente espacio",
@@ -1876,7 +2146,7 @@
"channelEditVue.questionTypeInput": "Entrada numérica",
"channelEditVue.questionTypeMultipleSelection": "Selección múltiple",
"channelEditVue.questionTypePerseus": "Perseus",
- "channelEditVue.questionTypeSingleSelection": "Respuesta única",
+ "channelEditVue.questionTypeSingleSelection": "Selección única",
"channelEditVue.questionTypeTrueFalse": "Verdadero/Falso",
"channelEditVue.selectionCount": "{topicCount, plural, =0 {} one {# carpeta, } other {# carpetas, }}{resourceCount, plural, one {# recurso} other {# recursos}}",
"channelEditVue.true": "Verdadero",
@@ -1906,4 +2176,4 @@
"sharedVue.masteryModelRequired": "Tiene que seleccionar el criterio de dominio",
"sharedVue.shortActivityLteThirty": "El valor debe ser igual o menor que 30",
"sharedVue.titleRequired": "Este campo es obligatorio"
-}
+}
\ No newline at end of file
diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/django.po b/contentcuration/locale/es_ES/LC_MESSAGES/django.po
index 3073c45a4b..76fe11bfea 100644
--- a/contentcuration/locale/es_ES/LC_MESSAGES/django.po
+++ b/contentcuration/locale/es_ES/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-09-03 19:45+0000\n"
-"PO-Revision-Date: 2025-09-12 13:59\n"
+"POT-Creation-Date: 2026-04-16 00:57+0000\n"
+"PO-Revision-Date: 2026-04-20 19:33\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -14,8 +14,8 @@ msgstr ""
"X-Crowdin-Project: kolibri-studio\n"
"X-Crowdin-Project-ID: 286000\n"
"X-Crowdin-Language: es-ES\n"
-"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n"
-"X-Crowdin-File-ID: 4886\n"
+"X-Crowdin-File: /unstable/django.po\n"
+"X-Crowdin-File-ID: 4322\n"
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
#: contentcuration/settings.py:271
@@ -24,27 +24,27 @@ msgstr "Árabe"
#: contentcuration/middleware/db_readonly.py:25
msgid "The site is currently in read-only mode. Please try again later."
-msgstr "La plataforma está actualmente en modo de solo lectura. Por favor, inténtelo más tarde."
+msgstr "El sitio está actualmente en modo de solo lectura. Por favor, inténtelo más tarde."
-#: contentcuration/models.py:342
+#: contentcuration/models.py:356
msgid "Not enough space. Check your storage under Settings page."
msgstr "No hay suficiente espacio. Compruebe el almacenamiento disponible en la página de Configuración."
-#: contentcuration/models.py:374 contentcuration/models.py:386
+#: contentcuration/models.py:440 contentcuration/models.py:452
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "El espacio de almacenamiento es insuficiente. Solicite más espacio en Configuración > Almacenamiento."
-#: contentcuration/models.py:2155
+#: contentcuration/models.py:2531
msgid " (Original)"
msgstr " (Original)"
-#: contentcuration/models.py:3297
+#: contentcuration/models.py:3848
msgid "Created DateTime"
-msgstr "Fecha y hora de creación"
+msgstr ""
-#: contentcuration/models.py:3299
+#: contentcuration/models.py:3850
msgid "Datetime field when the custom_metadata for task was created in UTC"
-msgstr "Campo de fecha cuando el custom_metadata para la tarea fue creado en UTC"
+msgstr ""
#: contentcuration/settings.py:269
msgid "English"
@@ -653,11 +653,11 @@ msgstr "Tipo"
#: contentcuration/utils/csv_writer.py:89
msgid "Filename"
-msgstr "Nombre del archivo"
+msgstr "Nombre del fichero"
#: contentcuration/utils/csv_writer.py:90
msgid "File Size"
-msgstr "Tamaño del archivo"
+msgstr "Tamaño del fichero"
#: contentcuration/utils/csv_writer.py:91
msgid "URL"
@@ -719,7 +719,7 @@ msgstr "Hubo un problema con un servicio externo. Esto significa que no es posib
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here"
msgstr "Estamos encontrando problemas con nuestro centro de datos. Esto significa que puede encontrar problemas de red mientras utiliza Studio. Agradecemos su paciencia mientras se resuelvenn estos problemas. Para comprobar el estado de este servicio visite este enlace"
-#: contentcuration/utils/publish.py:101
+#: contentcuration/utils/publish.py:103
msgid "Kolibri Studio Channel Published"
msgstr "Canal publicado en Kolibri Studio"
@@ -731,14 +731,15 @@ msgstr "Informe de incidencias de Kolibri Studio"
msgid "Kolibri Studio account deleted"
msgstr "Cuenta de Kolibri Studio eliminada"
-#: kolibri_public/views.py:223
+#: kolibri_public/views.py:323
msgid "Resource"
msgstr "Recurso"
-#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94
+#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167
msgid "API version is unavailable"
msgstr "La versión API no está disponible"
-#: kolibri_public/views_v1.py:97
+#: kolibri_public/views_v1.py:170
msgid "No channel matching {} found"
msgstr "Ningún canal con {} encontrado"
+
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/README.md b/contentcuration/locale/fr_FR/LC_MESSAGES/README.md
index 0f82b94d50..014a952e9a 100644
--- a/contentcuration/locale/fr_FR/LC_MESSAGES/README.md
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/README.md
@@ -1 +1 @@
-The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
+The JSON messages files in this folder were generated by kolibri-i18n csvToJSON.js
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json
index 23aa4b3f1b..090fde701f 100644
--- a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json
@@ -67,8 +67,6 @@
"AdministrationAppError.unauthorizedDetails": "Il faut être administrateur de Studio pour voir cette page",
"AdministrationIndex.channelsLabel": "Chaînes",
"AdministrationIndex.usersLabel": "Utilisateurs",
- "Alert.closeButtonLabel": "OK",
- "Alert.dontShowAgain": "Ne plus afficher ce message",
"AnswersEditor.answersLabel": "Réponses",
"AnswersEditor.newAnswerBtnLabel": "Nouvelle réponse",
"AnswersEditor.noAnswersPlaceholder": "La question ne comporte pas d'options de réponse",
@@ -184,26 +182,31 @@
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "Favorites",
"CatalogFilterBar.subtitles": "Sous-titres",
- "CatalogFilters.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux",
- "CatalogFilters.coachLabel": "Ressources pour les éducateurs",
- "CatalogFilters.copyright": "© {year} Learning Equality",
- "CatalogFilters.formatLabel": "Formats",
- "CatalogFilters.frequentlyAskedQuestionsLink": "Foire aux questions",
- "CatalogFilters.includesLabel": "N'afficher que les chaînes avec",
- "CatalogFilters.licenseLabel": "Licences",
- "CatalogFilters.searchLabel": "Mots clés",
- "CatalogFilters.searchText": "Rechercher",
- "CatalogFilters.starredLabel": "Favorites",
- "CatalogFilters.subtitlesLabel": "Légendes ou sous-titres",
+ "CatalogFilterPanelContent.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux",
+ "CatalogFilterPanelContent.coachLabel": "Ressources pour les éducateurs",
+ "CatalogFilterPanelContent.copyright": "© {year} Learning Equality",
+ "CatalogFilterPanelContent.formatLabel": "Formats",
+ "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Foire aux questions",
+ "CatalogFilterPanelContent.includesLabel": "N'afficher que les chaînes avec",
+ "CatalogFilterPanelContent.licenseLabel": "Licences",
+ "CatalogFilterPanelContent.searchLabel": "Mots clés",
+ "CatalogFilterPanelContent.starredLabel": "Favorite",
+ "CatalogFilterPanelContent.subtitlesLabel": "Légendes ou sous-titres",
+ "CatalogFilters.filterLabel": "Filtrer",
"CatalogList.cancelButton": "Annuler",
"CatalogList.channelSelectionCount": "{count, plural, one {}\n =1 {# chaîne sélectionnée}\n other {# chaînes sélectionnées}}",
+ "CatalogList.copyToken": "Copier le jeton de la chaîne",
"CatalogList.downloadButton": "Télécharger",
"CatalogList.downloadCSV": "Télécharger au format CSV",
"CatalogList.downloadPDF": "Télécharger au format PDF",
"CatalogList.downloadingMessage": "Téléchargement en cours",
+ "CatalogList.goToWebsite": "Aller sur le site web source",
+ "CatalogList.moreOptions": "Plus d'options",
"CatalogList.resultsText": "{count, plural, one {}\n =1 {# résultat obtenu}\n other {# résultats obtenus}}",
"CatalogList.selectAll": "Tout sélectionner",
"CatalogList.selectChannels": "Télécharger un résumé des chaînes sélectionnées",
+ "CatalogList.title": "Bibliothèque de contenus",
+ "CatalogList.viewContent": "Afficher la chaîne sur Kolibri",
"CategoryOptions.noCategoryFoundText": "Catégorie introuvable",
"ChangePasswordForm.cancelAction": "Annuler",
"ChangePasswordForm.changePasswordHeader": "Changer de mot de passe",
@@ -224,9 +227,6 @@
"ChannelCatalogFrontPage.languagesHeading": "Langues",
"ChannelCatalogFrontPage.numberOfChannels": "{ num } chaînes",
"ChannelCatalogFrontPage.subtitlesIncludedText": "Légendes ou sous-titres",
- "ChannelDeletedError.backToHomeAction": "Retour à l'accueil",
- "ChannelDeletedError.channelDeletedDetails": "Cette chaîne n'existe pas ou peut avoir été supprimée. Si vous croyez qu'il s'agit d'une erreur, veuillez nous contacter à l'adresse content@learningequality.org.",
- "ChannelDeletedError.channelDeletedHeader": "Chaîne introuvable",
"ChannelDetailsModal.downloadButton": "Télécharger le résumé de la chaîne",
"ChannelDetailsModal.downloadCSV": "Télécharger au format CSV",
"ChannelDetailsModal.downloadPDF": "Télécharger au format PDF",
@@ -263,23 +263,17 @@
"ChannelInvitation.editText": "{sender} vous a invité à modifier {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "Aller sur la chaîne",
"ChannelInvitation.viewText": "{sender} vous a invité à visualiser {channel}",
- "ChannelItem.cancel": "Annuler",
"ChannelItem.channelDeletedSnackbar": "Chaîne supprimée",
"ChannelItem.channelLanguageNotSetIndicator": "Aucune langue définie",
"ChannelItem.channelRemovedSnackbar": "Chaîne supprimée",
"ChannelItem.copyToken": "Copier le jeton de la chaîne",
"ChannelItem.deleteChannel": "Supprimer la chaîne",
- "ChannelItem.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.",
- "ChannelItem.deleteTitle": "Supprimer cette chaîne",
"ChannelItem.details": "Détails",
"ChannelItem.editChannel": "Modifier les détails de la chaîne",
"ChannelItem.goToWebsite": "Aller sur le site web source",
"ChannelItem.lastPublished": "Publié le {last_published}",
"ChannelItem.lastUpdated": "Mis à jour {updated}",
- "ChannelItem.removeBtn": "Supprimer",
- "ChannelItem.removeChannel": "Retirer de la liste des chaînes",
- "ChannelItem.removePrompt": "Vous disposez d’un accès en lecture seule à cette chaîne. Confirmez que vous souhaitez la retirer de votre liste de chaînes.",
- "ChannelItem.removeTitle": "Retirer de la liste de chaînes",
+ "ChannelItem.removeChannel": "Supprimer la chaîne",
"ChannelItem.resourceCount": "{count, plural, one {}\n =1 {# ressource}\n other {# ressources}}",
"ChannelItem.unpublishedText": "Non publié",
"ChannelItem.versionText": "Version {version}",
@@ -292,7 +286,6 @@
"ChannelListIndex.catalog": "Bibliothèque de contenus",
"ChannelListIndex.channelSets": "Recueils",
"ChannelListIndex.frequentlyAskedQuestions": "Foire aux questions",
- "ChannelListIndex.invitations": "Vous avez {count, plural, one {}\n =1 {# invitation}\n other {# invitations}}",
"ChannelListIndex.libraryTitle": "Catalogue de la bibliothèque de contenus Kolibri",
"ChannelModal.APIText": "Les chaînes générées automatiquement ne sont pas modifiables.",
"ChannelModal.changesSaved": "Modifications enregistrées",
@@ -316,25 +309,6 @@
"ChannelNotFoundError.channelNotFoundHeader": "Chaîne introuvable",
"ChannelSelectionList.noChannelsFound": "Aucune chaîne trouvée",
"ChannelSelectionList.searchText": "Rechercher une chaîne",
- "ChannelSetItem.cancel": "Annuler",
- "ChannelSetItem.delete": "Supprimer le recueil",
- "ChannelSetItem.deleteChannelSetText": "Êtes-vous sûr de vouloir supprimer ce recueil ?",
- "ChannelSetItem.deleteChannelSetTitle": "Supprimer le recueil ",
- "ChannelSetItem.edit": "Modifier le recueil",
- "ChannelSetItem.options": "Options",
- "ChannelSetItem.saving": "Enregistrement en cours",
- "ChannelSetList.aboutChannelSets": "À propos des recueils",
- "ChannelSetList.aboutChannelSetsLink": "En savoir plus sur les recueils",
- "ChannelSetList.addChannelSetTitle": "Nouveau recueil",
- "ChannelSetList.cancelButtonLabel": "Fermer",
- "ChannelSetList.channelNumber": "Nombre de chaînes",
- "ChannelSetList.channelSetsDescriptionText": "Un recueil contient plusieurs chaînes de Kolibri Studio pouvant être importées en une seule fois dans Kolibri avec un seul jeton de recueil.",
- "ChannelSetList.channelSetsDisclaimer": "Seule la version 0.12.0 ou supérieure de Kolibri permet d'importer des recueils de chaînes.",
- "ChannelSetList.channelSetsInstructionsText": "Vous pouvez créer un recueil en sélectionnant les chaînes que vous souhaitez importer ensemble.",
- "ChannelSetList.noChannelSetsFound": "Il est possible de regrouper plusieurs chaînes pour constituer un recueil. Vous pourrez ensuite importer tout le recueil dans Kolibri en une seule fois à l'aide d'un jeton de recueil.",
- "ChannelSetList.options": "Options",
- "ChannelSetList.title": "Nom du recueil",
- "ChannelSetList.token": "ID du jeton",
"ChannelSetModal.bookmark": "Favoris",
"ChannelSetModal.channelAdded": "Chaîne ajoutée",
"ChannelSetModal.channelCountText": "{channelCount, plural, =0 {Aucune chaîne publiée dans votre recueil} =1 {# chaîne} other {# chaînes}}",
@@ -356,7 +330,7 @@
"ChannelSetModal.titleRequiredText": "Ce champ est obligatoire",
"ChannelSetModal.token": "Jeton du recueil",
"ChannelSetModal.tokenPrompt": "Pour pouvoir importer ce recueil sur votre appareil, copiez ce jeton dans Kolibri.",
- "ChannelSetModal.unsavedChangesHeader": "Modifications non enregistrées",
+ "ChannelSetModal.unsavedChangesHeader": " Modifications non enregistrées",
"ChannelSetModal.unsavedChangesText": "Vous perdrez toutes les modifications non enregistrées. Êtes-vous sûr de vouloir quitter ?",
"ChannelSetModal.view": "Lecture seule",
"ChannelSharing.alreadyHasAccessError": "L'utilisateur a déjà accès à cette chaîne",
@@ -450,7 +424,7 @@
"CommonMetadataStrings.completion": "Achèvement",
"CommonMetadataStrings.computerScience": "Informatique",
"CommonMetadataStrings.create": "Créer",
- "CommonMetadataStrings.currentEvents": "Événements en cours",
+ "CommonMetadataStrings.currentEvents": "Evènements en cours",
"CommonMetadataStrings.dailyLife": "Vie quotidienne",
"CommonMetadataStrings.dance": "Danse",
"CommonMetadataStrings.determinedByResource": "Déterminé par la ressource",
@@ -459,7 +433,7 @@
"CommonMetadataStrings.drama": "Drame",
"CommonMetadataStrings.duration": "Durée",
"CommonMetadataStrings.earthScience": "Sciences de la Terre",
- "CommonMetadataStrings.entrepreneurship": "Entrepreneuriat",
+ "CommonMetadataStrings.entrepreneurship": "Entreprenariat",
"CommonMetadataStrings.environment": "Environnement",
"CommonMetadataStrings.exactTime": "Délai d'exécution",
"CommonMetadataStrings.explore": "Explorer",
@@ -533,6 +507,163 @@
"CommonMetadataStrings.webDesign": "Web design",
"CommonMetadataStrings.work": "Professionnel",
"CommonMetadataStrings.writing": "Écriture",
+ "CommonStrings.backAction": "Retour",
+ "CommonStrings.channelDetailsLabel": "Détails de la chaîne",
+ "CommonStrings.clearAction": "Supprimer",
+ "CommonStrings.closeAction": "Fermer",
+ "CommonStrings.copyChannelTokenAction": "Copier le jeton de la chaîne",
+ "CommonStrings.dismissAction": "Rejeter",
+ "CommonStrings.genericErrorMessage": "Désolé ! Une erreur est survenue. Veuillez réessayer.",
+ "CommonStrings.previewAction": "Aperçu",
+ "CommonStrings.seeAllAction": "Afficher tout",
+ "CommonStrings.seeLessAction": "Afficher moins",
+ "CommunityChannelsStrings.aboutCommunityLibraryDescription": "La bibliothèque communautaire comprend les chaînes proposées par la communauté et approuvées pour être retrouvées dans Studio.",
+ "CommunityChannelsStrings.aboutCommunityLibraryTitle": "À propos de la bibliothèque communautaire",
+ "CommunityChannelsStrings.activityHistoryLabel": "Historique des activités",
+ "CommunityChannelsStrings.adminLabel": "Administrateur",
+ "CommunityChannelsStrings.allLicensesCompatible": "Toutes les licences sont compatibles avec la bibliothèque communautaire.",
+ "CommunityChannelsStrings.allNotificationsLabel": "Toutes les notifications",
+ "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Veuillez patienter pendant l'examen de la chaîne. Vous recevrez une notification dans votre compte Studio une fois l'examen terminé.",
+ "CommunityChannelsStrings.alreadySubmittedWarningTitle": "Cette version de la chaîne a déjà été soumise à la bibliothèque communautaire.",
+ "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) a approuvé {channelVersion}",
+ "CommunityChannelsStrings.approvedPrimaryInfo": "Une version antérieure est disponible dans la bibliothèque communautaire. Les évaluateurs verront d'abord la dernière version soumise.",
+ "CommunityChannelsStrings.approvedStatus": "Approuvé",
+ "CommunityChannelsStrings.availableStatus": "Disponible dans la bibliothèque communautaire",
+ "CommunityChannelsStrings.cancelAction": "Annuler",
+ "CommunityChannelsStrings.categoriesLabel": "Catégories",
+ "CommunityChannelsStrings.channelCannotBeDistributed": "Cette chaîne ne peut pas être diffusée via Kolibri.",
+ "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution",
+ "CommunityChannelsStrings.channelFitChannelInfoLabel": "Informations sur la chaîne",
+ "CommunityChannelsStrings.channelFitChecklistAttribution": "Chaque ressource comporte-t-elle un auteur ?",
+ "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Les informations de base de votre chaîne sont-elles renseignées : titre, description, vignette, langue, catégorie et niveau ?",
+ "CommunityChannelsStrings.channelFitChecklistIntro": "Critères d'envoi à la bibliothèque communautaire",
+ "CommunityChannelsStrings.channelFitChecklistLicense": "Les ressources de votre chaîne sont-elles sous licence libre ou relèvent-elles du domaine public ?",
+ "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Les ressources de votre chaîne sont-elles accessibles sans connexion Internet ?",
+ "CommunityChannelsStrings.channelFitChecklistQuality": "Y a-t-il quelqu'un dans votre équipe ou au sein de votre organisation qui a examiné cette chaîne ?",
+ "CommunityChannelsStrings.channelFitLicenseLabel": "Licence",
+ "CommunityChannelsStrings.channelFitOfflineUseLabel": "Utilisation hors ligne",
+ "CommunityChannelsStrings.channelFitQualityLabel": "Qualité",
+ "CommunityChannelsStrings.channelTokenDescription": "Vous pouvez utiliser ce jeton pour importer et obtenir un aperçu de la version préliminaire de la chaîne dans Kolibri. Attention, le jeton utilisé pour la chaîne publiée définitive sera différent.",
+ "CommunityChannelsStrings.channelVersion": "{name} v{version}",
+ "CommunityChannelsStrings.channelVersionTokenLabel": "Jeton de version de chaîne",
+ "CommunityChannelsStrings.clearAll": "Supprimer tout",
+ "CommunityChannelsStrings.clearAllAction": "Supprimer tout",
+ "CommunityChannelsStrings.communityLibraryCTADescription": "Vous disposez d'une chaîne qui mérite d'être partagée avec d'autres enseignants et apprenants ? Soumettez-la pour examen via le menu « Partager » afin qu'elle puisse être retrouvée dans Studio.",
+ "CommunityChannelsStrings.communityLibraryCTATitle": "Contribuer au développement de la bibliothèque communautaire",
+ "CommunityChannelsStrings.communityLibraryDescription": "Parcourez les chaînes soumises par la communauté et approuvées pour être retrouvées dans Studio. Copiez un jeton pour utiliser une chaîne dans Kolibri.",
+ "CommunityChannelsStrings.communityLibraryLabel": "Bibliothèque communautaire",
+ "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Envoi à la bibliothèque communautaire",
+ "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - Toutes les licences sont compatibles avec la bibliothèque communautaire.",
+ "CommunityChannelsStrings.confirmDistributionRights": "Veuillez confirmer que vous êtes autorisé à diffuser ces ressources via Kolibri.",
+ "CommunityChannelsStrings.confirmReplacementText": "Je comprends que cela remplacera ma demande précédente dans la file d'attente pour l'examen",
+ "CommunityChannelsStrings.countriesInfoText": "Sélectionnez un ou plusieurs pays pour ajouter une mention à votre chaîne. Par exemple, si votre chaîne contient des ressources conformes à un programme scolaire national ou du contenu spécifique à une région, le fait de sélectionner les pays concernés aidera les utilisateurs à la trouver. Sinon, laissez ce champ vide.",
+ "CommunityChannelsStrings.countryLabel": "Pays",
+ "CommunityChannelsStrings.descriptionLabel": "Décrivez le contenu de votre demande",
+ "CommunityChannelsStrings.dismissAction": "Rejeter",
+ "CommunityChannelsStrings.draftBeingPublishedNotice": "Une version brouillon est en cours de publication",
+ "CommunityChannelsStrings.draftPublishedNotice": "Brouillon publié avec succès",
+ "CommunityChannelsStrings.draftTokenLabel": "Jeton de brouillon",
+ "CommunityChannelsStrings.editorLabel": "Éditeur",
+ "CommunityChannelsStrings.emptyNotificationsNotice": "Vous n'avez aucune notification pour le moment.",
+ "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "Aucune notification ne correspond aux filtres appliqués.",
+ "CommunityChannelsStrings.errorLoadingVersions": "Impossible de charger l'historique des versions",
+ "CommunityChannelsStrings.errorSnackbar": "Une erreur s'est presentée lors de l'envoi de la chaîne",
+ "CommunityChannelsStrings.feedbackNotesLabel": "Notes de l'évaluateur",
+ "CommunityChannelsStrings.filterByDateLabel": "Filtrer par date",
+ "CommunityChannelsStrings.filterByStatusLabel": "Filtrer par statut",
+ "CommunityChannelsStrings.filterLabel": "Filtrer",
+ "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Veuillez corriger les problèmes liés à la licence avant de soumettre une nouvelle version.",
+ "CommunityChannelsStrings.flaggedNotification": "Modifications requises pour la version {channelVersion}",
+ "CommunityChannelsStrings.flaggedStatus": "À modifier",
+ "CommunityChannelsStrings.getDraftTokenAction": "Copier le jeton pour la chaîne brouillon",
+ "CommunityChannelsStrings.goToMyChannelsAction": "Aller à Mes chaînes",
+ "CommunityChannelsStrings.hideCriteriaAction": "Masquer les critères",
+ "CommunityChannelsStrings.hideVersions": "Masquer les versions",
+ "CommunityChannelsStrings.incompatibleLicensesDescription": "« {licenseNames} » - cette chaîne ne peut pas être diffusée via Kolibri. Si vous ne pouvez pas modifier la licence, supprimez toutes les ressources contenant « {licenseNames} » avant de soumettre à nouveau votre demande.",
+ "CommunityChannelsStrings.incompatibleLicensesDetected": "Licences incompatibles détectées.",
+ "CommunityChannelsStrings.incompleteResourcesDescription1": "Les ressources incomplètes ne seront pas publiées et mises à disposition pour le téléchargement dans Kolibri.",
+ "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {ressource incomplète} other {ressources incomplètes}}",
+ "CommunityChannelsStrings.internalNotesLabel": "Notes de l'administrateur (à usage interne uniquement)",
+ "CommunityChannelsStrings.invalidLicensingReason": "Licences non valides ou non conformes",
+ "CommunityChannelsStrings.invalidMetadataReason": "Métadonnées non valides ou manquantes",
+ "CommunityChannelsStrings.languageLabel": "Langue",
+ "CommunityChannelsStrings.languagesLabel": "Langages",
+ "CommunityChannelsStrings.lessDetailsButton": "Masquer les détails",
+ "CommunityChannelsStrings.licenseCheckPassed": "Vérification de la licence réussie.",
+ "CommunityChannelsStrings.licensesLabel": "Licences",
+ "CommunityChannelsStrings.loadError": "Une erreur s'est produite lors du chargement des chaînes.",
+ "CommunityChannelsStrings.loadingVersionHistory": "Chargement de l'historique des versions",
+ "CommunityChannelsStrings.moreDetails": "Une fois votre envoi approuvé, la chaîne sera accessible pour les autres utilisateurs de Kolibri Studio sur la page « Bibliothèque communautaire ».",
+ "CommunityChannelsStrings.moreDetailsButton": "Plus de détails",
+ "CommunityChannelsStrings.needKolibriVersionToImport": "Vous devez disposer de la version 0.19.4 ou d'une version ultérieure de Kolibri pour importer des chaînes depuis la bibliothèque communautaire.",
+ "CommunityChannelsStrings.needsChangesPrimaryInfo": "La version que vous avez soumise précédemment doit être modifiée. Assurez-vous d'avoir pris en compte toutes les remarques avant de la soumettre à nouveau.",
+ "CommunityChannelsStrings.newLabel": "Nouveau",
+ "CommunityChannelsStrings.newNotificationsNotice": "Nouvelles notifications disponibles.",
+ "CommunityChannelsStrings.nextPageAction": "Suivant",
+ "CommunityChannelsStrings.noCommunityChannels": "Aucune chaîne n'a encore été publiée dans la bibliothèque communautaire.",
+ "CommunityChannelsStrings.noResultsWithFilters": "Aucune chaîne ne correspond aux filtres sélectionnés.",
+ "CommunityChannelsStrings.noVersionsAvailable": "Aucun historique des versions n'est disponible",
+ "CommunityChannelsStrings.nonePrimaryInfo": "Nous invitons les membres de la communauté Kolibri à nous soumettre les chaînes qu'ils ont créées pour l'apprentissage hors ligne dans des contextes où les ressources sont limitées. ",
+ "CommunityChannelsStrings.notPublishedWarningDescription": "Publiez d'abord sur Studio, puis soumettez votre contenu à la bibliothèque communautaire.",
+ "CommunityChannelsStrings.notPublishedWarningTitle": "Cette chaîne n'est pas encore publiée sur Kolibri Studio",
+ "CommunityChannelsStrings.notificationsLabel": "Notifications",
+ "CommunityChannelsStrings.otherIssuesReason": "Autres problèmes",
+ "CommunityChannelsStrings.pageIndicator": "{currentPage} sur {totalPages}",
+ "CommunityChannelsStrings.pendingStatus": "Envoyé",
+ "CommunityChannelsStrings.portabilityIssuesReason": "Problèmes de portabilité",
+ "CommunityChannelsStrings.previewYourDraftTitle": "Visualisez votre brouillon de chaîne dans Kolibri",
+ "CommunityChannelsStrings.previousPageAction": "Question précédente",
+ "CommunityChannelsStrings.publicWarningDescription": "Il n'est pas possible de soumettre des chaînes publiques à la bibliothèque communautaire.",
+ "CommunityChannelsStrings.publicWarningTitle": "Cette chaîne est actuellement accessible à tous dans la bibliothèque Kolibri.",
+ "CommunityChannelsStrings.publishAction": "Publier",
+ "CommunityChannelsStrings.publishChannel": "Publier la chaîne",
+ "CommunityChannelsStrings.publishChannelDescription": "Pour afficher votre chaîne dans Kolibri, importez-la à l'aide du jeton de la chaîne.",
+ "CommunityChannelsStrings.publishChannelMode": "Publier la chaîne",
+ "CommunityChannelsStrings.publishDraftDescription": "Votre chaîne sera enregistrée sous forme de brouillon, ce qui vous permettra de la consulter et de vérifier sa qualité sans modifier la version actuellement disponible pour les utilisateurs de Kolibri. Pour afficher le brouillon en question dans Kolibri, importez-le à l'aide du jeton de brouillon de chaîne.",
+ "CommunityChannelsStrings.publishDraftMode": "Publier le brouillon de la chaîne",
+ "CommunityChannelsStrings.publishedVersionLabel": "Version publiée:",
+ "CommunityChannelsStrings.publishingInfo": "Vous publiez : Version {version}",
+ "CommunityChannelsStrings.publishingMessage": "La chaîne est en cours de publication",
+ "CommunityChannelsStrings.qualityAssuranceReason": "Problèmes liés à l'assurance qualité",
+ "CommunityChannelsStrings.reasonLabel": "Raison : {reason}",
+ "CommunityChannelsStrings.resubmitAction": "Soumettre à nouveau",
+ "CommunityChannelsStrings.resubmitModalBodyFirst": "La chaîne {channelName} v{version} est également publiée dans la bibliothèque communautaire.",
+ "CommunityChannelsStrings.resubmitModalBodySecond": "Souhaitez-vous soumettre à nouveau cette version, avec vos modifications, pour qu'elle soit examinée par la bibliothèque communautaire ?",
+ "CommunityChannelsStrings.resubmitModalTitle": "Soumettre à nouveau la chaîne pour qu'elle soit examinée par la bibliothèque communautaire ?",
+ "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}",
+ "CommunityChannelsStrings.retry": "Réessayer",
+ "CommunityChannelsStrings.reviewAction": "Passer en revue",
+ "CommunityChannelsStrings.saveDraft": "Enregistrer le brouillon",
+ "CommunityChannelsStrings.searchLabel": "Rechercher",
+ "CommunityChannelsStrings.searchNotificationsLabel": "Rechercher des notifications",
+ "CommunityChannelsStrings.seeAllVersions": "Voir toutes les versions",
+ "CommunityChannelsStrings.showMore": "Voir plus",
+ "CommunityChannelsStrings.showOlderAction": "Afficher les plus anciens éléments",
+ "CommunityChannelsStrings.specialPermissionsDetected": "Licences avec autorisations spéciales détectées",
+ "CommunityChannelsStrings.submissionCreationNotification": "Votre demande auprès de la bibliothèque communautaire a été enregistrée et est actuellement en cours d'examen.",
+ "CommunityChannelsStrings.submissionNotesLabel": "Notes annexes",
+ "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) a soumis {channelVersion}",
+ "CommunityChannelsStrings.submitButton": "Soumettre pour examen",
+ "CommunityChannelsStrings.submitToCommunityLibrary": "Soumettre à la bibliothèque communautaire",
+ "CommunityChannelsStrings.submittedPrimaryInfo": "Une version antérieure est toujours en attente d'examen. Les évaluateurs verront par défaut la dernière version soumise.",
+ "CommunityChannelsStrings.submittedSnackbar": "Chaîne envoyée à la bibliothèque communautaire",
+ "CommunityChannelsStrings.submittingSnackbar": "Envoi de la chaîne à la bibliothèque communautaire...",
+ "CommunityChannelsStrings.supersededStatus": "Remplacé",
+ "CommunityChannelsStrings.thisMonthLabel": "Ce mois-ci",
+ "CommunityChannelsStrings.thisWeekLabel": "Cette semaine",
+ "CommunityChannelsStrings.thisYearLabel": "Cette année",
+ "CommunityChannelsStrings.todayLabel": "Aujourd’hui",
+ "CommunityChannelsStrings.unreadNotificationsLabel": "Non lu",
+ "CommunityChannelsStrings.versionDescriptionLabel": "Description de la version",
+ "CommunityChannelsStrings.versionLabel": "Version {version}",
+ "CommunityChannelsStrings.versionNotesLabel": "Décrivez les nouveautés de cette version de la chaîne",
+ "CommunityChannelsStrings.viewCriteriaAction": "Voir les critères",
+ "CommunityChannelsStrings.viewMoreAction": "Afficher davantage",
+ "CommunityChannelsStrings.whatCanYouDoHere": "Ce que vous pouvez faire ici",
+ "CommunityChannelsStrings.whatCanYouDoHereItem1": "Parcourir les chaînes par pays, catégorie et langue",
+ "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copier un jeton de chaîne à utiliser dans Kolibri",
+ "CommunityChannelsStrings.whatCanYouDoHereItem3": "Voir les détails de la chaîne, y compris la description et les métadonnées",
+ "CommunityChannelsStrings.whatIsCommunityLibrary": "Qu'est-ce que la bibliothèque communautaire ?",
"CommunityStandardsModal.communityStandardsHeader": "Règles de la communauté",
"CommunityStandardsModal.coreValuesLink": "En savoir plus sur les valeurs fondamentales de Learning Equality",
"CommunityStandardsModal.description": "Learning Equality est une organisation à but non lucratif qui se consacre à fournir un accès équitable à des expériences éducatives de qualité. En plus des Valeurs fondamentales que nous revendiquons, ces Normes communautaires visent à favoriser un environnement valorisant et inclusif pour nos utilisateurs.",
@@ -577,13 +708,13 @@
"ConstantStrings.document": "Document",
"ConstantStrings.document_thumbnail": "Vignette",
"ConstantStrings.edit": "Mes chaînes",
- "ConstantStrings.epub": "Document EPUB",
+ "ConstantStrings.epub": "Document EPub",
"ConstantStrings.exercise": "Exercice",
"ConstantStrings.exercise_thumbnail": "Vignette",
"ConstantStrings.firstCopy": "Copie de {title}",
"ConstantStrings.gif": "Image GIF",
"ConstantStrings.h5p": "Application H5P",
- "ConstantStrings.high_res_video": "Haute définition",
+ "ConstantStrings.high_res_video": "Haute résolution",
"ConstantStrings.html5": "Application HTML5",
"ConstantStrings.html5_thumbnail": "Vignette",
"ConstantStrings.html5_zip": "Zip HTML5",
@@ -600,7 +731,7 @@
"ConstantStrings.mp3": "Audio MP3",
"ConstantStrings.mp4": "Vidéo MP4",
"ConstantStrings.multiple_selection": "Choix multiple",
- "ConstantStrings.nthCopy": "Copier {n, number, integer} du {title}",
+ "ConstantStrings.nthCopy": "Copier {n, number, integer} du {titre}",
"ConstantStrings.num_correct_in_a_row_10": "Objectif : 10 d'affilée",
"ConstantStrings.num_correct_in_a_row_10_description": "L'apprenant doit répondre correctement à 10 questions d'affilée",
"ConstantStrings.num_correct_in_a_row_2": "Objectif : 2 d'affilée",
@@ -616,7 +747,7 @@
"ConstantStrings.public": "Bibliothèque de contenus",
"ConstantStrings.single_selection": "Choix unique",
"ConstantStrings.slideshow": "Diaporama",
- "ConstantStrings.svg": "Image SVG",
+ "ConstantStrings.svg": "Image SVG ",
"ConstantStrings.topic": "Dossier",
"ConstantStrings.topic_thumbnail": "Vignette",
"ConstantStrings.true_false": "Vrai/Faux",
@@ -819,40 +950,11 @@
"DeleteAccountForm.emailInvalidText": "L'adresse e-mail ne correspond pas à celle de votre compte",
"DeleteAccountForm.fieldRequired": "Ce champ est obligatoire",
"DeleteAccountForm.ok": "OK",
- "DetailsPanel.AVERAGE": "Moyenne",
- "DetailsPanel.LARGE": "Volumineuse",
- "DetailsPanel.SMALL": "Petite",
- "DetailsPanel.VERY_LARGE": "Très volumineuse",
- "DetailsPanel.VERY_SMALL": "Très petite",
- "DetailsPanel.aggregatorToolTip": "Site web ou organisation hébergeant le recueil de contenus, sans forcément en être le créateur ou le titulaire des droits d'auteur",
- "DetailsPanel.aggregatorsLabel": "Agrégateurs",
- "DetailsPanel.assessmentsIncludedText": "Évaluations",
- "DetailsPanel.authorToolTip": "Personne ou organisation créatrice de ce contenu",
- "DetailsPanel.authorsLabel": "Auteurs",
- "DetailsPanel.categoriesHeading": "Catégories",
- "DetailsPanel.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux",
- "DetailsPanel.coachHeading": "Ressources pour les éducateurs",
- "DetailsPanel.containsContentHeading": "Contient du contenu provenant de",
- "DetailsPanel.containsHeading": "Contient",
- "DetailsPanel.copyrightHoldersLabel": "Titulaires des droits d'auteur",
- "DetailsPanel.creationHeading": "Créé le",
- "DetailsPanel.currentVersionHeading": "Version publiée",
- "DetailsPanel.languagesHeading": "Langages",
- "DetailsPanel.levelsHeading": "Niveaux",
- "DetailsPanel.licensesLabel": "Licences",
- "DetailsPanel.primaryLanguageHeading": "Langue principale",
- "DetailsPanel.providerToolTip": "Organisation ayant commandé le contenu ou le distribuant",
- "DetailsPanel.providersLabel": "Fournisseurs",
- "DetailsPanel.publishedHeading": "Publiée le",
- "DetailsPanel.resourceHeading": "Total des ressources",
- "DetailsPanel.sampleFromChannelHeading": "Exemple de contenu de cette chaîne",
- "DetailsPanel.sampleFromTopicHeading": "Exemple de contenu sur ce sujet",
- "DetailsPanel.sizeHeading": "Taille de la chaîne",
- "DetailsPanel.sizeText": "{text} ({size})",
- "DetailsPanel.subtitlesHeading": "Légendes et sous-titres",
- "DetailsPanel.tagsHeading": "Étiquettes communes",
- "DetailsPanel.tokenHeading": "Jeton de la chaîne",
- "DetailsPanel.unpublishedText": "Non publiée",
+ "DeleteChannelModal.cancel": "Annuler",
+ "DeleteChannelModal.channelDeletedSnackbar": "Chaîne supprimée",
+ "DeleteChannelModal.deleteChannel": "Supprimer la chaîne",
+ "DeleteChannelModal.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.",
+ "DeleteChannelModal.deleteTitle": "Supprimer cette chaîne",
"DetailsTabView.aggregatorLabel": "Agrégateur",
"DetailsTabView.aggregatorToolTip": "Site Web ou organisation hébergeant le recueil de contenus, sans forcément en être le créateur ou le titulaire des droits d'auteur",
"DetailsTabView.assessmentOptionsLabel": "Options d'évaluation",
@@ -1008,10 +1110,8 @@
"ForgotPassword.forgotPasswordPrompt": "Merci de renseigner votre adresse e-mail pour recevoir les instructions de réinitialisation de votre mot de passe",
"ForgotPassword.forgotPasswordTitle": "Réinitialiser votre mot de passe",
"ForgotPassword.submitButton": "Envoyer",
- "FormulasMenu.btnLabelInsert": "Insérer",
- "FormulasMenu.formulasMenuTitle": "Caractères spéciaux",
"FormulasStrings.addition": "Addition",
- "FormulasStrings.advancedCategory": "Advanced",
+ "FormulasStrings.advancedCategory": "Avancé",
"FormulasStrings.alpha": "alpha",
"FormulasStrings.and": "And",
"FormulasStrings.angle": "Angle",
@@ -1019,7 +1119,7 @@
"FormulasStrings.bar": "Bar",
"FormulasStrings.basicCategory": "Basic",
"FormulasStrings.because": "Because",
- "FormulasStrings.beta": "beta",
+ "FormulasStrings.beta": "bêta",
"FormulasStrings.binomialCoefficient": "Binomial coefficient",
"FormulasStrings.cardinality": "Cardinality",
"FormulasStrings.charactersCategory": "Characters",
@@ -1029,7 +1129,7 @@
"FormulasStrings.congruentTo": "Congruent to",
"FormulasStrings.conjugate": "Conjugate",
"FormulasStrings.conjugateTranspose": "Conjugate transpose",
- "FormulasStrings.contains": "Contains",
+ "FormulasStrings.contains": "Contient",
"FormulasStrings.contourIntegral": "Contour integral",
"FormulasStrings.coproduct": "Coproduct",
"FormulasStrings.definition": "Definition",
@@ -1061,7 +1161,7 @@
"FormulasStrings.fraction": "Fraction",
"FormulasStrings.gamma": "gamma",
"FormulasStrings.gammaCapital": "Gamma",
- "FormulasStrings.geometryCategory": "Geometry",
+ "FormulasStrings.geometryCategory": "Géométrie",
"FormulasStrings.givenThat": "Given that/Such that",
"FormulasStrings.greaterThan": "Greater than",
"FormulasStrings.greaterThanOrEqual": "Greater than or equal to",
@@ -1078,7 +1178,7 @@
"FormulasStrings.lambda": "lambda",
"FormulasStrings.lambdaCapital": "Lambda",
"FormulasStrings.left": "Left",
- "FormulasStrings.leftArrow": "Left arrow",
+ "FormulasStrings.leftArrow": "Flèche gauche",
"FormulasStrings.leftCeiling": "Left ceiling",
"FormulasStrings.leftDouble": "Left (double)",
"FormulasStrings.leftFloor": "Left floor",
@@ -1134,7 +1234,7 @@
"FormulasStrings.reducibleTo": "Reducible to",
"FormulasStrings.rho": "rho",
"FormulasStrings.right": "Right",
- "FormulasStrings.rightArrow": "Right arrow",
+ "FormulasStrings.rightArrow": "Flèche droite",
"FormulasStrings.rightCeiling": "Right ceiling",
"FormulasStrings.rightDouble": "Right (double)",
"FormulasStrings.rightFloor": "Right floor",
@@ -1155,21 +1255,21 @@
"FormulasStrings.southeast": "Southeast",
"FormulasStrings.southwest": "Southwest",
"FormulasStrings.spade": "Spade",
- "FormulasStrings.squareRoot": "Square root",
+ "FormulasStrings.squareRoot": "Racine carrée",
"FormulasStrings.subscript": "Subscript",
"FormulasStrings.subset": "Subset",
"FormulasStrings.subsetOrEqual": "Subset or equal",
"FormulasStrings.subtraction": "Subtraction",
"FormulasStrings.sum": "Sum",
- "FormulasStrings.superscript": "Superscript",
+ "FormulasStrings.superscript": "Exposant",
"FormulasStrings.superset": "Superset",
"FormulasStrings.supersetOrEqual": "Superset or equal",
"FormulasStrings.symmetricDifference": "Symmetric difference",
"FormulasStrings.tau": "tau",
"FormulasStrings.tensorProduct": "Tensor product",
"FormulasStrings.therefore": "Therefore",
- "FormulasStrings.theta": "theta",
- "FormulasStrings.thetaCapital": "Theta",
+ "FormulasStrings.theta": "thêta",
+ "FormulasStrings.thetaCapital": "Thêta",
"FormulasStrings.topElement": "Top element",
"FormulasStrings.triangleDown": "Triangle down",
"FormulasStrings.triangleUp": "Triangle up",
@@ -1204,16 +1304,6 @@
"HintsEditor.newHintBtnLabel": "Nouvel indice",
"HintsEditor.noHintsPlaceholder": "La question n'a aucun indice",
"ImageOnlyThumbnail.thumbnail": "Vignette {title}",
- "ImagesMenu.acceptsText": "Types de fichiers supportés : {acceptedFormats}",
- "ImagesMenu.altTextHint": "La description de l'image est nécessaire pour permettre aux apprenants handicapés visuels de répondre aux questions. Elle s'affiche également en cas d'échec du chargement de l'image",
- "ImagesMenu.altTextLabel": "Description de l’image",
- "ImagesMenu.btnLabelCancel": "Annuler",
- "ImagesMenu.btnLabelInsert": "Insérer",
- "ImagesMenu.currentImageDefaultText": "Image actuelle",
- "ImagesMenu.defaultDropText": "Glissez et déposez une image ici ou téléversez-la manuellement",
- "ImagesMenu.imageHeader": "Téléverser une image",
- "ImagesMenu.selectFile": "Sélectionner le fichier",
- "ImagesMenu.selectFileButton": "Sélectionner le fichier",
"ImportFromChannelsModal.addButton": "Ajouter",
"ImportFromChannelsModal.addedText": "Ajouté",
"ImportFromChannelsModal.importAction": "Importer",
@@ -1223,6 +1313,7 @@
"ImportFromChannelsModal.reviewAction": "Passer en revue",
"ImportFromChannelsModal.reviewTitle": "Sélection de ressources",
"InfoModal.close": "Fermer",
+ "InfoModal.open": "Plus d'informations sur les licences",
"InheritAncestorMetadataModal.applyResourceDetailsTitle": "Appliquer les détails du dossier \"{folder}\"",
"InheritAncestorMetadataModal.cancelAction": "Annuler",
"InheritAncestorMetadataModal.categories": "Catégories : {categories}",
@@ -1255,17 +1346,48 @@
"MainNavigationDrawer.helpLink": "Aide et support",
"MainNavigationDrawer.logoutLink": "Se déconnecter",
"MainNavigationDrawer.settingsLink": "Paramètres",
- "MarkdownEditor.bold": "Gras (Ctrl+B)",
- "MarkdownEditor.formulas": "Insérer une formule (Ctrl+F)",
- "MarkdownEditor.image": "Insérer une image (Ctrl+P)",
- "MarkdownEditor.italic": "Italique (Ctrl+I)",
- "MarkdownEditor.minimize": "Réduire",
- "MarkdownImageField.editImageOption": "Modifier",
- "MarkdownImageField.removeImageOption": "Supprimer",
- "MarkdownImageField.resizeImageOption": "Redimensionner",
"MasteryCriteriaGoal.labelText": "Objectif",
"MasteryCriteriaMofNFields.mHint": "Bonnes réponses nécessaires",
"MasteryCriteriaMofNFields.nHint": "Réponses récentes",
+ "MathLiveA11yStrings.accented": "accentué",
+ "MathLiveA11yStrings.array": "tableau",
+ "MathLiveA11yStrings.box": "encadré",
+ "MathLiveA11yStrings.chemicalFormula": "formule chimique",
+ "MathLiveA11yStrings.crossOut": "barrer",
+ "MathLiveA11yStrings.deleted": "supprimé: ",
+ "MathLiveA11yStrings.delimiter": "séparateur",
+ "MathLiveA11yStrings.denominator": "dénominateur",
+ "MathLiveA11yStrings.endOf": "{spokenText}; fin de {relationName}",
+ "MathLiveA11yStrings.endOfMathfield": "{spokenText}; fin du champ mathématique",
+ "MathLiveA11yStrings.error": "erreur",
+ "MathLiveA11yStrings.extensibleSymbol": "symbole extensible",
+ "MathLiveA11yStrings.first": "premier",
+ "MathLiveA11yStrings.fraction": "fraction",
+ "MathLiveA11yStrings.group": "groupe",
+ "MathLiveA11yStrings.index": "index",
+ "MathLiveA11yStrings.latex": "LaTeX",
+ "MathLiveA11yStrings.line": "ligne",
+ "MathLiveA11yStrings.mathField": "champ mathématique",
+ "MathLiveA11yStrings.mathfield": "champ mathématique",
+ "MathLiveA11yStrings.numerator": "numérateur",
+ "MathLiveA11yStrings.operator": "opérateur",
+ "MathLiveA11yStrings.outOf": "sur {relationName};",
+ "MathLiveA11yStrings.overUnder": "sur-sous",
+ "MathLiveA11yStrings.parent": "parent",
+ "MathLiveA11yStrings.placeholder": "espace réservé",
+ "MathLiveA11yStrings.prompt": "invite",
+ "MathLiveA11yStrings.radicand": "radicande",
+ "MathLiveA11yStrings.rule": "règle",
+ "MathLiveA11yStrings.selected": "sélectionné: ",
+ "MathLiveA11yStrings.space": "espace",
+ "MathLiveA11yStrings.spacing": "espacement",
+ "MathLiveA11yStrings.squareRoot": "racine carrée",
+ "MathLiveA11yStrings.startOf": "début de {relationName} : ",
+ "MathLiveA11yStrings.subscript": "indice",
+ "MathLiveA11yStrings.subscriptSuperscript": "indice-exposant",
+ "MathLiveA11yStrings.superscript": "exposant",
+ "MathLiveA11yStrings.superscriptAndSubscript": "indice et exposant",
+ "MathLiveA11yStrings.text": "texte",
"MessageLayout.backToLogin": "Continuer vers la page de connexion",
"MoveModal.addTopic": "Ajouter un nouveau dossier",
"MoveModal.cancel": "Annuler",
@@ -1296,7 +1418,7 @@
"PasswordField.passwordLabel": "Mot de passe",
"PasswordInstructionsSent.passwordInstructionsHeader": "Instructions envoyées. Merci !",
"PasswordInstructionsSent.passwordInstructionsText": "Si un compte associé à l'adresse e-mail fournie existe déjà, vous devriez recevoir des instructions dans les plus brefs délais. Au cas où vous ne verriez pas d'e-mail de notre part, veuillez vérifier vos spams.",
- "PermissionsError.goToHomePageAction": "Aller sur la page d'accueil",
+ "PermissionsError.backToHome": "Retour à l'accueil",
"PermissionsError.permissionDeniedHeader": "Avez-vous oublié de vous connecter ?",
"PoliciesModal.checkboxText": "J'ai accepté les conditions ci-dessus",
"PoliciesModal.closeButton": "Fermer",
@@ -1306,6 +1428,7 @@
"PrivacyPolicyModal.updatedPrivacyHeader": "Politique de confidentialité mise à jour",
"ProgressBar.progressText": "{percent} %",
"ProgressModal.defaultErrorText": "Échec de la dernière tentative de publication",
+ "ProgressModal.draftHeader": "Enregistrement du brouillon...",
"ProgressModal.lastPublished": "Publié le {last_published}",
"ProgressModal.publishHeader": "Publication de la chaîne",
"ProgressModal.syncError": "Échec de la dernière tentative de synchronisation",
@@ -1346,6 +1469,19 @@
"RelatedResourcesTab.showPreviewBtnLabel": "Montrer",
"RelatedResourcesTab.tooManyNextStepsWarning": "Pour une expérience d'apprentissage mieux guidée, limiter le nombre d'étapes suivantes",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "Pour une expérience d'apprentissage mieux guidée, limiter le nombre d'étapes antérieures",
+ "RemoveChannelFromListModal.cancel": "Annuler",
+ "RemoveChannelFromListModal.channelRemovedSnackbar": "Chaîne supprimée",
+ "RemoveChannelFromListModal.removeBtn": "Supprimer",
+ "RemoveChannelFromListModal.removePrompt": "Vous disposez d’un accès en lecture seule à cette chaîne. Confirmez que vous souhaitez la retirer de votre liste de chaînes.",
+ "RemoveChannelFromListModal.removeTitle": "Retirer de la liste de chaînes",
+ "RemoveChannelModal.cancel": "Annuler",
+ "RemoveChannelModal.deleteChannel": "Supprimer la chaîne",
+ "RemoveChannelModal.deleteChannelWithCLWarning": "Cette chaîne a été partagée avec la bibliothèque communautaire. Le fait de la supprimer ici ne la supprimera pas de la bibliothèque communautaire : elle pourra toujours y être approuvée ou rester disponible.",
+ "RemoveChannelModal.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.",
+ "RemoveChannelModal.deleteTitle": "Supprimer cette chaîne",
+ "RemoveChannelModal.removeBtn": "Supprimer",
+ "RemoveChannelModal.removePrompt": "Vous disposez d’un accès en lecture seule à cette chaîne. Confirmez que vous souhaitez la retirer de votre liste de chaînes.",
+ "RemoveChannelModal.removeTitle": "Retirer de la liste de chaînes",
"ReportErrorModal.closeAction": "Fermer",
"ReportErrorModal.emailDescription": "Contactez l’équipe support avec les détails de l’erreur, et nous ferons de notre mieux pour vous aider.",
"ReportErrorModal.emailPrompt": "Envoyer un e-mail aux développeurs",
@@ -1533,8 +1669,11 @@
"SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Trop avancé pour le niveau de connaissances des apprenants que je recherche",
"SearchRecommendationsStrings.tooBasicForLearnersLabel": "Trop basique pour le niveau de connaissances des apprenants que je recherche",
"SearchRecommendationsStrings.tryAgainLink": "Réessayer",
+ "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Essayez notre nouvelle fonctionnalité de « Recommandations » !",
+ "SearchRecommendationsStrings.trySearchRecommendationsText": "En fonction du titre et de la description du dossier sur lequel vous travaillez, nous vous proposerons des ressources pertinentes issues de la bibliothèque Kolibri. Sélectionnez « Importer depuis les chaînes » dans n'importe quel dossier de vos chaînes pour afficher les recommandations.",
"SearchRecommendationsStrings.undoAction": "Annuler",
"SearchRecommendationsStrings.viewMoreLink": "Afficher davantage",
+ "SearchRecommendationsStrings.viewRecommendationsButton": "Voir les recommandations",
"SearchResultsList.failedToLoad": "Impossible de charger les résultats de la recherche",
"SearchResultsList.resultsPerPageLabel": "Résultats par page",
"SearchResultsList.saveSearchAction": "Enregistrer la recherche",
@@ -1588,10 +1727,121 @@
"Storage.spaceUsedOfMax": "{qty} sur {max}",
"Storage.storagePercentageUsed": "{qty} % du stockage utilisé",
"Storage.videoFiles": "Les fichiers vidéo doivent être compressés pour maximiser l’espace de stockage et garantir une diffusion et une lecture hors ligne fluides. Une fois les fichiers compressés, l’espace total nécessaire peut être inférieur à votre estimation initiale.",
+ "StudioChannelCard.channelLanguageNotSetIndicator": "Aucune langue définie",
+ "StudioChannelCard.details": "Détails",
+ "StudioChannelCard.lastPublished": "Publié le {last_published}",
+ "StudioChannelCard.lastUpdated": "Mis à jour {updated}",
+ "StudioChannelCard.multipleCountries": "Plusieurs pays",
+ "StudioChannelCard.resourceCount": "{count, plural, one {}\n =1 {# ressource}\n other {# ressources}}",
+ "StudioChannelCard.selectChannel": "Sélectionner {name}",
+ "StudioChannelCard.unpublishedText": "Non publiée",
+ "StudioChannelsPage.invitations": "Vous avez {count, plural, one {}\n =1 {# invitation}\n other {# invitations}}",
+ "StudioChannelsPage.noChannelsFound": "Aucune chaîne trouvée",
+ "StudioCollectionsTable.aboutChannelSets": "À propos des recueils",
+ "StudioCollectionsTable.aboutChannelSetsLink": "En savoir plus sur les collections",
+ "StudioCollectionsTable.addChannelSetTitle": "Nouveau recueil",
+ "StudioCollectionsTable.cancel": "Annuler",
+ "StudioCollectionsTable.cancelButtonLabel": "Fermer",
+ "StudioCollectionsTable.channelNumber": "Nombre de chaînes",
+ "StudioCollectionsTable.channelSetsDescriptionText": "Un recueil contient plusieurs chaînes de Kolibri Studio pouvant être importées en une seule fois dans Kolibri avec un seul jeton de recueil.",
+ "StudioCollectionsTable.channelSetsDisclaimer": "Seule la version 0.12.0 ou supérieure de Kolibri permet d'importer des recueils de chaînes.",
+ "StudioCollectionsTable.channelSetsInstructionsText": "Vous pouvez créer un recueil en sélectionnant les chaînes que vous souhaitez importer ensemble.",
+ "StudioCollectionsTable.collectionDeleted": "Collection supprimée",
+ "StudioCollectionsTable.copiedTokenId": "Jeton copié",
+ "StudioCollectionsTable.copyFailed": "Échec de la copie",
+ "StudioCollectionsTable.copyToken": "Copier le jeton",
+ "StudioCollectionsTable.delete": "Supprimer le recueil",
+ "StudioCollectionsTable.deleteChannelSetText": "Êtes-vous sûr de vouloir supprimer ce recueil ?",
+ "StudioCollectionsTable.deleteChannelSetTitle": "Supprimer le recueil",
+ "StudioCollectionsTable.deleteError": "Erreur lors de la suppression de la collection",
+ "StudioCollectionsTable.edit": "Modifier le recueil",
+ "StudioCollectionsTable.noChannelSetsFound": "Il est possible de regrouper plusieurs chaînes pour constituer un recueil. Vous pourrez ensuite importer tout le recueil dans Kolibri en une seule fois à l'aide d'un jeton de recueil.",
+ "StudioCollectionsTable.options": "Options",
+ "StudioCollectionsTable.pageTitle": "Recueils",
+ "StudioCollectionsTable.saving": "Enregistrement en cours",
+ "StudioCollectionsTable.tableCaption": "Liste des collections",
+ "StudioCollectionsTable.title": "Nom du recueil",
+ "StudioCollectionsTable.token": "ID du jeton",
+ "StudioCopyToken.copiedTokenId": "Jeton copié",
+ "StudioCopyToken.copyFailed": "Échec de la copie",
+ "StudioCopyToken.token": "Jeton",
+ "StudioCopyToken.tooltipText": "Copier le jeton pour importer la chaîne dans Kolibri",
+ "StudioDetailsPanel.AVERAGE": "Moyenne",
+ "StudioDetailsPanel.LARGE": "Volumineuse",
+ "StudioDetailsPanel.SMALL": "Petite",
+ "StudioDetailsPanel.VERY_LARGE": "Très volumineuse",
+ "StudioDetailsPanel.VERY_SMALL": "Très petite",
+ "StudioDetailsPanel.aggregatorToolTip": "Site web ou organisation hébergeant le recueil de contenus, sans forcément en être le créateur ou le titulaire des droits d'auteur",
+ "StudioDetailsPanel.aggregatorsLabel": "Agrégateurs",
+ "StudioDetailsPanel.assessmentsIncludedText": "Évaluations",
+ "StudioDetailsPanel.authorToolTip": "Personne ou organisation créatrice de ce contenu",
+ "StudioDetailsPanel.authorsLabel": "Auteurs",
+ "StudioDetailsPanel.categoriesHeading": "Catégories",
+ "StudioDetailsPanel.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux",
+ "StudioDetailsPanel.coachHeading": "Ressources pour les éducateurs",
+ "StudioDetailsPanel.containsContentHeading": "Contient du contenu provenant de",
+ "StudioDetailsPanel.containsHeading": "Contient",
+ "StudioDetailsPanel.copyrightHoldersLabel": "Titulaires des droits d'auteur",
+ "StudioDetailsPanel.creationHeading": "Créé le",
+ "StudioDetailsPanel.currentVersionHeading": "Version publiée",
+ "StudioDetailsPanel.languagesHeading": "Langages",
+ "StudioDetailsPanel.levelsHeading": "Niveaux",
+ "StudioDetailsPanel.licensesLabel": "Licences",
+ "StudioDetailsPanel.primaryLanguageHeading": "Langue principale",
+ "StudioDetailsPanel.providerToolTip": "Organisation ayant commandé le contenu ou le distribuant",
+ "StudioDetailsPanel.providersLabel": "Fournisseurs",
+ "StudioDetailsPanel.publishedHeading": "Publiée le",
+ "StudioDetailsPanel.resourceHeading": "Total des ressources",
+ "StudioDetailsPanel.sampleFromChannelHeading": "Exemple de contenu de cette chaîne",
+ "StudioDetailsPanel.sizeHeading": "Taille de la chaîne",
+ "StudioDetailsPanel.sizeText": "{text} ({size})",
+ "StudioDetailsPanel.subtitlesHeading": "Légendes et sous-titres",
+ "StudioDetailsPanel.tagsHeading": "Étiquettes communes",
+ "StudioDetailsPanel.tokenHeading": "Jeton de chaîne",
+ "StudioDetailsPanel.unpublishedText": "Non publiée",
+ "StudioImmersiveModal.close": "Fermer",
+ "StudioMessageLayout.backToLogin": "Continuer vers la page de connexion",
+ "StudioMyChannels.copyToken": "Copier le jeton de la chaîne",
+ "StudioMyChannels.deleteChannel": "Supprimer la chaîne",
+ "StudioMyChannels.editChannel": "Modifier les détails de la chaîne",
+ "StudioMyChannels.goToWebsite": "Aller sur le site web source",
+ "StudioMyChannels.moreOptions": "Plus d'options",
+ "StudioMyChannels.newChannel": "Nouvelle chaîne",
+ "StudioMyChannels.title": "Mes chaînes",
+ "StudioMyChannels.viewContent": "Afficher la chaîne sur Kolibri",
"StudioOfflineAlert.offlineText": "Il semblerait que vous soyez hors ligne. Vos modifications seront enregistrées dès que votre connexion sera rétablie.",
"StudioOfflineAlert.onlineText": "Vous êtes à nouveau en ligne.",
+ "StudioStarredChannels.copyToken": "Copier le jeton de la chaîne",
+ "StudioStarredChannels.deleteChannel": "Supprimer la chaîne",
+ "StudioStarredChannels.editChannel": "Modifier les détails de la chaîne",
+ "StudioStarredChannels.goToWebsite": "Aller sur le site web source",
+ "StudioStarredChannels.moreOptions": "Plus d'options",
+ "StudioStarredChannels.removeChannel": "Supprimer la chaîne",
+ "StudioStarredChannels.title": "Chaînes favorites",
+ "StudioStarredChannels.viewContent": "Afficher la chaîne sur Kolibri",
"StudioTree.missingTitle": "Titre manquant",
"StudioTree.optionsTooltip": "Options",
+ "StudioViewOnlyChannels.copyToken": "Copier le jeton de la chaîne",
+ "StudioViewOnlyChannels.goToWebsite": "Aller sur le site web source",
+ "StudioViewOnlyChannels.moreOptions": "Plus d'options",
+ "StudioViewOnlyChannels.removeChannel": "Supprimer la chaîne",
+ "StudioViewOnlyChannels.title": "Chaînes en lecture seule",
+ "StudioViewOnlyChannels.viewContent": "Afficher la chaîne sur Kolibri",
+ "SubscriptionCard.annualPrice": "{price, number} $/an",
+ "SubscriptionCard.cancelNotice": "Votre abonnement expirera le {date, date, medium}. Vos données seront supprimées après cela.",
+ "SubscriptionCard.dismiss": "Rejeter",
+ "SubscriptionCard.genericError": "Une erreur s'est produite lors de la connexion au prestataire de paiement. Veuillez réessayer.",
+ "SubscriptionCard.instantUpgrade": "Mettez à niveau votre stockage dès maintenant",
+ "SubscriptionCard.manageSubscription": "Gérer l'abonnement",
+ "SubscriptionCard.renewalNotice": "Votre abonnement sera automatiquement renouvelé le {date, date, medium}.",
+ "SubscriptionCard.storageAmount": "Stockage (Go)",
+ "SubscriptionCard.storageIncluded": "{size} compris dans votre abonnement",
+ "SubscriptionCard.storageRange": "Saisir une valeur comprise entre 1 et 50",
+ "SubscriptionCard.subscriptionActive": "Abonnement de stockage actif",
+ "SubscriptionCard.subscriptionCanceling": "Abonnement résilié",
+ "SubscriptionCard.upgradeDescription": "Achetez de l'espace de stockage supplémentaire au prix de 15 $ par Go et par an.",
+ "SubscriptionCard.upgradeNow": "Mettez à niveau dès maintenant",
+ "SubscriptionCard.upgradeSuccess": "Stockage augmenté à {size}",
"SubtitlesList.acceptedFormatsTooltip": "Formats supportés : {extensions}",
"SubtitlesList.addSubtitleText": "Ajouter des légendes",
"SubtitlesList.subtitlesHeader": "Légendes et sous-titres",
@@ -1620,7 +1870,7 @@
"TechnicalTextBlock.copiedToClipboardConfirmation": "Copié dans le presse-papier",
"TechnicalTextBlock.copiedToClipboardFailure": "Échec de la copie dans le presse-papier",
"TechnicalTextBlock.copyToClipboardButtonPrompt": "Copier dans le presse-papier",
- "TermsOfServiceModal.ToSHeader": "Terms of Service",
+ "TermsOfServiceModal.ToSHeader": "Conditions d'utilisation",
"TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions",
"TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;",
"TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and",
@@ -1651,7 +1901,7 @@
"TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.",
"TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality",
"TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.",
- "TermsOfServiceModal.communityStandardsHeader": "Community Standards",
+ "TermsOfServiceModal.communityStandardsHeader": "Normes communautaires",
"TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards",
"TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.",
"TermsOfServiceModal.definitionsHeader": "Definitions",
@@ -1690,7 +1940,7 @@
"TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.",
"TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights",
"TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.",
- "TermsOfServiceModal.updatedToSHeader": "Updated terms of service",
+ "TermsOfServiceModal.updatedToSHeader": "Conditions d'utilisation mises à jour",
"TermsOfServiceModal.userContentHeader": "User-Generated Content",
"TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.",
"TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.",
@@ -1715,82 +1965,101 @@
"TextArea.fieldRequiredMessage": "Ce champ est obligatoire",
"TextField.fieldRequiredMessage": "Ce champ est obligatoire",
"Thumbnail.thumbnail": "{title} vignette",
+ "ThumbnailGenerator.closeButtonLabel": "OK",
"ThumbnailGenerator.generatedDefaultFilename": "Vignette générée",
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "Impossible de générer une vignette",
"ThumbnailGenerator.thumbnailGenerationFailedText": "Une erreur s'est produite lors de la génération d'une vignette",
- "TipTapEditorStrings.addLink": "Add link",
- "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
- "TipTapEditorStrings.altTextLabel": "Alt text (Optional)",
- "TipTapEditorStrings.altTextPlaceholder": "Describe your image...",
- "TipTapEditorStrings.bold": "Strong",
- "TipTapEditorStrings.bulletList": "Bullet list",
- "TipTapEditorStrings.cancel": "Cancel",
- "TipTapEditorStrings.cancelLoading": "Cancel loading",
- "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.",
- "TipTapEditorStrings.close": "Close",
- "TipTapEditorStrings.closeModal": "Close modal",
- "TipTapEditorStrings.codeBlock": "Code block",
- "TipTapEditorStrings.copy": "Copy",
- "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions",
- "TipTapEditorStrings.copyLink": "Copy link",
+ "TipTapEditorStrings.TipTapEditorLabel": "éditeur de texte - Appuyez sur Entrée pour commencer à modifier",
+ "TipTapEditorStrings.TipTapViewerLabel": "contenu de l'éditeur de texte",
+ "TipTapEditorStrings.addLink": "Ajouter un lien",
+ "TipTapEditorStrings.alignLeft": "Aligner à gauche",
+ "TipTapEditorStrings.alignRight": "Aligner à droite",
+ "TipTapEditorStrings.altTextDescription": "Le texte alternatif est indispensable pour permettre aux apprenants malvoyants de répondre aux questions ; il s'affiche également lorsque l'image ne parvient pas à se charger",
+ "TipTapEditorStrings.altTextLabel": "Texte alternatif (facultatif)",
+ "TipTapEditorStrings.altTextPlaceholder": "Décrivez votre image...",
+ "TipTapEditorStrings.bold": "Gras",
+ "TipTapEditorStrings.bulletList": "Liste à puces",
+ "TipTapEditorStrings.cancel": "Annuler",
+ "TipTapEditorStrings.cancelLoading": "Annuler le chargement",
+ "TipTapEditorStrings.clearFormatting": "Supprimer la mise en forme",
+ "TipTapEditorStrings.clipboardAccessFailed": "Échec de l'accès au presse-papiers. Veuillez réessayer.",
+ "TipTapEditorStrings.close": "Fermer",
+ "TipTapEditorStrings.closeModal": "Fermez la fenêtre",
+ "TipTapEditorStrings.codeBlock": "Bloc de code",
+ "TipTapEditorStrings.collapseFormattingBar": "Réduire la barre de mise en forme",
+ "TipTapEditorStrings.copy": "Copie",
+ "TipTapEditorStrings.copyAndPasteActions": "Actions de copier-coller",
+ "TipTapEditorStrings.copyLink": "Copier le lien",
+ "TipTapEditorStrings.decreaseFormatSize": "Diminuer la taille du format",
"TipTapEditorStrings.defaultImageName": "Image",
- "TipTapEditorStrings.edit": "Edit",
- "TipTapEditorStrings.editImage": "Edit image",
- "TipTapEditorStrings.editLink": "Edit link",
- "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.",
- "TipTapEditorStrings.fileSizeUnit": "MB.",
- "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ",
- "TipTapEditorStrings.formatHeader1": "Header 1",
- "TipTapEditorStrings.formatHeader2": "Header 2",
- "TipTapEditorStrings.formatHeader3": "Header 3",
+ "TipTapEditorStrings.edit": "Modifier",
+ "TipTapEditorStrings.editImage": "Modifier l'image",
+ "TipTapEditorStrings.editLink": "Modifier le lien",
+ "TipTapEditorStrings.editorControls": "Commandes de l'éditeur",
+ "TipTapEditorStrings.errorUploadingImage": "Erreur lors du chargement de l'image",
+ "TipTapEditorStrings.expandFormattingBar": "Étendre la barre de formatage",
+ "TipTapEditorStrings.failedToProcessImage": "Échec du traitement du fichier image.",
+ "TipTapEditorStrings.fileSizeUnit": "Mo.",
+ "TipTapEditorStrings.fileTooLarge": "Le fichier est trop volumineux. La taille maximale est de ",
+ "TipTapEditorStrings.formatHeader1": "En-tête 1",
+ "TipTapEditorStrings.formatHeader2": "En-tête 2",
+ "TipTapEditorStrings.formatHeader3": "En-tête 3",
"TipTapEditorStrings.formatNormal": "Normal",
- "TipTapEditorStrings.formatOptions": "Format options",
- "TipTapEditorStrings.formatSmall": "Small",
- "TipTapEditorStrings.formulasMenuTitle": "Special Characters",
- "TipTapEditorStrings.goToLink": "Go to link",
- "TipTapEditorStrings.historyActions": "History actions",
- "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually",
- "TipTapEditorStrings.imagePreview": "Image preview",
- "TipTapEditorStrings.insert": "Insert",
- "TipTapEditorStrings.insertImage": "Insert image",
- "TipTapEditorStrings.insertLink": "Insert link",
- "TipTapEditorStrings.insertTools": "Insert tools",
- "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ",
- "TipTapEditorStrings.italic": "Italic",
- "TipTapEditorStrings.link": "Link",
- "TipTapEditorStrings.linkActions": "Link actions",
- "TipTapEditorStrings.listFormatting": "List formatting",
- "TipTapEditorStrings.mathFormula": "Math formula",
- "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.",
- "TipTapEditorStrings.noFileProvided": "No file provided.",
- "TipTapEditorStrings.numberedList": "Numbered list",
- "TipTapEditorStrings.opensInNewTab": "(opens in new tab)",
- "TipTapEditorStrings.paste": "Paste",
- "TipTapEditorStrings.pasteOptions": "Paste options",
- "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu",
- "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting",
- "TipTapEditorStrings.redo": "Redo",
- "TipTapEditorStrings.remove": "Remove",
- "TipTapEditorStrings.removeImage": "Remove image",
- "TipTapEditorStrings.removeLink": "Remove link",
- "TipTapEditorStrings.replaceFile": "Replace file",
- "TipTapEditorStrings.save": "Save",
- "TipTapEditorStrings.saveChanges": "Save changes",
- "TipTapEditorStrings.scriptFormatting": "Script formatting",
- "TipTapEditorStrings.selectFile": "Select file",
- "TipTapEditorStrings.selectFileToUpload": "Select file to upload",
- "TipTapEditorStrings.strikethrough": "Strikethrough",
- "TipTapEditorStrings.subscript": "Subscript",
- "TipTapEditorStrings.superscript": "Superscript",
- "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp",
- "TipTapEditorStrings.text": "Text",
- "TipTapEditorStrings.textFormatOptions": "Text format options",
- "TipTapEditorStrings.textFormattingOptions": "Text formatting options",
- "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar",
- "TipTapEditorStrings.textStyleFormatting": "Text style formatting",
- "TipTapEditorStrings.underline": "Underline",
- "TipTapEditorStrings.undo": "Undo",
- "TipTapEditorStrings.uploadImage": "Upload image",
+ "TipTapEditorStrings.formatOptions": "Options de format",
+ "TipTapEditorStrings.formatSize": "Taille du format",
+ "TipTapEditorStrings.formatSmall": "Petite",
+ "TipTapEditorStrings.formulasMenuTitle": "Caractères spéciaux",
+ "TipTapEditorStrings.goToLink": "Cliquez sur le lien",
+ "TipTapEditorStrings.historyActions": "Actions de l'historique",
+ "TipTapEditorStrings.imageDropZoneText": "Faites glisser et déposez une image ici ou chargez-la manuellement",
+ "TipTapEditorStrings.imagePreview": "Aperçu de l'image",
+ "TipTapEditorStrings.increaseFormatSize": "Augmenter la taille du format",
+ "TipTapEditorStrings.insert": "Insérer",
+ "TipTapEditorStrings.insertContent": "Insérer du contenu",
+ "TipTapEditorStrings.insertContentMenu": "Insérer le menu de contenu",
+ "TipTapEditorStrings.insertContentOption": "Insérer l'option de contenu",
+ "TipTapEditorStrings.insertImage": "Insérer une image",
+ "TipTapEditorStrings.insertLink": "Insérer un lien",
+ "TipTapEditorStrings.insertTools": "Insérer des outils",
+ "TipTapEditorStrings.invalidFileType": "Type de fichier non valide. Veuillez utiliser : ",
+ "TipTapEditorStrings.italic": "Italique",
+ "TipTapEditorStrings.link": "Lien",
+ "TipTapEditorStrings.linkActions": "Actions sur les liens",
+ "TipTapEditorStrings.listFormatting": "Formatage des listes",
+ "TipTapEditorStrings.loadingFormulas": "Chargement de l'éditeur mathématique",
+ "TipTapEditorStrings.mathFormula": "Formule mathématique",
+ "TipTapEditorStrings.moreButtonText": "Plus",
+ "TipTapEditorStrings.multipleFilesDroppedWarning": "Plusieurs fichiers ont été déposés. Seul le premier fichier a été sélectionné.",
+ "TipTapEditorStrings.noEnoughStorageSpace": "L'espace de stockage disponible est insuffisant. La taille du fichier dépasse l'espace de stockage restant.",
+ "TipTapEditorStrings.noFileProvided": "Aucun fichier fourni.",
+ "TipTapEditorStrings.numberedList": "Liste numérotée",
+ "TipTapEditorStrings.opensInNewTab": "(s'ouvre dans un nouvel onglet)",
+ "TipTapEditorStrings.paste": "Coller",
+ "TipTapEditorStrings.pasteOptions": "Options de collage",
+ "TipTapEditorStrings.pasteOptionsMenu": "Menu des options de collage",
+ "TipTapEditorStrings.pasteWithoutFormatting": "Coller sans formatage",
+ "TipTapEditorStrings.redo": "Refaire",
+ "TipTapEditorStrings.remove": "Supprimer",
+ "TipTapEditorStrings.removeImage": "Supprimer l'image",
+ "TipTapEditorStrings.removeLink": "Supprimer le lien",
+ "TipTapEditorStrings.replaceFile": "Remplacer le fichier",
+ "TipTapEditorStrings.save": "Enregistrer",
+ "TipTapEditorStrings.saveChanges": "Enregistrer les modifications",
+ "TipTapEditorStrings.scriptFormatting": "Formatage du script",
+ "TipTapEditorStrings.selectFile": "Sélectionner un fichier",
+ "TipTapEditorStrings.selectFileToUpload": "Sélectionnez le fichier à télécharger",
+ "TipTapEditorStrings.strikethrough": "Barré",
+ "TipTapEditorStrings.subscript": "Indice",
+ "TipTapEditorStrings.superscript": "Exposant",
+ "TipTapEditorStrings.supportedFileTypes": "Types de fichiers supportés : { extensions }",
+ "TipTapEditorStrings.text": "Texte",
+ "TipTapEditorStrings.textFormatOptions": "Options de format de texte",
+ "TipTapEditorStrings.textFormattingOptions": "Options de mise en forme du texte",
+ "TipTapEditorStrings.textFormattingToolbar": "Barre d'outils de mise en forme du texte",
+ "TipTapEditorStrings.textStyleFormatting": "Formatage du style du texte",
+ "TipTapEditorStrings.underline": "Souligner",
+ "TipTapEditorStrings.undo": "Annuler",
+ "TipTapEditorStrings.uploadImage": "Téléverser une image",
"TitleStrings.catalogTitle": "Catalogue de la bibliothèque de contenus Kolibri",
"TitleStrings.defaultTitle": "Kolibri Studio",
"TitleStrings.tabTitle": "{title} - {site}",
@@ -1814,25 +2083,26 @@
"TreeView.showSidebar": "Afficher la barre latérale",
"TreeView.updatedResourcesReadyForReview": "Les ressources mises à jour sont prêtes à être revues",
"TreeViewBase.apiGenerated": "Généré par l'API",
- "TreeViewBase.cancel": "Annuler",
"TreeViewBase.channelDeletedSnackbar": "Chaîne supprimée",
"TreeViewBase.channelDetails": "Voir les détails de la chaîne",
"TreeViewBase.deleteChannel": "Supprimer la chaîne",
- "TreeViewBase.deleteChannelButton": "Supprimer la chaîne",
- "TreeViewBase.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.",
- "TreeViewBase.deleteTitle": "Supprimer cette chaîne",
"TreeViewBase.editChannel": "Modifier les détails de la chaîne",
"TreeViewBase.emptyChannelTooltip": "Impossible de publier une chaîne vide",
"TreeViewBase.getToken": "Obtenir un jeton",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {la ressource est incomplète et ne peut pas être publiée} other {les ressources sont incomplètes et ne peuvent pas être publiées}}",
+ "TreeViewBase.inviteCollaborators": "Inviter des collaborateurs",
"TreeViewBase.noChangesText": "Aucune modification trouvée dans la chaîne",
"TreeViewBase.noLanguageSetError": "La langue de la chaîne est obligatoire",
"TreeViewBase.openTrash": "Ouvrir la corbeille",
"TreeViewBase.publishButton": "Publier",
"TreeViewBase.publishButtonTitle": "Rendre cette chaîne disponible pour importation dans Kolibri",
"TreeViewBase.shareChannel": "Partager la chaîne",
+ "TreeViewBase.shareMenuButton": "Partager",
+ "TreeViewBase.shareToken": "Partager le jeton",
+ "TreeViewBase.submitToCommunityLibrary": "Soumettre à la bibliothèque communautaire",
"TreeViewBase.syncChannel": "Synchroniser les ressources",
"TreeViewBase.viewOnly": "Lecture seule",
+ "Uploader.closeButtonLabel": "OK",
"Uploader.listDelimiter": ", ",
"Uploader.maxFileSizeText": "{count, plural, one {}\n =1 {# fichier ne sera pas téléversé.}\n other {# fichiers ne seront pas téléversés.}} La taille du fichier doit être inférieure à {size}",
"Uploader.noStorageHeader": "Espace insuffisant",
@@ -1906,4 +2176,4 @@
"sharedVue.masteryModelRequired": "Le critère de maîtrise est obligatoire",
"sharedVue.shortActivityLteThirty": "La valeur doit être égale ou inférieure à 30",
"sharedVue.titleRequired": "Le titre est obligatoire"
-}
+}
\ No newline at end of file
diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po
index e38a3b7b6f..309db530dd 100644
--- a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po
+++ b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-09-03 19:45+0000\n"
-"PO-Revision-Date: 2025-09-12 13:59\n"
+"POT-Creation-Date: 2026-04-16 00:57+0000\n"
+"PO-Revision-Date: 2026-04-24 16:20\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -14,8 +14,8 @@ msgstr ""
"X-Crowdin-Project: kolibri-studio\n"
"X-Crowdin-Project-ID: 286000\n"
"X-Crowdin-Language: fr\n"
-"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n"
-"X-Crowdin-File-ID: 4886\n"
+"X-Crowdin-File: /unstable/django.po\n"
+"X-Crowdin-File-ID: 4322\n"
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
#: contentcuration/settings.py:271
@@ -26,25 +26,25 @@ msgstr "Arabe"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "Le site est actuellement en mode lecture seule. Veuillez réessayer plus tard."
-#: contentcuration/models.py:342
+#: contentcuration/models.py:356
msgid "Not enough space. Check your storage under Settings page."
msgstr "Espace insuffisant. Vérifiez votre espace de stockage disponible sur la page Paramètres."
-#: contentcuration/models.py:374 contentcuration/models.py:386
+#: contentcuration/models.py:440 contentcuration/models.py:452
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "Espace de stockage insuffisant ! Demandez plus d'espace dans Paramètres > Stockage."
-#: contentcuration/models.py:2155
+#: contentcuration/models.py:2531
msgid " (Original)"
msgstr " (Original)"
-#: contentcuration/models.py:3297
+#: contentcuration/models.py:3848
msgid "Created DateTime"
-msgstr "Date et heure de création"
+msgstr ""
-#: contentcuration/models.py:3299
+#: contentcuration/models.py:3850
msgid "Datetime field when the custom_metadata for task was created in UTC"
-msgstr "Champ de date et heure indiquant la création des custom_metadata de la tâche en UTC"
+msgstr ""
#: contentcuration/settings.py:269
msgid "English"
@@ -717,7 +717,7 @@ msgstr "Nous rencontrons des problèmes avec un service tiers. Toute opération
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here"
msgstr "Nous rencontrons des problèmes avec notre centre de données. Vous pourriez par conséquent rencontrer des problèmes de réseau lors de l'utilisation de Studio. Nous vous remercions de votre patience pendant la résolution de ces problèmes. Pour vérifier l'état de ce service, veuillez visiter ici"
-#: contentcuration/utils/publish.py:101
+#: contentcuration/utils/publish.py:103
msgid "Kolibri Studio Channel Published"
msgstr "Chaîne Kolibri Studio publiée"
@@ -729,14 +729,15 @@ msgstr "Rapport d'erreurs de Kolibri Studio"
msgid "Kolibri Studio account deleted"
msgstr "Compte Kolibri Studio supprimé"
-#: kolibri_public/views.py:223
+#: kolibri_public/views.py:323
msgid "Resource"
msgstr "Ressource"
-#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94
+#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167
msgid "API version is unavailable"
msgstr "La version de l’API est indisponible"
-#: kolibri_public/views_v1.py:97
+#: kolibri_public/views_v1.py:170
msgid "No channel matching {} found"
msgstr "Aucune chaîne correspondant à {} trouvée"
+
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/README.md b/contentcuration/locale/hi_IN/LC_MESSAGES/README.md
index 0f82b94d50..014a952e9a 100644
--- a/contentcuration/locale/hi_IN/LC_MESSAGES/README.md
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/README.md
@@ -1 +1 @@
-The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
+The JSON messages files in this folder were generated by kolibri-i18n csvToJSON.js
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json
index 100e64a758..ea81e3a301 100644
--- a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json
@@ -1,8 +1,8 @@
{
- "AccessibilityOptions.altText": "Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners",
- "AccessibilityOptions.audioDescription": "The resource contains a second narration audio track that provides additional information for the benefit of blind users and those with low vision",
- "AccessibilityOptions.highContrast": "The resource text and visual elements are displayed with high contrast for the benefit of users with low vision",
- "AccessibilityOptions.signLanguage": "Synchronized sign language intepretation is available for audio and video content",
+ "AccessibilityOptions.altText": "Les éléments visuels de la ressource sont accompagnés de descriptions accessibles aux lecteurs d'écran, ce qui facilite l'apprentissage des apprenants aveugles",
+ "AccessibilityOptions.audioDescription": "La ressource contient une deuxième piste audio narrative qui fournit des informations supplémentaires à l'intention des utilisateurs aveugles et malvoyants",
+ "AccessibilityOptions.highContrast": "Le texte et les éléments visuels sont affichés à fort contraste pour faciliter la lecture des utilisateurs malvoyants",
+ "AccessibilityOptions.signLanguage": "Une interprétation synchronisée en langue des signes est disponible pour les contenus audio et vidéo",
"AccessibilityOptions.taggedPdf": "The document contains PDF tags that can be accessed by screen readers for the benefit of blind learners",
"Account.apiDocumentation": "API documentation",
"Account.apiTokenHeading": "API Token",
@@ -15,6 +15,7 @@
"Account.exportAccountDataHeading": "Export account data",
"Account.exportAccountDataLabel": "You will receive an email with all information linked to your account",
"Account.exportAccountDataModalMessage": "You'll receive an email with your data when the export is completed",
+ "Account.exportDataBtn": "OK",
"Account.exportDataButton": "Export data",
"Account.exportFailed": "Unable to export data. Please try again.",
"Account.exportStartedHeader": "Data export started",
@@ -30,6 +31,18 @@
"AccountNotActivated.requestNewLink": "Request a new activation link",
"AccountNotActivated.text": "Please check your email for an activation link or request a new link.",
"AccountNotActivated.title": "Account has not been activated",
+ "AccountsMain.TOSLink": "Terms of service",
+ "AccountsMain.copyright": "© {year} Learning Equality",
+ "AccountsMain.createAccountButton": "अकाउंट बनाएँ",
+ "AccountsMain.forgotPasswordLink": "Forgot your password?",
+ "AccountsMain.guestModeLink": "Explore without an account",
+ "AccountsMain.kolibriStudio": "Kolibri स्टूडियो",
+ "AccountsMain.loginFailed": "Email or password is incorrect",
+ "AccountsMain.loginFailedOffline": "You seem to be offline. Please connect to the internet before signing in.",
+ "AccountsMain.loginToProceed": "You must sign in to view that page",
+ "AccountsMain.passwordLabel": "पासवर्ड",
+ "AccountsMain.privacyPolicyLink": "Privacy policy",
+ "AccountsMain.signInButton": "साइन इन करें",
"ActivationExpired.activationExpiredText": "This activation link has been used already or has expired.",
"ActivationExpired.activationExpiredTitle": "Activation failed",
"ActivationExpired.requestNewLink": "Request a new activation link",
@@ -54,8 +67,6 @@
"AdministrationAppError.unauthorizedDetails": "You need to be an administrator of Studio to view this page",
"AdministrationIndex.channelsLabel": "चैनल",
"AdministrationIndex.usersLabel": "उपयोगकर्ता",
- "Alert.closeButtonLabel": "OK",
- "Alert.dontShowAgain": "यह संदेश दोबारा न दिखाएँ",
"AnswersEditor.answersLabel": "Answers",
"AnswersEditor.newAnswerBtnLabel": "New answer",
"AnswersEditor.noAnswersPlaceholder": "Question has no answer options",
@@ -73,6 +84,7 @@
"AssessmentEditor.noQuestionsPlaceholder": "Exercise has no questions",
"AssessmentEditor.showAnswers": "Show answers",
"AssessmentEditor.toolbarItemLabel": "प्रश्न",
+ "AssessmentItemEditor.dialogMessageChangeToFreeResponse": "Switching to 'free response' will remove all current answers. Continue?",
"AssessmentItemEditor.dialogMessageChangeToInput": "Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?",
"AssessmentItemEditor.dialogMessageChangeToSingleSelection": "Switching to 'single choice' will set only one answer as correct. Continue?",
"AssessmentItemEditor.dialogMessageChangeToTrueFalse": "Switching to 'true or false' will remove all current answers. Continue?",
@@ -88,8 +100,8 @@
"AssessmentItemToolbar.toolbarLabelAddBelow": "Add {itemLabel} below",
"AssessmentItemToolbar.toolbarLabelDelete": "हटाएँ",
"AssessmentItemToolbar.toolbarLabelEdit": "संपादित करें (एडिट)",
- "AssessmentItemToolbar.toolbarLabelMoveDown": "Move down",
- "AssessmentItemToolbar.toolbarLabelMoveUp": "Move up",
+ "AssessmentItemToolbar.toolbarLabelMoveDown": "नीचे जाएँ",
+ "AssessmentItemToolbar.toolbarLabelMoveUp": "ऊपर जाएं",
"AssessmentTab.dialogCancelBtnLabel": "रद्द करें",
"AssessmentTab.dialogSubmitBtnLabel": "Submit",
"AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} incomplete {invalidItemsCount, plural, one {question} other {questions}}",
@@ -104,7 +116,7 @@
"BytesForHumansStrings.fileSizeInGigabytes": "{n, number, integer} GB",
"BytesForHumansStrings.fileSizeInKilobytes": "{n, number, integer} KB",
"BytesForHumansStrings.fileSizeInMegabytes": "{n, number, integer} MB",
- "BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB",
+ "BytesForHumansStrings.fileSizeInTerabytes": "{n, number} TB",
"CatalogFAQ.KolibriAnswer": "Kolibri is an open source ed-tech platform designed for low-resource communities, focused on:",
"CatalogFAQ.KolibriAnswerItem1": "Overcoming infrastructural barriers that prevent equitable access to quality education for learners in low-resource and low-connectivity contexts",
"CatalogFAQ.KolibriAnswerItem2": "Increasing the availability of open learning materials suitable for many curricula, learning goals, and situations",
@@ -170,26 +182,31 @@
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "Starred",
"CatalogFilterBar.subtitles": "उपशीर्षक",
- "CatalogFilters.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
- "CatalogFilters.coachLabel": "Resources for coaches",
- "CatalogFilters.copyright": "© {year} Learning Equality",
- "CatalogFilters.formatLabel": "Formats",
- "CatalogFilters.frequentlyAskedQuestionsLink": "Frequently asked questions",
- "CatalogFilters.includesLabel": "Display only channels with",
- "CatalogFilters.licenseLabel": "Licenses",
- "CatalogFilters.searchLabel": "कीबोर्ड",
- "CatalogFilters.searchText": "खोज",
- "CatalogFilters.starredLabel": "Starred",
- "CatalogFilters.subtitlesLabel": "Captions or subtitles",
+ "CatalogFilterPanelContent.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
+ "CatalogFilterPanelContent.coachLabel": "Resources for coaches",
+ "CatalogFilterPanelContent.copyright": "© {year} Learning Equality",
+ "CatalogFilterPanelContent.formatLabel": "Formats",
+ "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Frequently asked questions",
+ "CatalogFilterPanelContent.includesLabel": "Display only channels with",
+ "CatalogFilterPanelContent.licenseLabel": "Licenses",
+ "CatalogFilterPanelContent.searchLabel": "कीबोर्ड",
+ "CatalogFilterPanelContent.starredLabel": "Starred",
+ "CatalogFilterPanelContent.subtitlesLabel": "Captions or subtitles",
+ "CatalogFilters.filterLabel": "फ़िल्टर करें",
"CatalogList.cancelButton": "रद्द करें",
"CatalogList.channelSelectionCount": "{count, plural,\n =1 {# channel selected}\n other {# channels selected}}",
+ "CatalogList.copyToken": "Copy channel token",
"CatalogList.downloadButton": "डाउनलोड करें",
"CatalogList.downloadCSV": "CSV डाउनलोड करें",
"CatalogList.downloadPDF": "Download PDF",
"CatalogList.downloadingMessage": "डाउनलोड शुरू",
+ "CatalogList.goToWebsite": "Go to source website",
+ "CatalogList.moreOptions": "अधिक विकल्प",
"CatalogList.resultsText": "{count, plural,\n =1 {# result found}\n other {# results found}}",
"CatalogList.selectAll": "सभी का चयन करें",
"CatalogList.selectChannels": "Download a summary of selected channels",
+ "CatalogList.title": "Kolibri library",
+ "CatalogList.viewContent": "View channel on Kolibri",
"CategoryOptions.noCategoryFoundText": "Category not found",
"ChangePasswordForm.cancelAction": "रद्द करें",
"ChangePasswordForm.changePasswordHeader": "पासवर्ड बदलें",
@@ -210,9 +227,6 @@
"ChannelCatalogFrontPage.languagesHeading": "भाषाएँ",
"ChannelCatalogFrontPage.numberOfChannels": "{ num } channels",
"ChannelCatalogFrontPage.subtitlesIncludedText": "Captions or subtitles",
- "ChannelDeletedError.backToHomeAction": "होमपेज पर वापिस जाएं",
- "ChannelDeletedError.channelDeletedDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.",
- "ChannelDeletedError.channelDeletedHeader": "चैनल नहीं मिला",
"ChannelDetailsModal.downloadButton": "Download channel summary",
"ChannelDetailsModal.downloadCSV": "CSV डाउनलोड करें",
"ChannelDetailsModal.downloadPDF": "Download PDF",
@@ -220,6 +234,7 @@
"ChannelExportStrings.assessments": "Assessments",
"ChannelExportStrings.authors": "Authors",
"ChannelExportStrings.coachContent": "Resources for coaches",
+ "ChannelExportStrings.containsContentFrom": "Contains content from",
"ChannelExportStrings.copyrightHolders": "Copyright holders",
"ChannelExportStrings.description": "विवरण",
"ChannelExportStrings.downloadFilename": "{year}_{month}_Kolibri_Content_Library",
@@ -248,18 +263,17 @@
"ChannelInvitation.editText": "{sender} has invited you to edit {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "Go to channel",
"ChannelInvitation.viewText": "{sender} has invited you to view {channel}",
- "ChannelItem.cancel": "रद्द करें",
"ChannelItem.channelDeletedSnackbar": "Channel deleted",
"ChannelItem.channelLanguageNotSetIndicator": "No language set",
+ "ChannelItem.channelRemovedSnackbar": "Channel removed",
"ChannelItem.copyToken": "Copy channel token",
"ChannelItem.deleteChannel": "चैनल हटाएँ",
- "ChannelItem.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
- "ChannelItem.deleteTitle": "Delete this channel",
"ChannelItem.details": "विवरण",
"ChannelItem.editChannel": "Edit channel details",
"ChannelItem.goToWebsite": "Go to source website",
"ChannelItem.lastPublished": "Published {last_published}",
"ChannelItem.lastUpdated": "Updated {updated}",
+ "ChannelItem.removeChannel": "Remove channel",
"ChannelItem.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
"ChannelItem.unpublishedText": "Unpublished",
"ChannelItem.versionText": "संस्करण {version}",
@@ -269,10 +283,9 @@
"ChannelList.noChannelsFound": "No channels found",
"ChannelList.noMatchingChannels": "There are no matching channels",
"ChannelListAppError.channelPermissionsErrorDetails": "Sign in or ask the owner of this channel to give you permission to edit or view",
- "ChannelListIndex.catalog": "Content Library",
+ "ChannelListIndex.catalog": "Kolibri लाइब्रेरी",
"ChannelListIndex.channelSets": "Collections",
"ChannelListIndex.frequentlyAskedQuestions": "Frequently asked questions",
- "ChannelListIndex.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}",
"ChannelListIndex.libraryTitle": "Kolibri Content Library Catalog",
"ChannelModal.APIText": "Channels generated automatically are not editable.",
"ChannelModal.changesSaved": "परिवर्तन सहेजे गए",
@@ -284,7 +297,7 @@
"ChannelModal.creatingHeader": "New channel",
"ChannelModal.details": "Channel details",
"ChannelModal.editTab": "विवरण",
- "ChannelModal.keepEditingButton": "Keep editing",
+ "ChannelModal.keepEditingButton": "एडिटिंग करते रहें",
"ChannelModal.notFoundError": "Channel does not exist",
"ChannelModal.saveChangesButton": "परिवर्तनों को सेव करें",
"ChannelModal.shareTab": "Sharing",
@@ -296,25 +309,6 @@
"ChannelNotFoundError.channelNotFoundHeader": "चैनल नहीं मिला",
"ChannelSelectionList.noChannelsFound": "No channels found",
"ChannelSelectionList.searchText": "किसी चैनल को खोजें",
- "ChannelSetItem.cancel": "रद्द करें",
- "ChannelSetItem.delete": "Delete collection",
- "ChannelSetItem.deleteChannelSetText": "Are you sure you want to delete this collection?",
- "ChannelSetItem.deleteChannelSetTitle": "Delete collection",
- "ChannelSetItem.edit": "Edit collection",
- "ChannelSetItem.options": "विकल्प",
- "ChannelSetItem.saving": "Saving",
- "ChannelSetList.aboutChannelSets": "About collections",
- "ChannelSetList.aboutChannelSetsLink": "Learn about collections",
- "ChannelSetList.addChannelSetTitle": "New collection",
- "ChannelSetList.cancelButtonLabel": "बंद करें",
- "ChannelSetList.channelNumber": "Number of channels",
- "ChannelSetList.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.",
- "ChannelSetList.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections",
- "ChannelSetList.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.",
- "ChannelSetList.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.",
- "ChannelSetList.options": "विकल्प",
- "ChannelSetList.title": "Collection name",
- "ChannelSetList.token": "Token ID",
"ChannelSetModal.bookmark": "Starred",
"ChannelSetModal.channelAdded": "Channel added",
"ChannelSetModal.channelCountText": "{channelCount, plural, =0 {No published channels in your collection} =1 {# channel} other {# channels}}",
@@ -330,7 +324,7 @@
"ChannelSetModal.public": "Public",
"ChannelSetModal.publishedChannelsOnlyText": "Only published channels are available for selection",
"ChannelSetModal.removeText": "हटा दें",
- "ChannelSetModal.saveButton": "Save and close",
+ "ChannelSetModal.saveButton": "सेव करें और बंद करें",
"ChannelSetModal.selectChannelsHeader": "Select channels",
"ChannelSetModal.titleLabel": "Collection name",
"ChannelSetModal.titleRequiredText": "Field is required",
@@ -400,7 +394,7 @@
"Clipboard.close": "बंद करें",
"Clipboard.copiedItemsToClipboard": "Copied in clipboard",
"Clipboard.deleteSelectedButton": "हटाएँ",
- "Clipboard.duplicateSelectedButton": "Make a copy",
+ "Clipboard.duplicateSelectedButton": "एक कॉपी बनाएं",
"Clipboard.emptyDefaultText": "Use the clipboard to copy resources and move them to other folders and channels",
"Clipboard.emptyDefaultTitle": "No resources in your clipboard",
"Clipboard.moveSelectedButton": "Move",
@@ -423,7 +417,7 @@
"CommonMetadataStrings.browseChannel": "चैनल ब्राउज़ करें",
"CommonMetadataStrings.calculus": "कैल्कुलस",
"CommonMetadataStrings.captionsSubtitles": "Includes captions or subtitles",
- "CommonMetadataStrings.category": "Category",
+ "CommonMetadataStrings.category": "श्रेणी",
"CommonMetadataStrings.chemistry": "रसायन विज्ञान",
"CommonMetadataStrings.civicEducation": "नागरिक शास्त्र",
"CommonMetadataStrings.completeDuration": "When time spent is equal to duration",
@@ -477,7 +471,7 @@
"CommonMetadataStrings.physics": "भौतिक विज्ञान",
"CommonMetadataStrings.politicalScience": "राजनीतिशास्त्र",
"CommonMetadataStrings.practice": "अभ्यास",
- "CommonMetadataStrings.practiceQuiz": "Practice quiz",
+ "CommonMetadataStrings.practiceQuiz": "क्विज करें",
"CommonMetadataStrings.preschool": "बालवाड़ी",
"CommonMetadataStrings.professionalSkills": "पेशेवर कौशल",
"CommonMetadataStrings.programming": "प्रोग्रामिंग",
@@ -499,6 +493,7 @@
"CommonMetadataStrings.softwareToolsAndTraining": "सॉफ्टवेयर साधन और प्रशिक्षण",
"CommonMetadataStrings.specializedProfessionalTraining": "विशेषज्ञता पेशेवर प्रशिक्षण",
"CommonMetadataStrings.statistics": "सांख्यिकी",
+ "CommonMetadataStrings.survey": "Survey",
"CommonMetadataStrings.taggedPdf": "टैग की गयी PDF फाइल",
"CommonMetadataStrings.teacher": "Working with a teacher",
"CommonMetadataStrings.technicalAndVocationalTraining": "तकनीकी और व्यावसायिक प्रशिक्षण",
@@ -512,6 +507,163 @@
"CommonMetadataStrings.webDesign": "वेब डिजाइन",
"CommonMetadataStrings.work": "कार्य",
"CommonMetadataStrings.writing": "लेखन",
+ "CommonStrings.backAction": "वापस जाएं",
+ "CommonStrings.channelDetailsLabel": "Channel Details",
+ "CommonStrings.clearAction": "साफ़ करें",
+ "CommonStrings.closeAction": "बंद करें",
+ "CommonStrings.copyChannelTokenAction": "Copy channel token",
+ "CommonStrings.dismissAction": "ख़ारिज करें",
+ "CommonStrings.genericErrorMessage": "माफ़ करें! कुछ गलत हो गया। कृपया पुन: प्रयास करें।",
+ "CommonStrings.previewAction": "पूर्वावलोकन",
+ "CommonStrings.seeAllAction": "See all",
+ "CommonStrings.seeLessAction": "See less",
+ "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.",
+ "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library",
+ "CommunityChannelsStrings.activityHistoryLabel": "Activity history",
+ "CommunityChannelsStrings.adminLabel": "एडमिन",
+ "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.",
+ "CommunityChannelsStrings.allNotificationsLabel": "All Notifications",
+ "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.",
+ "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.",
+ "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}",
+ "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.",
+ "CommunityChannelsStrings.approvedStatus": "Approved",
+ "CommunityChannelsStrings.availableStatus": "Available in Community Library",
+ "CommunityChannelsStrings.cancelAction": "रद्द करें",
+ "CommunityChannelsStrings.categoriesLabel": "श्रेणियाँ",
+ "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.",
+ "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution",
+ "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information",
+ "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?",
+ "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?",
+ "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library",
+ "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?",
+ "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?",
+ "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?",
+ "CommunityChannelsStrings.channelFitLicenseLabel": "लाइसेंस",
+ "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use",
+ "CommunityChannelsStrings.channelFitQualityLabel": "Quality",
+ "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.",
+ "CommunityChannelsStrings.channelVersion": "{name} v{version}",
+ "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token",
+ "CommunityChannelsStrings.clearAll": "सभी साफ़ करें",
+ "CommunityChannelsStrings.clearAllAction": "सभी साफ़ करें",
+ "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.",
+ "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library",
+ "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.",
+ "CommunityChannelsStrings.communityLibraryLabel": "Community Library",
+ "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission",
+ "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.",
+ "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.",
+ "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue",
+ "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.",
+ "CommunityChannelsStrings.countryLabel": "Countries",
+ "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission",
+ "CommunityChannelsStrings.dismissAction": "ख़ारिज करें",
+ "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published",
+ "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully",
+ "CommunityChannelsStrings.draftTokenLabel": "Draft token",
+ "CommunityChannelsStrings.editorLabel": "Editor",
+ "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.",
+ "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.",
+ "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history",
+ "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel",
+ "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer",
+ "CommunityChannelsStrings.filterByDateLabel": "Filter by date",
+ "CommunityChannelsStrings.filterByStatusLabel": "Filter by status",
+ "CommunityChannelsStrings.filterLabel": "फ़िल्टर करें",
+ "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.",
+ "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}",
+ "CommunityChannelsStrings.flaggedStatus": "Needs changes",
+ "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel",
+ "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels",
+ "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria",
+ "CommunityChannelsStrings.hideVersions": "Hide versions",
+ "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.",
+ "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.",
+ "CommunityChannelsStrings.incompleteResourcesDescription1": "Incomplete resources will not be published and made available for download in Kolibri.",
+ "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}",
+ "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)",
+ "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses",
+ "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata",
+ "CommunityChannelsStrings.languageLabel": "भाषा ",
+ "CommunityChannelsStrings.languagesLabel": "भाषाएँ",
+ "CommunityChannelsStrings.lessDetailsButton": "Hide details",
+ "CommunityChannelsStrings.licenseCheckPassed": "License check passed.",
+ "CommunityChannelsStrings.licensesLabel": "Licenses",
+ "CommunityChannelsStrings.loadError": "There was an error loading channels.",
+ "CommunityChannelsStrings.loadingVersionHistory": "Loading version history",
+ "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.",
+ "CommunityChannelsStrings.moreDetailsButton": "More details",
+ "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.",
+ "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.",
+ "CommunityChannelsStrings.newLabel": "नया",
+ "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.",
+ "CommunityChannelsStrings.nextPageAction": "अगला",
+ "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.",
+ "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.",
+ "CommunityChannelsStrings.noVersionsAvailable": "No version history available",
+ "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ",
+ "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.",
+ "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet",
+ "CommunityChannelsStrings.notificationsLabel": "Notifications",
+ "CommunityChannelsStrings.otherIssuesReason": "Other issues",
+ "CommunityChannelsStrings.pageIndicator": "{currentPage} of {totalPages}",
+ "CommunityChannelsStrings.pendingStatus": "Submitted",
+ "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems",
+ "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri",
+ "CommunityChannelsStrings.previousPageAction": "पिछला प्रश्न",
+ "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.",
+ "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.",
+ "CommunityChannelsStrings.publishAction": "Publish",
+ "CommunityChannelsStrings.publishChannel": "Publish channel",
+ "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.",
+ "CommunityChannelsStrings.publishChannelMode": "Publish channel",
+ "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.",
+ "CommunityChannelsStrings.publishDraftMode": "Publish draft channel",
+ "CommunityChannelsStrings.publishedVersionLabel": "Published version:",
+ "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}",
+ "CommunityChannelsStrings.publishingMessage": "Channel is being published",
+ "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues",
+ "CommunityChannelsStrings.reasonLabel": "Reason: {reason}",
+ "CommunityChannelsStrings.resubmitAction": "Resubmit",
+ "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.",
+ "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?",
+ "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?",
+ "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}",
+ "CommunityChannelsStrings.retry": "पुन: प्रयास करें",
+ "CommunityChannelsStrings.reviewAction": "Review",
+ "CommunityChannelsStrings.saveDraft": "Save draft",
+ "CommunityChannelsStrings.searchLabel": "खोज",
+ "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications",
+ "CommunityChannelsStrings.seeAllVersions": "See all versions",
+ "CommunityChannelsStrings.showMore": "और दिखाएँ",
+ "CommunityChannelsStrings.showOlderAction": "Show older",
+ "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected",
+ "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.",
+ "CommunityChannelsStrings.submissionNotesLabel": "Submission notes",
+ "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}",
+ "CommunityChannelsStrings.submitButton": "Submit for review",
+ "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library",
+ "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.",
+ "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library",
+ "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...",
+ "CommunityChannelsStrings.supersededStatus": "Superseded",
+ "CommunityChannelsStrings.thisMonthLabel": "इस महीने",
+ "CommunityChannelsStrings.thisWeekLabel": "This week",
+ "CommunityChannelsStrings.thisYearLabel": "This year",
+ "CommunityChannelsStrings.todayLabel": "Today",
+ "CommunityChannelsStrings.unreadNotificationsLabel": "Unread",
+ "CommunityChannelsStrings.versionDescriptionLabel": "Version description",
+ "CommunityChannelsStrings.versionLabel": "संस्करण {version}",
+ "CommunityChannelsStrings.versionNotesLabel": "Describe what's included in this channel version",
+ "CommunityChannelsStrings.viewCriteriaAction": "View criteria",
+ "CommunityChannelsStrings.viewMoreAction": "अधिक देखें",
+ "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here",
+ "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language",
+ "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri",
+ "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata",
+ "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?",
"CommunityStandardsModal.communityStandardsHeader": "Community Standards",
"CommunityStandardsModal.coreValuesLink": "Learn more about Learning Equality's core values",
"CommunityStandardsModal.description": "Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.",
@@ -566,10 +718,12 @@
"ConstantStrings.html5": "HTML5 App",
"ConstantStrings.html5_thumbnail": "Thumbnail",
"ConstantStrings.html5_zip": "HTML5 zip",
+ "ConstantStrings.imscp_zip": "IMSCP zip",
"ConstantStrings.input_question": "Numeric input",
"ConstantStrings.jpeg": "JPEG image",
"ConstantStrings.jpg": "JPG image",
"ConstantStrings.json": "JSON",
+ "ConstantStrings.kpub": "KPub document",
"ConstantStrings.learner": "Anyone",
"ConstantStrings.low_res_video": "Low resolution",
"ConstantStrings.m_of_n": "M of N...",
@@ -590,7 +744,7 @@
"ConstantStrings.perseus": "Perseus Exercise",
"ConstantStrings.perseus_question": "Khan Academy question",
"ConstantStrings.png": "PNG image",
- "ConstantStrings.public": "Content library",
+ "ConstantStrings.public": "Kolibri library",
"ConstantStrings.single_selection": "Single choice",
"ConstantStrings.slideshow": "स्लाइडशो",
"ConstantStrings.svg": "SVG image",
@@ -631,9 +785,9 @@
"ContentNodeEditListItem.creatingCopies": "Copying...",
"ContentNodeEditListItem.editTooltip": "Edit title and description",
"ContentNodeEditListItem.optionsTooltip": "विकल्प",
- "ContentNodeEditListItem.removeNode": "Remove",
- "ContentNodeEditListItem.retryCopy": "Retry",
- "ContentNodeEditListItem.undo": "Undo",
+ "ContentNodeEditListItem.removeNode": "हटा दें",
+ "ContentNodeEditListItem.retryCopy": "पुन: प्रयास करें",
+ "ContentNodeEditListItem.undo": "अकृत करें",
"ContentNodeIcon.audio": "ऑडियो",
"ContentNodeIcon.document": "दस्तावेज़",
"ContentNodeIcon.exercise": "अभ्यास",
@@ -655,7 +809,7 @@
"ContentNodeOptions.copiedToClipboardSnackbar": "क्लिपबोर्ड पर कॉपी कर दिया गया",
"ContentNodeOptions.copyToClipboard": "क्लिपबोर्ड पर कॉपी करें",
"ContentNodeOptions.creatingCopies": "Copying...",
- "ContentNodeOptions.editAllDetails": "Edit details",
+ "ContentNodeOptions.editAllDetails": "विवरण संपादित करें",
"ContentNodeOptions.editAudience": "Edit audience",
"ContentNodeOptions.editCategories": "Edit categories",
"ContentNodeOptions.editCompletion": "Edit completion",
@@ -667,11 +821,12 @@
"ContentNodeOptions.editTitleDescription": "Edit title and description",
"ContentNodeOptions.editWhatIsNeeded": "Edit requirements",
"ContentNodeOptions.goToOriginalLocation": "Go to original location",
- "ContentNodeOptions.makeACopy": "Make a copy",
+ "ContentNodeOptions.makeACopy": "एक कॉपी बनाएं",
"ContentNodeOptions.move": "Move",
"ContentNodeOptions.moveTo": "Move to...",
"ContentNodeOptions.newSubtopic": "New folder",
- "ContentNodeOptions.remove": "Delete",
+ "ContentNodeOptions.remove": "हटा दें",
+ "ContentNodeOptions.removeNode": "हटाएँ",
"ContentNodeOptions.removedFromClipboard": "Deleted from clipboard",
"ContentNodeOptions.removedItems": "Sent to trash",
"ContentNodeOptions.undo": "अकृत करें",
@@ -731,9 +886,9 @@
"Create.organizationSourceOption": "Organization",
"Create.organizationSourcePlaceholder": "Name of organization",
"Create.organizingUsageOption": "Organizing or aligning existing materials",
- "Create.otherSourceOption": "Other",
+ "Create.otherSourceOption": "अन्य",
"Create.otherSourcePlaceholder": "Please describe",
- "Create.otherUsageOption": "Other",
+ "Create.otherUsageOption": "अन्य",
"Create.otherUsagePlaceholder": "Please describe",
"Create.passwordLabel": "पासवर्ड",
"Create.passwordMatchMessage": "Passwords don't match",
@@ -765,15 +920,15 @@
"CurrentTopicView.copySelectedButton": "क्लिपबोर्ड पर कॉपी करें",
"CurrentTopicView.copyToClipboardButton": "क्लिपबोर्ड पर कॉपी करें",
"CurrentTopicView.creatingCopies": "Copying...",
- "CurrentTopicView.deleteSelectedButton": "Remove",
- "CurrentTopicView.duplicateSelectedButton": "Make a copy",
+ "CurrentTopicView.deleteSelectedButton": "हटा दें",
+ "CurrentTopicView.duplicateSelectedButton": "एक कॉपी बनाएं",
"CurrentTopicView.editAudienceButton": "Edit audience",
"CurrentTopicView.editButton": "संपादित करें (एडिट)",
"CurrentTopicView.editCategoriesButton": "Edit categories",
"CurrentTopicView.editLanguageButton": "Edit language",
"CurrentTopicView.editLearningActivitiesButton": "Edit learning activities",
"CurrentTopicView.editLevelsButton": "Edit levels",
- "CurrentTopicView.editSelectedButton": "Edit details",
+ "CurrentTopicView.editSelectedButton": "विवरण संपादित करें",
"CurrentTopicView.editSourceButton": "Edit source",
"CurrentTopicView.editWhatIsNeededButton": "Edit requirements",
"CurrentTopicView.importFromChannels": "Import from channels",
@@ -794,40 +949,12 @@
"DeleteAccountForm.emailAddressLabel": "Email address",
"DeleteAccountForm.emailInvalidText": "Email does not match your account email",
"DeleteAccountForm.fieldRequired": "Field is required",
- "Details.AVERAGE": "Average",
- "Details.LARGE": "Large",
- "Details.SMALL": "Small",
- "Details.VERY_LARGE": "Very large",
- "Details.VERY_SMALL": "Very small",
- "Details.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder",
- "Details.aggregatorsLabel": "Aggregators",
- "Details.assessmentsIncludedText": "Assessments",
- "Details.authorToolTip": "Person or organization who created this content",
- "Details.authorsLabel": "Authors",
- "Details.categoriesHeading": "Categories",
- "Details.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
- "Details.coachHeading": "Resources for coaches",
- "Details.containsContentHeading": "Contains content from",
- "Details.containsHeading": "Contains",
- "Details.copyrightHoldersLabel": "Copyright holders",
- "Details.creationHeading": "Created on",
- "Details.currentVersionHeading": "Published version",
- "Details.languagesHeading": "भाषाएँ",
- "Details.levelsHeading": "Levels",
- "Details.licensesLabel": "Licenses",
- "Details.primaryLanguageHeading": "Primary language",
- "Details.providerToolTip": "Organization that commissioned or is distributing the content",
- "Details.providersLabel": "Providers",
- "Details.publishedHeading": "Published on",
- "Details.resourceHeading": "Total resources",
- "Details.sampleFromChannelHeading": "Sample content from this channel",
- "Details.sampleFromTopicHeading": "Sample content from this topic",
- "Details.sizeHeading": "Channel size",
- "Details.sizeText": "{text} ({size})",
- "Details.subtitlesHeading": "Captions and subtitles",
- "Details.tagsHeading": "Common tags",
- "Details.tokenHeading": "चैनल टोकन",
- "Details.unpublishedText": "Unpublished",
+ "DeleteAccountForm.ok": "OK",
+ "DeleteChannelModal.cancel": "रद्द करें",
+ "DeleteChannelModal.channelDeletedSnackbar": "Channel deleted",
+ "DeleteChannelModal.deleteChannel": "चैनल हटाएँ",
+ "DeleteChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
+ "DeleteChannelModal.deleteTitle": "Delete this channel",
"DetailsTabView.aggregatorLabel": "Aggregator",
"DetailsTabView.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder",
"DetailsTabView.assessmentOptionsLabel": "Assessment options",
@@ -865,28 +992,28 @@
"DiffTable.typeTopics": "फ़ोल्डर्स",
"DiffTable.typeVersion": "API version",
"DiffTable.typeVideos": "वीडियो",
- "EditAudienceModal.cancelAction": "Cancel",
+ "EditAudienceModal.cancelAction": "रद्द करें",
"EditAudienceModal.editAudienceTitle": "Edit audience",
- "EditAudienceModal.forBeginnersCheckbox": "For beginners",
+ "EditAudienceModal.forBeginnersCheckbox": "नौसिखियों के लिए",
"EditAudienceModal.multipleAudience": "The selected resources are visible to different audiences. Choosing an option below will change the visibility of all selected resources.",
- "EditAudienceModal.saveAction": "Save",
+ "EditAudienceModal.saveAction": "सेव करें",
"EditAudienceModal.visibleTo": "Visible to:",
"EditAudienceModal.visibleToAnyone": "Resources are visible to anyone",
"EditAudienceModal.visibleToCoaches": "Resources are visible only to coaches (teachers, facilitators, administrators)",
- "EditBooleanMapModal.cancelAction": "Cancel",
- "EditBooleanMapModal.saveAction": "Save",
+ "EditBooleanMapModal.cancelAction": "रद्द करें",
+ "EditBooleanMapModal.saveAction": "सेव करें",
"EditBooleanMapModal.updateDescendantCheckbox": "Apply to all resources, folders, and subfolders contained within the selected folders.",
"EditCategoriesModal.editCategories": "Edit categories",
- "EditCompletionModal.cancelAction": "Cancel",
+ "EditCompletionModal.cancelAction": "रद्द करें",
"EditCompletionModal.editCompletion": "Edit completion",
- "EditCompletionModal.saveAction": "Save",
- "EditLanguageModal.cancelAction": "Cancel",
+ "EditCompletionModal.saveAction": "सेव करें",
+ "EditLanguageModal.cancelAction": "रद्द करें",
"EditLanguageModal.differentLanguages": "You selected resources in different languages. The language you choose below will be applied to all selected resources.",
"EditLanguageModal.editLanguage": "Edit language",
"EditLanguageModal.emptyLanguagesSearch": "No language matches the search",
"EditLanguageModal.languageItemText": "{language} ({code})",
- "EditLanguageModal.saveAction": "Save",
- "EditLanguageModal.searchAction": "Search",
+ "EditLanguageModal.saveAction": "सेव करें",
+ "EditLanguageModal.searchAction": "खोज",
"EditLanguageModal.updateDescendantsCheckbox": "Apply the chosen language to all resources, folders, and subfolders contained within the selected folders.",
"EditLearningActivitiesModal.editLearningActivitiesTitle": "Edit learning activities",
"EditLevelsModal.editLevelsTitle": "Edit levels",
@@ -903,7 +1030,7 @@
"EditModal.finishButton": "समाप्त",
"EditModal.invalidNodesFound": "{count, plural,\n =1 {# incomplete resource found}\n other {# incomplete resources found}}",
"EditModal.invalidNodesFoundText": "Incomplete resources will not be published until these errors are resolved",
- "EditModal.keepEditingButton": "Keep editing",
+ "EditModal.keepEditingButton": "एडिटिंग करते रहें",
"EditModal.loadErrorText": "Failed to load content",
"EditModal.okButton": "OK",
"EditModal.saveAnywaysButton": "Exit anyway",
@@ -916,35 +1043,35 @@
"EditResourcesNeededModal.editResourcesNeededTitle": "Edit requirements",
"EditSourceModal.aggregatorLabel": "Aggregator",
"EditSourceModal.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder",
- "EditSourceModal.authorLabel": "Author",
+ "EditSourceModal.authorLabel": "लेखक",
"EditSourceModal.authorToolTip": "Person or organization who created this content",
- "EditSourceModal.cancelAction": "Cancel",
+ "EditSourceModal.cancelAction": "रद्द करें",
"EditSourceModal.cannotEditPublic": "Not editable for resources from public channels",
- "EditSourceModal.copyrightHolderLabel": "Copyright holder",
+ "EditSourceModal.copyrightHolderLabel": "कॉपीराइट धारक",
"EditSourceModal.editAttribution": "Edit source",
"EditSourceModal.editOnlyLocal": "Edits will be reflected only for local resources",
"EditSourceModal.mixed": "Mixed",
"EditSourceModal.providerLabel": "Provider",
"EditSourceModal.providerToolTip": "Organization that commissioned or is distributing the content",
- "EditSourceModal.saveAction": "Save",
- "EditTitleDescriptionModal.cancelAction": "Cancel",
- "EditTitleDescriptionModal.descriptionLabel": "Description",
+ "EditSourceModal.saveAction": "सेव करें",
+ "EditTitleDescriptionModal.cancelAction": "रद्द करें",
+ "EditTitleDescriptionModal.descriptionLabel": "विवरण",
"EditTitleDescriptionModal.editTitleDescription": "Edit title and description",
- "EditTitleDescriptionModal.saveAction": "Save",
- "EditTitleDescriptionModal.titleLabel": "Title",
+ "EditTitleDescriptionModal.saveAction": "सेव करें",
+ "EditTitleDescriptionModal.titleLabel": "शीर्षक",
"EditView.completionLabel": "Completion",
- "EditView.copyrightHolderLabel": "Copyright holder",
+ "EditView.copyrightHolderLabel": "कॉपीराइट धारक",
"EditView.details": "विवरण",
"EditView.editingMultipleCount": "Editing details for {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}",
"EditView.errorBannerText": "Please provide the required information",
"EditView.invalidFieldsToolTip": "Some required information is missing",
"EditView.learningActivityLabel": "Learning activity",
- "EditView.licenseLabel": "License",
+ "EditView.licenseLabel": "लाइसेंस",
"EditView.noItemsToEditText": "Please select resources or folders to edit",
"EditView.preview": "पूर्वावलोकन",
"EditView.questions": "प्रश्न",
"EditView.related": "संबंधित",
- "EditView.titleLabel": "Title",
+ "EditView.titleLabel": "शीर्षक",
"EmailField.emailLabel": "Email",
"EmailField.validEmailMessage": "Please enter a valid email",
"ExpandableList.less": "कम दिखाएँ",
@@ -959,14 +1086,22 @@
"FileStorage.storageLow": "Storage is running low",
"FileStorage.storageLowWithSize": "Total storage is running low: {used} of {total}",
"FileStorage.storageUsed": "Total storage used: {used} of {total}",
+ "FileUpload.cancelButton": "रद्द करें",
"FileUpload.fileError": "Unsupported file type",
- "FileUpload.filesHeader": "Preview files",
+ "FileUpload.filesHeader": "Files",
"FileUpload.noFileText": "Missing files",
+ "FileUpload.removeFile": "Remove file",
+ "FileUpload.removeFileDescription": "Once this file is removed, this resource will only be able to include a single file from the formats: { fileTypes }. Are you sure you want to continue?",
+ "FileUpload.yesButton": "हाँ",
"FileUploadDefault.acceptsHelp": "Supported file types: {extensions}",
"FileUploadDefault.chooseFilesButton": "Select files",
"FileUploadDefault.dropHereText": "Drag and drop your files here, or select your files manually",
"FileUploadDefault.uploadToText": "Upload to '{title}'",
+ "FileUploadItem.downloadFailed": "Failed to download file",
+ "FileUploadItem.downloadMenuOptionLabel": "डाउनलोड करें",
"FileUploadItem.removeFileButton": "हटा दें",
+ "FileUploadItem.removeMenuOptionLabel": "हटा दें",
+ "FileUploadItem.replaceFileMenuOptionLabel": "Replace file",
"FileUploadItem.retryUpload": "Retry upload",
"FileUploadItem.unknownFile": "Unknown filename",
"FileUploadItem.uploadButton": "Select file",
@@ -975,8 +1110,183 @@
"ForgotPassword.forgotPasswordPrompt": "Please enter your email address to receive instructions for resetting your password",
"ForgotPassword.forgotPasswordTitle": "Reset your password",
"ForgotPassword.submitButton": "Submit",
- "FormulasMenu.btnLabelInsert": "Insert",
- "FormulasMenu.formulasMenuTitle": "Special characters",
+ "FormulasStrings.addition": "Addition",
+ "FormulasStrings.advancedCategory": "उन्नत",
+ "FormulasStrings.alpha": "alpha",
+ "FormulasStrings.and": "And",
+ "FormulasStrings.angle": "Angle",
+ "FormulasStrings.approximately": "Approximately",
+ "FormulasStrings.bar": "Bar",
+ "FormulasStrings.basicCategory": "Basic",
+ "FormulasStrings.because": "Because",
+ "FormulasStrings.beta": "beta",
+ "FormulasStrings.binomialCoefficient": "Binomial coefficient",
+ "FormulasStrings.cardinality": "Cardinality",
+ "FormulasStrings.charactersCategory": "Characters",
+ "FormulasStrings.chi": "chi",
+ "FormulasStrings.circle": "Circle",
+ "FormulasStrings.club": "Club",
+ "FormulasStrings.congruentTo": "Congruent to",
+ "FormulasStrings.conjugate": "Conjugate",
+ "FormulasStrings.conjugateTranspose": "Conjugate transpose",
+ "FormulasStrings.contains": "Contains",
+ "FormulasStrings.contourIntegral": "Contour integral",
+ "FormulasStrings.coproduct": "Coproduct",
+ "FormulasStrings.definition": "Definition",
+ "FormulasStrings.degrees": "Degrees",
+ "FormulasStrings.delta": "delta",
+ "FormulasStrings.deltaCapital": "Delta",
+ "FormulasStrings.diamond": "Diamond",
+ "FormulasStrings.digamma": "digamma",
+ "FormulasStrings.directionalCategory": "Directional",
+ "FormulasStrings.division": "Division",
+ "FormulasStrings.doesNotEqual": "Does not equal",
+ "FormulasStrings.dot": "Dot",
+ "FormulasStrings.down": "Down",
+ "FormulasStrings.downDouble": "Down (double)",
+ "FormulasStrings.ell": "ell",
+ "FormulasStrings.ellipsis": "Ellipsis",
+ "FormulasStrings.ellipsisCentered": "Ellipsis (centered)",
+ "FormulasStrings.ellipsisDiagonal": "Ellipsis (diagonal)",
+ "FormulasStrings.ellipsisVertical": "Ellipsis (vertical)",
+ "FormulasStrings.emptySet": "Empty set",
+ "FormulasStrings.entails": "Entails",
+ "FormulasStrings.epsilon": "epsilon",
+ "FormulasStrings.eta": "eta",
+ "FormulasStrings.exclusiveOr": "Exclusive or",
+ "FormulasStrings.exists": "Exists",
+ "FormulasStrings.flat": "Flat",
+ "FormulasStrings.forAll": "For all",
+ "FormulasStrings.formulasCategory": "Formulas",
+ "FormulasStrings.fraction": "Fraction",
+ "FormulasStrings.gamma": "gamma",
+ "FormulasStrings.gammaCapital": "Gamma",
+ "FormulasStrings.geometryCategory": "रेखागणित",
+ "FormulasStrings.givenThat": "Given that/Such that",
+ "FormulasStrings.greaterThan": "Greater than",
+ "FormulasStrings.greaterThanOrEqual": "Greater than or equal to",
+ "FormulasStrings.heart": "Heart",
+ "FormulasStrings.ifAndOnlyIf": "If and only if",
+ "FormulasStrings.implies": "Implies",
+ "FormulasStrings.in": "In",
+ "FormulasStrings.incomparableTo": "Incomparable to",
+ "FormulasStrings.infinity": "Infinity",
+ "FormulasStrings.integral": "Integral",
+ "FormulasStrings.intersection": "Intersection",
+ "FormulasStrings.iota": "iota",
+ "FormulasStrings.kappa": "kappa",
+ "FormulasStrings.lambda": "lambda",
+ "FormulasStrings.lambdaCapital": "Lambda",
+ "FormulasStrings.left": "Left",
+ "FormulasStrings.leftArrow": "बायां तीर",
+ "FormulasStrings.leftCeiling": "Left ceiling",
+ "FormulasStrings.leftDouble": "Left (double)",
+ "FormulasStrings.leftFloor": "Left floor",
+ "FormulasStrings.leftHarpoonDown": "Left (harpoon down)",
+ "FormulasStrings.leftHarpoonUp": "Left (harpoon up)",
+ "FormulasStrings.leftHooked": "Left (hooked)",
+ "FormulasStrings.leftLong": "Left (long)",
+ "FormulasStrings.leftLongDouble": "Left (long, double)",
+ "FormulasStrings.leftRight": "Left-right",
+ "FormulasStrings.leftRightDouble": "Left-right (double)",
+ "FormulasStrings.leftRightLong": "Left-right (long)",
+ "FormulasStrings.leftRightLongDouble": "Left-right (long, double)",
+ "FormulasStrings.lessThan": "Less than",
+ "FormulasStrings.lessThanOrEqual": "Less than or equal to",
+ "FormulasStrings.linesCategory": "Lines",
+ "FormulasStrings.logicCategory": "Logic",
+ "FormulasStrings.measuredAngle": "Measured angle",
+ "FormulasStrings.minusPlus": "Minus-plus",
+ "FormulasStrings.miscellaneousCategory": "Miscellaneous",
+ "FormulasStrings.mu": "mu",
+ "FormulasStrings.multiplication": "Multiplication",
+ "FormulasStrings.nabla": "Nabla",
+ "FormulasStrings.natural": "Natural",
+ "FormulasStrings.naturalJoin": "Natural join",
+ "FormulasStrings.negation": "Negation",
+ "FormulasStrings.nondominatedBy": "Nondominated by",
+ "FormulasStrings.northeast": "Northeast",
+ "FormulasStrings.northwest": "Northwest",
+ "FormulasStrings.notGreaterThan": "Not greater than",
+ "FormulasStrings.notIn": "Not in",
+ "FormulasStrings.notLessThan": "Not less than",
+ "FormulasStrings.notSubsetOrEqual": "Not a subset or equal",
+ "FormulasStrings.notSupersetOrEqual": "Not a superset or equal",
+ "FormulasStrings.nu": "nu",
+ "FormulasStrings.omega": "omega",
+ "FormulasStrings.omegaCapital": "Omega",
+ "FormulasStrings.or": "Or",
+ "FormulasStrings.pWeierstrass": "P",
+ "FormulasStrings.parallel": "Parallel",
+ "FormulasStrings.partial": "Partial",
+ "FormulasStrings.perpendicular": "Perpendicular",
+ "FormulasStrings.phi": "phi",
+ "FormulasStrings.phiCapital": "Phi",
+ "FormulasStrings.pi": "पाई",
+ "FormulasStrings.piCapital": "पाई",
+ "FormulasStrings.planckConstant": "Planck's constant",
+ "FormulasStrings.plusMinus": "Plus-minus",
+ "FormulasStrings.product": "Product",
+ "FormulasStrings.proportional": "Proportional",
+ "FormulasStrings.psi": "psi",
+ "FormulasStrings.psiCapital": "Psi",
+ "FormulasStrings.qed": "QED",
+ "FormulasStrings.reducibleTo": "Reducible to",
+ "FormulasStrings.rho": "rho",
+ "FormulasStrings.right": "Right",
+ "FormulasStrings.rightArrow": "दायां तीर",
+ "FormulasStrings.rightCeiling": "Right ceiling",
+ "FormulasStrings.rightDouble": "Right (double)",
+ "FormulasStrings.rightFloor": "Right floor",
+ "FormulasStrings.rightHarpoonDown": "Right (harpoon down)",
+ "FormulasStrings.rightHarpoonUp": "Right (harpoon up)",
+ "FormulasStrings.rightHooked": "Right (hooked)",
+ "FormulasStrings.rightLong": "Right (long)",
+ "FormulasStrings.rightLongDouble": "Right (long, double)",
+ "FormulasStrings.setDifference": "Set difference",
+ "FormulasStrings.setsCategory": "Sets",
+ "FormulasStrings.sharp": "Sharp",
+ "FormulasStrings.sigma": "sigma",
+ "FormulasStrings.sigmaCapital": "Sigma",
+ "FormulasStrings.significantlyGreaterThan": "Significantly greater than",
+ "FormulasStrings.significantlyLessThan": "Significantly less than",
+ "FormulasStrings.similarOrEqual": "Similar or equal to",
+ "FormulasStrings.similarTo": "Similar to",
+ "FormulasStrings.southeast": "Southeast",
+ "FormulasStrings.southwest": "Southwest",
+ "FormulasStrings.spade": "Spade",
+ "FormulasStrings.squareRoot": "वर्ग मूल",
+ "FormulasStrings.subscript": "Subscript",
+ "FormulasStrings.subset": "Subset",
+ "FormulasStrings.subsetOrEqual": "Subset or equal",
+ "FormulasStrings.subtraction": "Subtraction",
+ "FormulasStrings.sum": "Sum",
+ "FormulasStrings.superscript": "सुपरस्क्रिप्ट",
+ "FormulasStrings.superset": "Superset",
+ "FormulasStrings.supersetOrEqual": "Superset or equal",
+ "FormulasStrings.symmetricDifference": "Symmetric difference",
+ "FormulasStrings.tau": "tau",
+ "FormulasStrings.tensorProduct": "Tensor product",
+ "FormulasStrings.therefore": "Therefore",
+ "FormulasStrings.theta": "थीटा",
+ "FormulasStrings.thetaCapital": "थीटा",
+ "FormulasStrings.topElement": "Top element",
+ "FormulasStrings.triangleDown": "Triangle down",
+ "FormulasStrings.triangleUp": "Triangle up",
+ "FormulasStrings.underline": "Underline",
+ "FormulasStrings.union": "Union",
+ "FormulasStrings.up": "Up",
+ "FormulasStrings.upDouble": "Up (double)",
+ "FormulasStrings.upDown": "Up-down",
+ "FormulasStrings.upDownDouble": "Up-down (double)",
+ "FormulasStrings.upsilon": "upsilon",
+ "FormulasStrings.upsilonCapital": "Upsilon",
+ "FormulasStrings.vector": "Vector",
+ "FormulasStrings.wedgeProduct": "Wedge product",
+ "FormulasStrings.wreathProduct": "Wreath product",
+ "FormulasStrings.xi": "xi",
+ "FormulasStrings.xiCapital": "Xi",
+ "FormulasStrings.zeta": "zeta",
"FullNameForm.cancelAction": "रद्द करें",
"FullNameForm.changesSavedMessage": "परिवर्तन सहेजे गए",
"FullNameForm.editNameHeader": "Edit full name",
@@ -994,35 +1304,24 @@
"HintsEditor.newHintBtnLabel": "New hint",
"HintsEditor.noHintsPlaceholder": "Question has no hints",
"ImageOnlyThumbnail.thumbnail": "{title} thumbnail",
- "ImagesMenu.acceptsText": "Supported file types: {acceptedFormats}",
- "ImagesMenu.altTextHint": "The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
- "ImagesMenu.altTextLabel": "Image description",
- "ImagesMenu.btnLabelCancel": "रद्द करें",
- "ImagesMenu.btnLabelInsert": "Insert",
- "ImagesMenu.currentImageDefaultText": "Current image",
- "ImagesMenu.defaultDropText": "Drag and drop an image here, or upload manually",
- "ImagesMenu.imageHeader": "Upload image",
- "ImagesMenu.selectFile": "Select file",
- "ImagesMenu.selectFileButton": "Select file",
"ImportFromChannelsModal.addButton": "जोड़ें",
"ImportFromChannelsModal.addedText": "जोड़ा गया",
"ImportFromChannelsModal.importAction": "आयात करें",
"ImportFromChannelsModal.importTitle": "Import from other channels",
"ImportFromChannelsModal.removeButton": "हटा दें",
- "ImportFromChannelsModal.resourcesAddedSnackbar": "{count, number} {count, plural, one {resource selected} other {resources selected}}",
- "ImportFromChannelsModal.resourcesRemovedSnackbar": "{count, number} {count, plural, one {संसाधन हटाया गया} other {संसाधन हटाए गए}} ",
"ImportFromChannelsModal.resourcesSelected": "{count, number} {count, plural, one {resource selected} other {resources selected}}",
"ImportFromChannelsModal.reviewAction": "Review",
"ImportFromChannelsModal.reviewTitle": "Resource selection",
"InfoModal.close": "बंद करें",
+ "InfoModal.open": "More information about licenses",
"InheritAncestorMetadataModal.applyResourceDetailsTitle": "Apply details from the folder '{folder}'",
- "InheritAncestorMetadataModal.cancelAction": "Cancel",
+ "InheritAncestorMetadataModal.cancelAction": "रद्द करें",
"InheritAncestorMetadataModal.categories": "Categories: {categories}",
- "InheritAncestorMetadataModal.continueAction": "Continue",
+ "InheritAncestorMetadataModal.continueAction": "जारी रखें",
"InheritAncestorMetadataModal.doNotShowAgainDescription": "All future additions to this folder will have the selected details by default",
"InheritAncestorMetadataModal.doNotShowThisAgain": "Don't ask me about this folder again",
"InheritAncestorMetadataModal.inheritMetadataDescription": "Select details to add:",
- "InheritAncestorMetadataModal.language": "Language: {language}",
+ "InheritAncestorMetadataModal.language": "भाषा: {language}",
"InheritAncestorMetadataModal.learnerNeeds": "Requirements: {learnerNeeds}",
"InheritAncestorMetadataModal.levels": "Levels: {levels}",
"InheritAncestorMetadataModal.updateLanguage": "Update language:",
@@ -1039,37 +1338,56 @@
"LicenseDropdown.licenseDescriptionLabel": "License description",
"LicenseDropdown.licenseLabel": "लाइसेंस",
"LicenseDropdown.mixed": "Mixed",
- "Main.TOSLink": "Terms of service",
- "Main.copyright": "© {year} Learning Equality",
- "Main.createAccountButton": "अकाउंट बनाएँ",
- "Main.forgotPasswordLink": "Forgot your password?",
- "Main.guestModeLink": "Explore without an account",
- "Main.kolibriStudio": "Kolibri स्टूडियो",
- "Main.loginFailed": "Email or password is incorrect",
- "Main.loginFailedOffline": "You seem to be offline. Please connect to the internet before signing in.",
- "Main.loginToProceed": "You must sign in to view that page",
- "Main.passwordLabel": "पासवर्ड",
- "Main.privacyPolicyLink": "Privacy policy",
- "Main.signInButton": "साइन इन करें",
"MainNavigationDrawer.administrationLink": "Administration",
- "MainNavigationDrawer.changeLanguage": "Change language",
+ "MainNavigationDrawer.changeLanguage": "भाषा बदलें",
"MainNavigationDrawer.channelsLink": "चैनल",
"MainNavigationDrawer.copyright": "© {year} Learning Equality",
"MainNavigationDrawer.giveFeedback": "Give feedback",
"MainNavigationDrawer.helpLink": "Help and support",
"MainNavigationDrawer.logoutLink": "साइन आउट करें",
"MainNavigationDrawer.settingsLink": "सेटिंग",
- "MarkdownEditor.bold": "Bold (Ctrl+B)",
- "MarkdownEditor.formulas": "Insert formula (Ctrl+F)",
- "MarkdownEditor.image": "Insert image (Ctrl+P)",
- "MarkdownEditor.italic": "Italic (Ctrl+I)",
- "MarkdownEditor.minimize": "Minimize (Ctrl+M)",
- "MarkdownImageField.editImageOption": "संपादित करें (एडिट)",
- "MarkdownImageField.removeImageOption": "हटा दें",
- "MarkdownImageField.resizeImageOption": "Resize",
"MasteryCriteriaGoal.labelText": "Goal",
"MasteryCriteriaMofNFields.mHint": "Correct answers needed",
"MasteryCriteriaMofNFields.nHint": "Recent answers",
+ "MathLiveA11yStrings.accented": "accented",
+ "MathLiveA11yStrings.array": "array",
+ "MathLiveA11yStrings.box": "box",
+ "MathLiveA11yStrings.chemicalFormula": "chemical formula",
+ "MathLiveA11yStrings.crossOut": "क्रॉस करें",
+ "MathLiveA11yStrings.deleted": "हटाए गए: ",
+ "MathLiveA11yStrings.delimiter": "delimiter",
+ "MathLiveA11yStrings.denominator": "denominator",
+ "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}",
+ "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield",
+ "MathLiveA11yStrings.error": "त्रुटि",
+ "MathLiveA11yStrings.extensibleSymbol": "extensible symbol",
+ "MathLiveA11yStrings.first": "first",
+ "MathLiveA11yStrings.fraction": "fraction",
+ "MathLiveA11yStrings.group": "group",
+ "MathLiveA11yStrings.index": "index",
+ "MathLiveA11yStrings.latex": "LaTeX",
+ "MathLiveA11yStrings.line": "रेखा",
+ "MathLiveA11yStrings.mathField": "math field",
+ "MathLiveA11yStrings.mathfield": "math field",
+ "MathLiveA11yStrings.numerator": "numerator",
+ "MathLiveA11yStrings.operator": "operator",
+ "MathLiveA11yStrings.outOf": "out of {relationName};",
+ "MathLiveA11yStrings.overUnder": "over-under",
+ "MathLiveA11yStrings.parent": "parent",
+ "MathLiveA11yStrings.placeholder": "placeholder",
+ "MathLiveA11yStrings.prompt": "prompt",
+ "MathLiveA11yStrings.radicand": "radicand",
+ "MathLiveA11yStrings.rule": "rule",
+ "MathLiveA11yStrings.selected": "चयनित: ",
+ "MathLiveA11yStrings.space": "space",
+ "MathLiveA11yStrings.spacing": "spacing",
+ "MathLiveA11yStrings.squareRoot": "वर्ग मूल",
+ "MathLiveA11yStrings.startOf": "start of {relationName}: ",
+ "MathLiveA11yStrings.subscript": "subscript",
+ "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript",
+ "MathLiveA11yStrings.superscript": "सुपरस्क्रिप्ट",
+ "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript",
+ "MathLiveA11yStrings.text": "text",
"MessageLayout.backToLogin": "Continue to sign-in page",
"MoveModal.addTopic": "Add new folder",
"MoveModal.cancel": "रद्द करें",
@@ -1090,7 +1408,7 @@
"NodePanel.emptyChannelText": "Click \"ADD\" to start building your channel",
"NodePanel.emptyTopicText": "Nothing in this folder yet",
"NodePanel.emptyViewOnlyChannelText": "Nothing in this channel yet",
- "NodePanel.showMore": "Show more",
+ "NodePanel.showMore": "और दिखाएँ",
"NodeTreeNavigation.noResourcesDefaultText": "No resources found",
"OfflineText.offlineIndicatorText": "Offline",
"OfflineText.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.",
@@ -1100,7 +1418,7 @@
"PasswordField.passwordLabel": "पासवर्ड",
"PasswordInstructionsSent.passwordInstructionsHeader": "Instructions sent. Thank you!",
"PasswordInstructionsSent.passwordInstructionsText": "If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.",
- "PermissionsError.goToHomePageAction": "होमपेज पर जाएं",
+ "PermissionsError.backToHome": "होमपेज पर वापिस जाएं",
"PermissionsError.permissionDeniedHeader": "क्या आप साइन इन करना भूल गए?",
"PoliciesModal.checkboxText": "I have agreed to the above terms",
"PoliciesModal.closeButton": "बंद करें",
@@ -1110,6 +1428,7 @@
"PrivacyPolicyModal.updatedPrivacyHeader": "Updated privacy policy",
"ProgressBar.progressText": "{percent}%",
"ProgressModal.defaultErrorText": "Last attempt to publish failed",
+ "ProgressModal.draftHeader": "Saving draft...",
"ProgressModal.lastPublished": "Published {last_published}",
"ProgressModal.publishHeader": "Publishing channel",
"ProgressModal.syncError": "Last attempt to sync failed",
@@ -1123,12 +1442,15 @@
"PublishModal.incompleteInstructions": "Click 'Continue' to confirm that you would like to publish anyway.",
"PublishModal.incompleteWarning": "Incomplete resources will not be published and made available for download in Kolibri.",
"PublishModal.languageDescriptionTooltip": "The default language for a channel and its resources",
- "PublishModal.languageLabel": "Language",
+ "PublishModal.languageLabel": "भाषा ",
"PublishModal.languageRequiredMessage": "Please select a language for this channel",
"PublishModal.nextButton": "जारी रखें",
"PublishModal.publishButton": "Publish",
"PublishModal.publishMessageLabel": "Describe what's new in this channel version",
"PublishModal.versionDescriptionLabel": "Version description",
+ "RecommendedResourceCard.goToLocationTooltip": "Go to location",
+ "RecommendedResourceCard.markNotRelevantTooltip": "Mark as not relevant",
+ "RecommendedResourceCard.selectCard": "Select { title }",
"RelatedResourcesList.removeBtnLabel": "हटा दें",
"RelatedResourcesTab.addNextStepBtnLabel": "Add next step",
"RelatedResourcesTab.addPreviousStepBtnLabel": "Add previous step",
@@ -1147,6 +1469,19 @@
"RelatedResourcesTab.showPreviewBtnLabel": "Show me",
"RelatedResourcesTab.tooManyNextStepsWarning": "Limit the number of next steps to create a more guided learning experience",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "Limit the number of previous steps to create a more guided learning experience",
+ "RemoveChannelFromListModal.cancel": "रद्द करें",
+ "RemoveChannelFromListModal.channelRemovedSnackbar": "Channel removed",
+ "RemoveChannelFromListModal.removeBtn": "हटा दें",
+ "RemoveChannelFromListModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.",
+ "RemoveChannelFromListModal.removeTitle": "Remove from channel list",
+ "RemoveChannelModal.cancel": "रद्द करें",
+ "RemoveChannelModal.deleteChannel": "चैनल हटाएँ",
+ "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.",
+ "RemoveChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
+ "RemoveChannelModal.deleteTitle": "Delete this channel",
+ "RemoveChannelModal.removeBtn": "हटा दें",
+ "RemoveChannelModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.",
+ "RemoveChannelModal.removeTitle": "Remove from channel list",
"ReportErrorModal.closeAction": "बंद करें",
"ReportErrorModal.emailDescription": "अपने त्रुटि विवरण के साथ समर्थन टीम से संपर्क करें और हम मदद करने की पूरी कोशिश करेंगे ।",
"ReportErrorModal.emailPrompt": "डेवलपर को ई-मेल करें",
@@ -1179,7 +1514,7 @@
"RequestForm.oneWeekLabel": "1 week",
"RequestForm.organizationNamePlaceholder": "Organization name",
"RequestForm.organizationalAffiliationLabel": "Organizational affiliation",
- "RequestForm.otherLabel": "Other",
+ "RequestForm.otherLabel": "अन्य",
"RequestForm.pasteLinkPlaceholder": "Paste link here",
"RequestForm.provideSampleLinkLabel": "Please provide a link to a sample of your content (on Kolibri Studio or from source site)",
"RequestForm.requestFailed": "Unable to send request. Please try again.",
@@ -1251,12 +1586,14 @@
"ResourcePanel.resources": "संसाधन",
"ResourcePanel.showAnswers": "Show answers",
"ResourcePanel.source": "Source",
+ "ResourcePanel.sourceContentDoesntExist": "Source content no longer exists. Please contact your administrator.",
"ResourcePanel.subtitles": "Captions and subtitles",
"ResourcePanel.tags": "Tags",
"ResourcePanel.totalResources": "Total resources",
"ResourcePanel.visibleTo": "Visible to",
"ResourcesNeededOptions.furtherExplanation": "Please add to the 'Description' field any additional supplies learners will need in order to use this resource",
"ResourcesNeededOptions.resourcesNeededLabel": "Requirements",
+ "ReviewSelectionsPage.addResourcesToFolderLabel": "Add {count, number} {count, plural, one {resource} other {resources}} to '{folder}'",
"ReviewSelectionsPage.noResourcesSelected": "कोई भी संसाधन नहीं चुने गए है",
"ReviewSelectionsPage.removeAction": "हटा दें",
"ReviewSelectionsPage.resourcesInTopic": "{count, number} {count, plural, one {संसाधन} other {संसाधन}}",
@@ -1291,9 +1628,52 @@
"SearchOrBrowseWindow.backToBrowseAction": "Back to browse",
"SearchOrBrowseWindow.copiedToClipboard": "क्लिपबोर्ड पर कॉपी कर दिया गया",
"SearchOrBrowseWindow.copyFailed": "Failed to copy to clipboard",
+ "SearchOrBrowseWindow.jumpToRecommendations": "Jump to recommendations",
+ "SearchOrBrowseWindow.jumpToSearch": "Jump to search",
+ "SearchOrBrowseWindow.jumpToSearchResults": "Jump to search results",
+ "SearchOrBrowseWindow.jumpToTop": "Jump to top",
"SearchOrBrowseWindow.savedSearchesLabel": "View saved searches",
"SearchOrBrowseWindow.searchAction": "खोज",
"SearchOrBrowseWindow.searchLabel": "Search for resources…",
+ "SearchRecommendationsStrings.aboutRecommendationsDescription": "Our recommendation system uses the titles and descriptions of folders that you already have in your channel to show you other potentially relevant resources across the Kolibri Library.",
+ "SearchRecommendationsStrings.aboutRecommendationsFeedbackDescription": "Interacting with these recommendations, whether by using them or marking them as not relevant, will help us improve the quality of the recommendations you see.",
+ "SearchRecommendationsStrings.aboutRecommendationsText": "About recommendations",
+ "SearchRecommendationsStrings.alreadyUsedResourceLabel": "I already used this resource in my channel",
+ "SearchRecommendationsStrings.cancelAction": "रद्द करें",
+ "SearchRecommendationsStrings.closeAction": "बंद करें",
+ "SearchRecommendationsStrings.enterFeedbackLabel": "Enter your feedback",
+ "SearchRecommendationsStrings.feedbackConfirmationMessage": "Thank you! Your feedback will help us improve our recommendations.",
+ "SearchRecommendationsStrings.feedbackFailedMessage": "Feedback submission failed",
+ "SearchRecommendationsStrings.feedbackInputValidationMessage": "Please enter your feedback",
+ "SearchRecommendationsStrings.feedbackSubmittedMessage": "Feedback submitted",
+ "SearchRecommendationsStrings.giveFeedbackDescription": "Help us understand why this resource is not relevant for you right now. Check all that apply:",
+ "SearchRecommendationsStrings.giveFeedbackText": "Give feedback",
+ "SearchRecommendationsStrings.goToLocationText": "Go to location",
+ "SearchRecommendationsStrings.markAsNotRelevantTooltip": "Mark as not relevant",
+ "SearchRecommendationsStrings.noDirectMatchesMessage": "No direct matches found, but there are other resources that might be helpful",
+ "SearchRecommendationsStrings.noFeedbackSelectedErrorMessage": "Please select at least one option in order to submit your feedback",
+ "SearchRecommendationsStrings.notRelatedToSubjectLabel": "Not related to the subject or knowledge area I am trying to find resources for",
+ "SearchRecommendationsStrings.notRelevantAction": "Not relevant",
+ "SearchRecommendationsStrings.notSpecificLearningActivityLabel": "Not the type of learning activity I'm looking for (e.g. document instead of video, etc.)",
+ "SearchRecommendationsStrings.notSuitableForCulturalBackgroundLabel": "Not suitable for the cultural backgrounds and experiences of learners",
+ "SearchRecommendationsStrings.notSuitableForCurriculumLabel": "Not suitable for the curriculum I am using",
+ "SearchRecommendationsStrings.otherLabel": "अन्य",
+ "SearchRecommendationsStrings.problemShowingResourcesMessage": "There was a problem showing these resources",
+ "SearchRecommendationsStrings.resourceNotWellMadeLabel": "The resource doesn't look or sound well-made enough for use",
+ "SearchRecommendationsStrings.resourcesInChannelMightBeRelevantTitle": "These resources in { channelName } might be relevant to '{ topic }'",
+ "SearchRecommendationsStrings.resourcesMightBeRelevantTitle": "These resources might be relevant to '{ topic }'",
+ "SearchRecommendationsStrings.showOtherRecommendationsLink": "Show other recommendations",
+ "SearchRecommendationsStrings.showOtherResourcesLink": "Show other resources anyway",
+ "SearchRecommendationsStrings.showOtherResourcesMessage": "We can show you other resources, but they might not be what you're looking for",
+ "SearchRecommendationsStrings.submitAction": "Submit",
+ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Too advanced for the knowledge level of learners I'm looking for",
+ "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Too basic for the knowledge level of learners I'm looking for",
+ "SearchRecommendationsStrings.tryAgainLink": "दोबारा कोशिश करें",
+ "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!",
+ "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.",
+ "SearchRecommendationsStrings.undoAction": "अकृत करें",
+ "SearchRecommendationsStrings.viewMoreLink": "अधिक देखें",
+ "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations",
"SearchResultsList.failedToLoad": "Failed to load search results",
"SearchResultsList.resultsPerPageLabel": "Results per page",
"SearchResultsList.saveSearchAction": "Save search",
@@ -1305,19 +1685,25 @@
"SettingsIndex.usingStudioLabel": "About Studio",
"StagingTreePage.backToViewing": "Back to viewing",
"StagingTreePage.cancelDeployBtn": "रद्द करें",
+ "StagingTreePage.cancelPublishDraftBtn": "रद्द करें",
"StagingTreePage.channelDeployed": "Channel has been deployed",
"StagingTreePage.closeSummaryDetailsDialogBtn": "बंद करें",
- "StagingTreePage.collapseAllButton": "Collapse all",
+ "StagingTreePage.collapseAllButton": "सभी को संक्षिप्त करें",
"StagingTreePage.confirmDeployBtn": "Deploy channel",
+ "StagingTreePage.confirmPublishDraftBtn": "Publish",
"StagingTreePage.deploy": "Deploy",
"StagingTreePage.deployChannel": "Deploy channel",
"StagingTreePage.deployDialogDescription": "You are about to replace all live resources with staged resources.",
+ "StagingTreePage.draftPublished": "Draft channel has been published",
"StagingTreePage.emptyChannelSubText": "No changes to review! The channel contains all the most recent folders and resources.",
"StagingTreePage.emptyChannelText": "No resources found",
"StagingTreePage.emptyTopicText": "This topic is empty",
"StagingTreePage.liveResources": "Live resources",
"StagingTreePage.openCurrentLocationButton": "Expand to current folder location",
"StagingTreePage.openSummaryDetailsDialogBtn": "View summary",
+ "StagingTreePage.publishDraft": "Publish draft",
+ "StagingTreePage.publishDraftDialogDescription": "You are about to publish a draft of your staged changes.",
+ "StagingTreePage.publishDraftError": "An error occurred while publishing the draft channel",
"StagingTreePage.resourcesCount": "{count, number} {count, plural, one { resource } other { resources }}",
"StagingTreePage.reviewMode": "Review mode",
"StagingTreePage.stagedResources": "Staged resources",
@@ -1329,15 +1715,133 @@
"StatusStrings.noStorageError": "Not enough space",
"StatusStrings.uploadFailedError": "Upload failed",
"StatusStrings.uploadFileSize": "{uploaded} of {total}",
+ "Storage.beforeRequestingHeading": "Before requesting additional storage space, please note the following:",
+ "Storage.beforeRequestingMessage": "The resources you import from our Kolibri Library to your channels do not count towards your storage limit.",
"Storage.hideFormAction": "Close form",
- "Storage.learnMoreAboutImportingContentFromChannels": "Learn more about how to import resources from other channels",
+ "Storage.largerStorageRequestPricing": "For larger storage requests, there will be associated costs to help Learning Equality cover hosting, maintenance, and administrative expenses. We are happy to discuss possible rates based on your needs.",
+ "Storage.learnMoreAboutImportingContentFromChannels": "Learn more about how to import resources from other channels.",
+ "Storage.learnMoreAboutVideoCompression": "Learn more about how to compress video resources.",
"Storage.requestMoreSpaceHeading": "Request more space",
- "Storage.requestMoreSpaceMessage": "Please use this form to request additional uploading storage for your Kolibri Studio account. The resources you import from our public library to your channels do not count towards your storage limit.",
+ "Storage.requestMoreSpaceMessage": "Please use this form to request additional uploading storage for your Kolibri Studio account.",
"Storage.showFormAction": "Open form",
"Storage.spaceUsedOfMax": "{qty} of {max}",
"Storage.storagePercentageUsed": "{qty}% storage used",
+ "Storage.videoFiles": "Video files should be compressed to maximize storage space and ensure smooth offline distribution and playback. Once compressed, the total storage required may be less than what you originally estimated.",
+ "StudioChannelCard.channelLanguageNotSetIndicator": "No language set",
+ "StudioChannelCard.details": "विवरण",
+ "StudioChannelCard.lastPublished": "Published {last_published}",
+ "StudioChannelCard.lastUpdated": "Updated {updated}",
+ "StudioChannelCard.multipleCountries": "Multiple countries",
+ "StudioChannelCard.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}",
+ "StudioChannelCard.selectChannel": "Select {name}",
+ "StudioChannelCard.unpublishedText": "Unpublished",
+ "StudioChannelsPage.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}",
+ "StudioChannelsPage.noChannelsFound": "No channels found",
+ "StudioCollectionsTable.aboutChannelSets": "About collections",
+ "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections",
+ "StudioCollectionsTable.addChannelSetTitle": "New collection",
+ "StudioCollectionsTable.cancel": "रद्द करें",
+ "StudioCollectionsTable.cancelButtonLabel": "बंद करें",
+ "StudioCollectionsTable.channelNumber": "Number of channels",
+ "StudioCollectionsTable.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.",
+ "StudioCollectionsTable.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections",
+ "StudioCollectionsTable.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.",
+ "StudioCollectionsTable.collectionDeleted": "Collection deleted",
+ "StudioCollectionsTable.copiedTokenId": "Token copied",
+ "StudioCollectionsTable.copyFailed": "Copy failed",
+ "StudioCollectionsTable.copyToken": "Copy token",
+ "StudioCollectionsTable.delete": "Delete collection",
+ "StudioCollectionsTable.deleteChannelSetText": "Are you sure you want to delete this collection?",
+ "StudioCollectionsTable.deleteChannelSetTitle": "Delete collection",
+ "StudioCollectionsTable.deleteError": "Error deleting collection",
+ "StudioCollectionsTable.edit": "Edit collection",
+ "StudioCollectionsTable.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.",
+ "StudioCollectionsTable.options": "विकल्प",
+ "StudioCollectionsTable.pageTitle": "Collections",
+ "StudioCollectionsTable.saving": "Saving",
+ "StudioCollectionsTable.tableCaption": "List of collections",
+ "StudioCollectionsTable.title": "Collection name",
+ "StudioCollectionsTable.token": "Token ID",
+ "StudioCopyToken.copiedTokenId": "Token copied",
+ "StudioCopyToken.copyFailed": "Copy failed",
+ "StudioCopyToken.token": "Token",
+ "StudioCopyToken.tooltipText": "Copy token to import channel into Kolibri",
+ "StudioDetailsPanel.AVERAGE": "Average",
+ "StudioDetailsPanel.LARGE": "Large",
+ "StudioDetailsPanel.SMALL": "Small",
+ "StudioDetailsPanel.VERY_LARGE": "Very large",
+ "StudioDetailsPanel.VERY_SMALL": "Very small",
+ "StudioDetailsPanel.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder",
+ "StudioDetailsPanel.aggregatorsLabel": "Aggregators",
+ "StudioDetailsPanel.assessmentsIncludedText": "Assessments",
+ "StudioDetailsPanel.authorToolTip": "Person or organization who created this content",
+ "StudioDetailsPanel.authorsLabel": "Authors",
+ "StudioDetailsPanel.categoriesHeading": "श्रेणियाँ",
+ "StudioDetailsPanel.coachDescription": "Resources for coaches are only visible to coaches in Kolibri",
+ "StudioDetailsPanel.coachHeading": "Resources for coaches",
+ "StudioDetailsPanel.containsContentHeading": "Contains content from",
+ "StudioDetailsPanel.containsHeading": "Contains",
+ "StudioDetailsPanel.copyrightHoldersLabel": "Copyright holders",
+ "StudioDetailsPanel.creationHeading": "Created on",
+ "StudioDetailsPanel.currentVersionHeading": "Published version",
+ "StudioDetailsPanel.languagesHeading": "भाषाएँ",
+ "StudioDetailsPanel.levelsHeading": "Levels",
+ "StudioDetailsPanel.licensesLabel": "Licenses",
+ "StudioDetailsPanel.primaryLanguageHeading": "Primary language",
+ "StudioDetailsPanel.providerToolTip": "Organization that commissioned or is distributing the content",
+ "StudioDetailsPanel.providersLabel": "Providers",
+ "StudioDetailsPanel.publishedHeading": "Published on",
+ "StudioDetailsPanel.resourceHeading": "Total resources",
+ "StudioDetailsPanel.sampleFromChannelHeading": "Sample content from this channel",
+ "StudioDetailsPanel.sizeHeading": "Channel size",
+ "StudioDetailsPanel.sizeText": "{text} ({size})",
+ "StudioDetailsPanel.subtitlesHeading": "Captions and subtitles",
+ "StudioDetailsPanel.tagsHeading": "Common tags",
+ "StudioDetailsPanel.tokenHeading": "चैनल टोकन",
+ "StudioDetailsPanel.unpublishedText": "Unpublished",
+ "StudioImmersiveModal.close": "बंद करें",
+ "StudioMessageLayout.backToLogin": "Continue to sign-in page",
+ "StudioMyChannels.copyToken": "Copy channel token",
+ "StudioMyChannels.deleteChannel": "चैनल हटाएँ",
+ "StudioMyChannels.editChannel": "Edit channel details",
+ "StudioMyChannels.goToWebsite": "Go to source website",
+ "StudioMyChannels.moreOptions": "अधिक विकल्प",
+ "StudioMyChannels.newChannel": "New channel",
+ "StudioMyChannels.title": "My channels",
+ "StudioMyChannels.viewContent": "View channel on Kolibri",
+ "StudioOfflineAlert.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.",
+ "StudioOfflineAlert.onlineText": "You are back online.",
+ "StudioStarredChannels.copyToken": "Copy channel token",
+ "StudioStarredChannels.deleteChannel": "चैनल हटाएँ",
+ "StudioStarredChannels.editChannel": "Edit channel details",
+ "StudioStarredChannels.goToWebsite": "Go to source website",
+ "StudioStarredChannels.moreOptions": "अधिक विकल्प",
+ "StudioStarredChannels.removeChannel": "Remove channel",
+ "StudioStarredChannels.title": "Starred channels",
+ "StudioStarredChannels.viewContent": "View channel on Kolibri",
"StudioTree.missingTitle": "Missing title",
"StudioTree.optionsTooltip": "विकल्प",
+ "StudioViewOnlyChannels.copyToken": "Copy channel token",
+ "StudioViewOnlyChannels.goToWebsite": "Go to source website",
+ "StudioViewOnlyChannels.moreOptions": "अधिक विकल्प",
+ "StudioViewOnlyChannels.removeChannel": "Remove channel",
+ "StudioViewOnlyChannels.title": "View-only channels",
+ "StudioViewOnlyChannels.viewContent": "View channel on Kolibri",
+ "SubscriptionCard.annualPrice": "${price, number}/year",
+ "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.",
+ "SubscriptionCard.dismiss": "ख़ारिज करें",
+ "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.",
+ "SubscriptionCard.instantUpgrade": "Upgrade storage now",
+ "SubscriptionCard.manageSubscription": "Manage subscription",
+ "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.",
+ "SubscriptionCard.storageAmount": "Storage (GB)",
+ "SubscriptionCard.storageIncluded": "{size} included in your subscription",
+ "SubscriptionCard.storageRange": "Enter a value between 1 and 50",
+ "SubscriptionCard.subscriptionActive": "Storage subscription active",
+ "SubscriptionCard.subscriptionCanceling": "Subscription cancelled",
+ "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.",
+ "SubscriptionCard.upgradeNow": "Upgrade now",
+ "SubscriptionCard.upgradeSuccess": "Storage increased to {size}",
"SubtitlesList.acceptedFormatsTooltip": "Supported formats: {extensions}",
"SubtitlesList.addSubtitleText": "Add captions",
"SubtitlesList.subtitlesHeader": "Captions and subtitles",
@@ -1366,7 +1870,6 @@
"TechnicalTextBlock.copiedToClipboardConfirmation": "क्लिपबोर्ड पर कॉपी कर दिया गया",
"TechnicalTextBlock.copiedToClipboardFailure": "Copy to clipboard failed",
"TechnicalTextBlock.copyToClipboardButtonPrompt": "क्लिपबोर्ड पर कॉपी करें",
- "Template.templateString": "You have {count, plural,\n =1 {# node for testing}\n other {# nodes for testing}}",
"TermsOfServiceModal.ToSHeader": "Terms of Service",
"TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions",
"TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;",
@@ -1462,9 +1965,101 @@
"TextArea.fieldRequiredMessage": "Field is required",
"TextField.fieldRequiredMessage": "Field is required",
"Thumbnail.thumbnail": "{title} thumbnail",
+ "ThumbnailGenerator.closeButtonLabel": "OK",
"ThumbnailGenerator.generatedDefaultFilename": "Generated thumbnail",
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "Unable to generate thumbnail",
"ThumbnailGenerator.thumbnailGenerationFailedText": "There was a problem generating a thumbnail",
+ "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing",
+ "TipTapEditorStrings.TipTapViewerLabel": "text editor content",
+ "TipTapEditorStrings.addLink": "Add link",
+ "TipTapEditorStrings.alignLeft": "Align left",
+ "TipTapEditorStrings.alignRight": "Align right",
+ "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
+ "TipTapEditorStrings.altTextLabel": "Alt text (Optional)",
+ "TipTapEditorStrings.altTextPlaceholder": "Describe your image...",
+ "TipTapEditorStrings.bold": "Strong",
+ "TipTapEditorStrings.bulletList": "Bullet list",
+ "TipTapEditorStrings.cancel": "रद्द करें",
+ "TipTapEditorStrings.cancelLoading": "Cancel loading",
+ "TipTapEditorStrings.clearFormatting": "Clear formatting",
+ "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.",
+ "TipTapEditorStrings.close": "बंद करें",
+ "TipTapEditorStrings.closeModal": "Close modal",
+ "TipTapEditorStrings.codeBlock": "Code block",
+ "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar",
+ "TipTapEditorStrings.copy": "कॉपी बनाएँ",
+ "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions",
+ "TipTapEditorStrings.copyLink": "Copy link",
+ "TipTapEditorStrings.decreaseFormatSize": "Decrease format size",
+ "TipTapEditorStrings.defaultImageName": "Image",
+ "TipTapEditorStrings.edit": "संपादित करें (एडिट)",
+ "TipTapEditorStrings.editImage": "Edit image",
+ "TipTapEditorStrings.editLink": "Edit link",
+ "TipTapEditorStrings.editorControls": "Editor controls",
+ "TipTapEditorStrings.errorUploadingImage": "Error uploading image",
+ "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar",
+ "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.",
+ "TipTapEditorStrings.fileSizeUnit": "MB.",
+ "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ",
+ "TipTapEditorStrings.formatHeader1": "Header 1",
+ "TipTapEditorStrings.formatHeader2": "Header 2",
+ "TipTapEditorStrings.formatHeader3": "Header 3",
+ "TipTapEditorStrings.formatNormal": "Normal",
+ "TipTapEditorStrings.formatOptions": "Format options",
+ "TipTapEditorStrings.formatSize": "Format size",
+ "TipTapEditorStrings.formatSmall": "Small",
+ "TipTapEditorStrings.formulasMenuTitle": "Special Characters",
+ "TipTapEditorStrings.goToLink": "Go to link",
+ "TipTapEditorStrings.historyActions": "History actions",
+ "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually",
+ "TipTapEditorStrings.imagePreview": "Image preview",
+ "TipTapEditorStrings.increaseFormatSize": "Increase format size",
+ "TipTapEditorStrings.insert": "Insert",
+ "TipTapEditorStrings.insertContent": "Insert content",
+ "TipTapEditorStrings.insertContentMenu": "Insert content menu",
+ "TipTapEditorStrings.insertContentOption": "Insert content option",
+ "TipTapEditorStrings.insertImage": "Insert image",
+ "TipTapEditorStrings.insertLink": "Insert link",
+ "TipTapEditorStrings.insertTools": "Insert tools",
+ "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ",
+ "TipTapEditorStrings.italic": "Italic",
+ "TipTapEditorStrings.link": "Link",
+ "TipTapEditorStrings.linkActions": "Link actions",
+ "TipTapEditorStrings.listFormatting": "List formatting",
+ "TipTapEditorStrings.loadingFormulas": "Loading math editor",
+ "TipTapEditorStrings.mathFormula": "Math formula",
+ "TipTapEditorStrings.moreButtonText": "और",
+ "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.",
+ "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.",
+ "TipTapEditorStrings.noFileProvided": "No file provided.",
+ "TipTapEditorStrings.numberedList": "Numbered list",
+ "TipTapEditorStrings.opensInNewTab": "(opens in new tab)",
+ "TipTapEditorStrings.paste": "Paste",
+ "TipTapEditorStrings.pasteOptions": "Paste options",
+ "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu",
+ "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting",
+ "TipTapEditorStrings.redo": "Redo",
+ "TipTapEditorStrings.remove": "हटा दें",
+ "TipTapEditorStrings.removeImage": "Remove image",
+ "TipTapEditorStrings.removeLink": "Remove link",
+ "TipTapEditorStrings.replaceFile": "Replace file",
+ "TipTapEditorStrings.save": "सेव करें",
+ "TipTapEditorStrings.saveChanges": "परिवर्तनों को सेव करें",
+ "TipTapEditorStrings.scriptFormatting": "Script formatting",
+ "TipTapEditorStrings.selectFile": "Select file",
+ "TipTapEditorStrings.selectFileToUpload": "Select file to upload",
+ "TipTapEditorStrings.strikethrough": "Strikethrough",
+ "TipTapEditorStrings.subscript": "Subscript",
+ "TipTapEditorStrings.superscript": "सुपरस्क्रिप्ट",
+ "TipTapEditorStrings.supportedFileTypes": "Supported file types: { extensions }",
+ "TipTapEditorStrings.text": "Text",
+ "TipTapEditorStrings.textFormatOptions": "Text format options",
+ "TipTapEditorStrings.textFormattingOptions": "Text formatting options",
+ "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar",
+ "TipTapEditorStrings.textStyleFormatting": "Text style formatting",
+ "TipTapEditorStrings.underline": "Underline",
+ "TipTapEditorStrings.undo": "अकृत करें",
+ "TipTapEditorStrings.uploadImage": "Upload image",
"TitleStrings.catalogTitle": "Kolibri Content Library Catalog",
"TitleStrings.defaultTitle": "Kolibri स्टूडियो",
"TitleStrings.tabTitle": "{title} - {site}",
@@ -1472,7 +2067,7 @@
"ToggleText.more": "और दिखाएँ",
"TrashModal.deleteButton": "हटाएँ",
"TrashModal.deleteConfirmationCancelButton": "रद्द करें",
- "TrashModal.deleteConfirmationDeleteButton": "Delete permanently",
+ "TrashModal.deleteConfirmationDeleteButton": "हमेशा के लिए डिलीट करें",
"TrashModal.deleteConfirmationHeader": "Permanently delete {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}?",
"TrashModal.deleteConfirmationText": "You cannot undo this action. Are you sure you want to continue?",
"TrashModal.deleteSuccessMessage": "Permanently deleted",
@@ -1483,30 +2078,31 @@
"TrashModal.trashEmptyText": "Trash is empty",
"TrashModal.trashModalTitle": "Trash",
"TreeView.closeDrawer": "बंद करें",
- "TreeView.collapseAllButton": "Collapse all",
+ "TreeView.collapseAllButton": "सभी को संक्षिप्त करें",
"TreeView.openCurrentLocationButton": "Expand to current folder location",
"TreeView.showSidebar": "Show sidebar",
"TreeView.updatedResourcesReadyForReview": "Updated resources are ready for review",
"TreeViewBase.apiGenerated": "Generated by API",
- "TreeViewBase.cancel": "रद्द करें",
"TreeViewBase.channelDeletedSnackbar": "Channel deleted",
"TreeViewBase.channelDetails": "View channel details",
"TreeViewBase.deleteChannel": "चैनल हटाएँ",
- "TreeViewBase.deleteChannelButton": "चैनल हटाएँ",
- "TreeViewBase.deletePrompt": "This channel will be permanently deleted. This cannot be undone.",
- "TreeViewBase.deleteTitle": "Delete this channel",
"TreeViewBase.editChannel": "Edit channel details",
"TreeViewBase.emptyChannelTooltip": "You cannot publish an empty channel",
"TreeViewBase.getToken": "Get token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}",
+ "TreeViewBase.inviteCollaborators": "Invite collaborators",
"TreeViewBase.noChangesText": "No changes found in channel",
"TreeViewBase.noLanguageSetError": "Channel language is required",
"TreeViewBase.openTrash": "Open trash",
"TreeViewBase.publishButton": "Publish",
"TreeViewBase.publishButtonTitle": "Make this channel available for import into Kolibri",
"TreeViewBase.shareChannel": "Share channel",
+ "TreeViewBase.shareMenuButton": "साझा करें ",
+ "TreeViewBase.shareToken": "Share token",
+ "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library",
"TreeViewBase.syncChannel": "Sync resources",
"TreeViewBase.viewOnly": "View-only",
+ "Uploader.closeButtonLabel": "OK",
"Uploader.listDelimiter": ", ",
"Uploader.maxFileSizeText": "{count, plural,\n =1 {# file will not be uploaded.}\n other {# files will not be uploaded.}} File size must be under {size}",
"Uploader.noStorageHeader": "Not enough space",
@@ -1541,10 +2137,12 @@
"VisibilityDropdown.visibilityHeader": "About resource visibility",
"VisibilityDropdown.visibilityRequired": "Field is required",
"channelEditVue.errorChooseAtLeastOneCorrectAnswer": "Choose at least one correct answer",
+ "channelEditVue.errorInvalidQuestionType": "Invalid question type",
"channelEditVue.errorMissingAnswer": "Choose a correct answer",
"channelEditVue.errorProvideAtLeastOneCorrectAnswer": "Provide at least one correct answer",
"channelEditVue.errorQuestionRequired": "Question is required",
"channelEditVue.false": "गलत",
+ "channelEditVue.questionTypeFreeResponse": "Free response",
"channelEditVue.questionTypeInput": "Numeric input",
"channelEditVue.questionTypeMultipleSelection": "Multiple choice",
"channelEditVue.questionTypePerseus": "Perseus",
@@ -1557,7 +2155,7 @@
"sharedVue.activityDurationRequired": "यह जानकारी ज़रूरी है",
"sharedVue.activityDurationTooLongWarning": "This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it.",
"sharedVue.addAdditionalCatgoriesDescription": "You selected resources that have different categories. The categories you choose below will be added to all selected resources. This will not remove existing categories.",
- "sharedVue.changesSaved": "Changes saved",
+ "sharedVue.changesSaved": "परिवर्तन सहेजे गए",
"sharedVue.confirmLogout": "Changes you made may not be saved. Are you sure you want to leave this page?",
"sharedVue.copyrightHolderRequired": "Copyright holder is required",
"sharedVue.durationRequired": "Duration is required",
@@ -1578,4 +2176,4 @@
"sharedVue.masteryModelRequired": "Mastery is required",
"sharedVue.shortActivityLteThirty": "Value must be equal or less than 30",
"sharedVue.titleRequired": "Title is required"
-}
+}
\ No newline at end of file
diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po
index 523e513477..c8729a9817 100644
--- a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po
+++ b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-30 18:12+0000\n"
-"PO-Revision-Date: 2024-09-11 20:07\n"
+"POT-Creation-Date: 2026-04-16 00:57+0000\n"
+"PO-Revision-Date: 2026-04-20 19:33\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"Language: hi_IN\n"
@@ -18,47 +18,47 @@ msgstr ""
"X-Crowdin-File-ID: 4322\n"
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
-#: contentcuration/settings.py:289
+#: contentcuration/settings.py:271
msgid "Arabic"
msgstr ""
-#: contentcuration/middleware/db_readonly.py:24
+#: contentcuration/middleware/db_readonly.py:25
msgid "The site is currently in read-only mode. Please try again later."
msgstr ""
-#: contentcuration/models.py:295
+#: contentcuration/models.py:356
msgid "Not enough space. Check your storage under Settings page."
msgstr ""
-#: contentcuration/models.py:312 contentcuration/models.py:319
+#: contentcuration/models.py:440 contentcuration/models.py:452
msgid "Out of storage! Request more space under Settings > Storage."
msgstr ""
-#: contentcuration/models.py:1767
+#: contentcuration/models.py:2531
msgid " (Original)"
msgstr ""
-#: contentcuration/models.py:2657
+#: contentcuration/models.py:3848
msgid "Created DateTime"
msgstr ""
-#: contentcuration/models.py:2658
+#: contentcuration/models.py:3850
msgid "Datetime field when the custom_metadata for task was created in UTC"
msgstr ""
-#: contentcuration/settings.py:287
+#: contentcuration/settings.py:269
msgid "English"
msgstr ""
-#: contentcuration/settings.py:288
+#: contentcuration/settings.py:270
msgid "Spanish"
msgstr ""
-#: contentcuration/settings.py:290
+#: contentcuration/settings.py:272
msgid "French"
msgstr ""
-#: contentcuration/settings.py:291
+#: contentcuration/settings.py:273
msgid "Portuguese"
msgstr ""
@@ -575,151 +575,152 @@ msgstr ""
msgid "You can also try updating your current browser."
msgstr "आप अपने वर्तमान ब्राउज़र को अपडेट करने का प्रयास भी कर सकते हैं ।"
-#: contentcuration/templatetags/license_tags.py:11
+#: contentcuration/templatetags/license_tags.py:13
msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:16
+#: contentcuration/templatetags/license_tags.py:20
msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:26
+#: contentcuration/templatetags/license_tags.py:32
msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:29
+#: contentcuration/templatetags/license_tags.py:37
msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:33
+#: contentcuration/templatetags/license_tags.py:43
msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:37
+#: contentcuration/templatetags/license_tags.py:49
msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:41
+#: contentcuration/templatetags/license_tags.py:55
msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:44
+#: contentcuration/templatetags/license_tags.py:60
msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights."
msgstr ""
-#: contentcuration/templatetags/license_tags.py:47
+#: contentcuration/templatetags/license_tags.py:65
msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails."
msgstr ""
-#: contentcuration/utils/csv_writer.py:45
-#: contentcuration/utils/csv_writer.py:108
+#: contentcuration/utils/csv_writer.py:49
+#: contentcuration/utils/csv_writer.py:140
msgid "No Channel"
msgstr ""
-#: contentcuration/utils/csv_writer.py:46
+#: contentcuration/utils/csv_writer.py:50
msgid "No resource"
msgstr ""
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:86
msgid "Channel"
msgstr "चैनल"
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:87
msgid "Title"
msgstr "शीर्षक"
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:88
msgid "Kind"
msgstr ""
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:89
msgid "Filename"
msgstr ""
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:90
msgid "File Size"
msgstr ""
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:91
msgid "URL"
msgstr ""
-#: contentcuration/utils/csv_writer.py:71
+#: contentcuration/utils/csv_writer.py:92
msgid "Description"
msgstr "विवरण"
-#: contentcuration/utils/csv_writer.py:72
+#: contentcuration/utils/csv_writer.py:93
msgid "Author"
msgstr "लेखक"
-#: contentcuration/utils/csv_writer.py:72
+#: contentcuration/utils/csv_writer.py:94
msgid "Language"
msgstr "भाषा "
-#: contentcuration/utils/csv_writer.py:72
+#: contentcuration/utils/csv_writer.py:95
msgid "License"
msgstr "लाइसेंस"
-#: contentcuration/utils/csv_writer.py:72
+#: contentcuration/utils/csv_writer.py:96
msgid "License Description"
msgstr ""
-#: contentcuration/utils/csv_writer.py:72
+#: contentcuration/utils/csv_writer.py:97
msgid "Copyright Holder"
msgstr ""
-#: contentcuration/utils/csv_writer.py:108
+#: contentcuration/utils/csv_writer.py:141
msgid "No Resource"
msgstr ""
-#: contentcuration/utils/csv_writer.py:108
+#: contentcuration/utils/csv_writer.py:143
msgid "Staged File"
msgstr ""
-#: contentcuration/utils/incidents.py:7
+#: contentcuration/utils/incidents.py:8
msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved."
msgstr ""
-#: contentcuration/utils/incidents.py:16
+#: contentcuration/utils/incidents.py:18
msgid "EMERGENCY MAINTENANCE Kolibri Studio is operating on read-only mode for the time being in order for us to resolve some maintenance issues. This means all editing capabilities are disabled at the moment. We're currently working very hard to fix the issue as soon as possible. If you have any questions please contact us at content@learningequality.org. We apologize for any inconvenience caused and appreciate your patience while we resolve these issues."
msgstr ""
-#: contentcuration/utils/incidents.py:31
+#: contentcuration/utils/incidents.py:34
msgid "EMERGENCY MAINTENANCE Kolibri Studio is operating on read-only mode for the time being in order for us to resolve some database issues. This means all editing capabilities are disabled at the moment. We're currently working very hard to fix the issue as soon as possible. If you have any questions please contact us at content@learningequality.org. We apologize for any inconvenience caused and appreciate your patience while we resolve these issues."
msgstr ""
-#: contentcuration/utils/incidents.py:46
+#: contentcuration/utils/incidents.py:50
msgid "We are encountering issues with Google Cloud Storage. This means any file uploading and publishing operations are currently unavailable. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here"
msgstr ""
-#: contentcuration/utils/incidents.py:57
+#: contentcuration/utils/incidents.py:62
msgid "We are encountering issues with a third-party service. This means publishing is currently unavailable. We appreciate your patience while these issues are being resolved."
msgstr ""
-#: contentcuration/utils/incidents.py:65
+#: contentcuration/utils/incidents.py:71
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here"
msgstr ""
-#: contentcuration/utils/publish.py:101
+#: contentcuration/utils/publish.py:103
msgid "Kolibri Studio Channel Published"
msgstr ""
-#: contentcuration/views/settings.py:111
+#: contentcuration/views/settings.py:115
msgid "Kolibri Studio issue report"
msgstr ""
-#: contentcuration/views/settings.py:143
+#: contentcuration/views/settings.py:147
msgid "Kolibri Studio account deleted"
msgstr ""
-#: kolibri_public/views.py:220
+#: kolibri_public/views.py:323
msgid "Resource"
msgstr "संसाधन"
-#: kolibri_public/views_v1.py:65 kolibri_public/views_v1.py:76
-msgid "Api endpoint {} is not available"
+#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167
+msgid "API version is unavailable"
msgstr ""
-#: kolibri_public/views_v1.py:78
+#: kolibri_public/views_v1.py:170
msgid "No channel matching {} found"
msgstr ""
+
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/README.md b/contentcuration/locale/pt_BR/LC_MESSAGES/README.md
index 0f82b94d50..014a952e9a 100644
--- a/contentcuration/locale/pt_BR/LC_MESSAGES/README.md
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/README.md
@@ -1 +1 @@
-The JSON messages files in this folder were generated by kolibri-tools csvToJSON.js
+The JSON messages files in this folder were generated by kolibri-i18n csvToJSON.js
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json
index 677eb7883d..2a5a3904fb 100644
--- a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json
@@ -67,8 +67,6 @@
"AdministrationAppError.unauthorizedDetails": "Você precisa ser um administrador do Studio para visualizar esta página",
"AdministrationIndex.channelsLabel": "Canais",
"AdministrationIndex.usersLabel": "Usuários",
- "Alert.closeButtonLabel": "OK",
- "Alert.dontShowAgain": "Não mostrar esta mensagem novamente",
"AnswersEditor.answersLabel": "Respostas",
"AnswersEditor.newAnswerBtnLabel": "Nova resposta",
"AnswersEditor.noAnswersPlaceholder": "A pergunta não tem opções de resposta",
@@ -184,26 +182,31 @@
"CatalogFilterBar.keywords": "\"{text}\"",
"CatalogFilterBar.starred": "Favoritos",
"CatalogFilterBar.subtitles": "Legendas",
- "CatalogFilters.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri",
- "CatalogFilters.coachLabel": "Materiais para professores",
- "CatalogFilters.copyright": "© {year} Learning Equality",
- "CatalogFilters.formatLabel": "Formatos",
- "CatalogFilters.frequentlyAskedQuestionsLink": "Perguntas frequentes",
- "CatalogFilters.includesLabel": "Mostrar apenas canais com",
- "CatalogFilters.licenseLabel": "Licenças",
- "CatalogFilters.searchLabel": "Palavras-chave",
- "CatalogFilters.searchText": "Buscar",
- "CatalogFilters.starredLabel": "Favoritos",
- "CatalogFilters.subtitlesLabel": "Legendas",
+ "CatalogFilterPanelContent.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri",
+ "CatalogFilterPanelContent.coachLabel": "Materiais para professores",
+ "CatalogFilterPanelContent.copyright": "© {year} Learning Equality",
+ "CatalogFilterPanelContent.formatLabel": "Formatos",
+ "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Perguntas frequentes",
+ "CatalogFilterPanelContent.includesLabel": "Mostrar apenas canais com",
+ "CatalogFilterPanelContent.licenseLabel": "Licenças",
+ "CatalogFilterPanelContent.searchLabel": "Palavras-chave",
+ "CatalogFilterPanelContent.starredLabel": "Favoritos",
+ "CatalogFilterPanelContent.subtitlesLabel": "Legendas",
+ "CatalogFilters.filterLabel": "filtrar",
"CatalogList.cancelButton": "Cancelar",
"CatalogList.channelSelectionCount": "{count, plural, one {}\n =1 {# canal selecionado}\n other {# canais selecionados}}",
+ "CatalogList.copyToken": "Copiar token do canal",
"CatalogList.downloadButton": "Baixar",
"CatalogList.downloadCSV": "Baixar CSV",
"CatalogList.downloadPDF": "Baixar PDF",
"CatalogList.downloadingMessage": "Download iniciado",
+ "CatalogList.goToWebsite": "Ir para o website de origem",
+ "CatalogList.moreOptions": "Outras opções",
"CatalogList.resultsText": "{count, plural, one {}\n =1 {# resultado encontrado}\n other {# resultados encontrados}}",
"CatalogList.selectAll": "Selecionar todos",
"CatalogList.selectChannels": "Baixar um resumo dos canais selecionados",
+ "CatalogList.title": "Biblioteca de conteúdo",
+ "CatalogList.viewContent": "Visualizar canal no Kolibri",
"CategoryOptions.noCategoryFoundText": "Categoria não encontrada",
"ChangePasswordForm.cancelAction": "Cancelar",
"ChangePasswordForm.changePasswordHeader": "Mudar de senha",
@@ -224,9 +227,6 @@
"ChannelCatalogFrontPage.languagesHeading": "Idiomas",
"ChannelCatalogFrontPage.numberOfChannels": "{ num } canais",
"ChannelCatalogFrontPage.subtitlesIncludedText": "Legendas",
- "ChannelDeletedError.backToHomeAction": "Voltar à página inicial",
- "ChannelDeletedError.channelDeletedDetails": "Este canal não existe ou pode ter sido removido. Por favor, envie um e-mail para content@learningequality.org se achar que isto é um erro.",
- "ChannelDeletedError.channelDeletedHeader": "Canal não encontrado",
"ChannelDetailsModal.downloadButton": "Baixar resumo do canal",
"ChannelDetailsModal.downloadCSV": "Baixar CSV",
"ChannelDetailsModal.downloadPDF": "Baixar PDF",
@@ -263,23 +263,17 @@
"ChannelInvitation.editText": "{sender} te convidou para editar {channel}",
"ChannelInvitation.goToChannelSnackbarAction": "Ir para o canal",
"ChannelInvitation.viewText": "{sender} te convidou para visualizar {channel}",
- "ChannelItem.cancel": "Cancelar",
"ChannelItem.channelDeletedSnackbar": "Canal excluído",
"ChannelItem.channelLanguageNotSetIndicator": "Nenhum idioma definido",
"ChannelItem.channelRemovedSnackbar": "Canal removido",
"ChannelItem.copyToken": "Copiar token do canal",
"ChannelItem.deleteChannel": "Excluir canal",
- "ChannelItem.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.",
- "ChannelItem.deleteTitle": "Excluir este canal",
"ChannelItem.details": "Detalhes",
"ChannelItem.editChannel": "Editar detalhes do canal",
"ChannelItem.goToWebsite": "Ir para o website de origem",
"ChannelItem.lastPublished": "Publicado em {last_published}",
"ChannelItem.lastUpdated": "Atualizado {updated}",
- "ChannelItem.removeBtn": "Remover",
- "ChannelItem.removeChannel": "Remover da lista de canais",
- "ChannelItem.removePrompt": "Você tem acesso somente de visualização para este canal. Confirme que você deseja removê-lo da sua lista de canais.",
- "ChannelItem.removeTitle": "Remover da lista de canais",
+ "ChannelItem.removeChannel": "Remover canal",
"ChannelItem.resourceCount": "{count, plural, one {}\n =1 {# conteúdo}\n other {# conteúdos}}",
"ChannelItem.unpublishedText": "Não publicado",
"ChannelItem.versionText": "Versão {version}",
@@ -292,7 +286,6 @@
"ChannelListIndex.catalog": "Biblioteca de conteúdo",
"ChannelListIndex.channelSets": "Conjuntos",
"ChannelListIndex.frequentlyAskedQuestions": "Perguntas frequentes",
- "ChannelListIndex.invitations": "Você tem {count, plural, one {}\n =1 {# convite}\n other {# convites}}",
"ChannelListIndex.libraryTitle": "Catálogo da Biblioteca de Conteúdos Kolibri",
"ChannelModal.APIText": "Os canais gerados automaticamente não são editáveis.",
"ChannelModal.changesSaved": "Alterações salvas",
@@ -316,25 +309,6 @@
"ChannelNotFoundError.channelNotFoundHeader": "Canal não encontrado",
"ChannelSelectionList.noChannelsFound": "Nenhum canal encontrado",
"ChannelSelectionList.searchText": "Buscar canal",
- "ChannelSetItem.cancel": "Cancelar",
- "ChannelSetItem.delete": "Excluir conjunto",
- "ChannelSetItem.deleteChannelSetText": "Tem certeza que deseja excluir esta coleção?",
- "ChannelSetItem.deleteChannelSetTitle": "Excluir conjunto",
- "ChannelSetItem.edit": "Editar conjunto",
- "ChannelSetItem.options": "Opções",
- "ChannelSetItem.saving": "Salvando",
- "ChannelSetList.aboutChannelSets": "Sobre os conjuntos",
- "ChannelSetList.aboutChannelSetsLink": "Saiba mais sobre os conjuntos",
- "ChannelSetList.addChannelSetTitle": "Novo conjunto",
- "ChannelSetList.cancelButtonLabel": "Fechar",
- "ChannelSetList.channelNumber": "Número de canais",
- "ChannelSetList.channelSetsDescriptionText": "Um conjunto contém vários canais do Kolibri Studio que podem ser importados de uma vez ao Kolibri com um único token de conjunto.",
- "ChannelSetList.channelSetsDisclaimer": "Você precisará da versão 0.12.0 ou superior do Kolibri para importar conjuntos de canais",
- "ChannelSetList.channelSetsInstructionsText": "Você pode fazer um conjunto selecionando os canais que quer importar juntos.",
- "ChannelSetList.noChannelSetsFound": "Você pode integrar vários canais para criar um conjunto. O conjunto inteiro pode então ser importado ao Kolibri de uma vez por meio de um token de conjunto.",
- "ChannelSetList.options": "Opções",
- "ChannelSetList.title": "Nome do conjunto",
- "ChannelSetList.token": "ID do token",
"ChannelSetModal.bookmark": "Favoritos",
"ChannelSetModal.channelAdded": "Canal adicionado",
"ChannelSetModal.channelCountText": "{channelCount, plural, one {} =0 {Não há canais publicados no seu conjunto} =1 {# canal} other {# canais}}",
@@ -533,6 +507,163 @@
"CommonMetadataStrings.webDesign": "Desenho de Web",
"CommonMetadataStrings.work": "Trabalho",
"CommonMetadataStrings.writing": "Escrita",
+ "CommonStrings.backAction": "Voltar",
+ "CommonStrings.channelDetailsLabel": "Detalhes do canal",
+ "CommonStrings.clearAction": "Limpar",
+ "CommonStrings.closeAction": "Fechar",
+ "CommonStrings.copyChannelTokenAction": "Copiar token do canal",
+ "CommonStrings.dismissAction": "Descartar",
+ "CommonStrings.genericErrorMessage": "Desculpe, algo deu errado. Tente novamente.",
+ "CommonStrings.previewAction": "Pré-visualizar",
+ "CommonStrings.seeAllAction": "Ver tudo",
+ "CommonStrings.seeLessAction": "Ver menos",
+ "CommunityChannelsStrings.aboutCommunityLibraryDescription": "A biblioteca da comunidade contém canais enviados pela comunidade e aprovados para descoberta no Studio.",
+ "CommunityChannelsStrings.aboutCommunityLibraryTitle": "Sobre a Biblioteca da comunidade",
+ "CommunityChannelsStrings.activityHistoryLabel": "Histórico de atividades",
+ "CommunityChannelsStrings.adminLabel": "Administrador",
+ "CommunityChannelsStrings.allLicensesCompatible": "Todas as licenças são compatíveis com a Biblioteca da comunidade.",
+ "CommunityChannelsStrings.allNotificationsLabel": "Todas as notificações",
+ "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Aguarde a revisão do canal. Você verá uma notificação na sua conta do Studio após a conclusão da avaliação.",
+ "CommunityChannelsStrings.alreadySubmittedWarningTitle": "Esta versão do canal já foi enviada à Biblioteca da comunidade.",
+ "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) aprovado em {channelVersion}",
+ "CommunityChannelsStrings.approvedPrimaryInfo": "Uma versão anterior está disponível na Biblioteca da comunidade. Os editores verão primeiro os envios mais recentes.",
+ "CommunityChannelsStrings.approvedStatus": "Aprovado",
+ "CommunityChannelsStrings.availableStatus": "Disponível na Biblioteca da comunidade",
+ "CommunityChannelsStrings.cancelAction": "Cancelar",
+ "CommunityChannelsStrings.categoriesLabel": "Categorias",
+ "CommunityChannelsStrings.channelCannotBeDistributed": "Este canal não pode ser distribuído pelo Kolibri.",
+ "CommunityChannelsStrings.channelFitAttributionLabel": "Atribuição",
+ "CommunityChannelsStrings.channelFitChannelInfoLabel": "Informações do canal",
+ "CommunityChannelsStrings.channelFitChecklistAttribution": "Cada recurso tem um autor listado?",
+ "CommunityChannelsStrings.channelFitChecklistChannelInfo": "As informações básicas do seu canal são preenchidas — título, descrição, miniatura, língua, categoria e nível?",
+ "CommunityChannelsStrings.channelFitChecklistIntro": "Critérios para envio à Biblioteca da comunidade",
+ "CommunityChannelsStrings.channelFitChecklistLicense": "Os recursos do seu canal estão sob licença aberta ou no domínio público?",
+ "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Os recursos do seu canal funcionam sem uma conexão com a Internet?",
+ "CommunityChannelsStrings.channelFitChecklistQuality": "Alguém na sua equipe ou na sua organização revisou o canal?",
+ "CommunityChannelsStrings.channelFitLicenseLabel": "Licença",
+ "CommunityChannelsStrings.channelFitOfflineUseLabel": "Uso offline",
+ "CommunityChannelsStrings.channelFitQualityLabel": "Qualidade",
+ "CommunityChannelsStrings.channelTokenDescription": "Você pode usar esse token para importar e visualizar o canal de rascunhos no Kolibri. Observe que o token para o canal publicado final será diferente.",
+ "CommunityChannelsStrings.channelVersion": "{name} v{version}",
+ "CommunityChannelsStrings.channelVersionTokenLabel": "Token da versão do canal",
+ "CommunityChannelsStrings.clearAll": "Limpar tudo",
+ "CommunityChannelsStrings.clearAllAction": "Limpar tudo",
+ "CommunityChannelsStrings.communityLibraryCTADescription": "Tem um canal que vale a pena compartilhar com outros educadores e alunos? Envie para revisão através do menu Compartilhar para ser descoberto no Studio.",
+ "CommunityChannelsStrings.communityLibraryCTATitle": "Ajude a aumentar a Biblioteca da comunidade",
+ "CommunityChannelsStrings.communityLibraryDescription": "Navegue pelos canais enviados pela comunidade aprovados para descoberta no Studio. Copie um token para usar um canal no Kolibri.",
+ "CommunityChannelsStrings.communityLibraryLabel": "Biblioteca da comunidade",
+ "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Envio para Biblioteca da comunidade",
+ "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - Todas as licenças são compatíveis com a Biblioteca da comunidade.",
+ "CommunityChannelsStrings.confirmDistributionRights": "Confirme que você tem o direito de distribuir esses recursos através do Kolibri.",
+ "CommunityChannelsStrings.confirmReplacementText": "Eu entendo que isso vai substituir meu envio anterior na fila de análise",
+ "CommunityChannelsStrings.countriesInfoText": "Selecione um ou mais países para rotular o que é enviado por seu canal. Por exemplo, se o seu canal contiver materiais alinhados a um currículo nacional ou a um conteúdo regional específico, selecionar os países relevantes ajudará os usuários a encontrá-lo. Caso contrário, deixe em branco.",
+ "CommunityChannelsStrings.countryLabel": "Países",
+ "CommunityChannelsStrings.descriptionLabel": "Descreva o que há de novo nesta versão do canal",
+ "CommunityChannelsStrings.dismissAction": "Descartar",
+ "CommunityChannelsStrings.draftBeingPublishedNotice": "Versão do rascunho sendo publicada",
+ "CommunityChannelsStrings.draftPublishedNotice": "Rascunho publicado com sucesso",
+ "CommunityChannelsStrings.draftTokenLabel": "Token de rascunho",
+ "CommunityChannelsStrings.editorLabel": "Editor",
+ "CommunityChannelsStrings.emptyNotificationsNotice": "Você não tem notificações no momento.",
+ "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "Nenhuma notificação corresponde aos filtros aplicados.",
+ "CommunityChannelsStrings.errorLoadingVersions": "Não foi possível carregar histórico de versão",
+ "CommunityChannelsStrings.errorSnackbar": "Ocorreu um erro ao enviar o canal",
+ "CommunityChannelsStrings.feedbackNotesLabel": "Notas do revisor",
+ "CommunityChannelsStrings.filterByDateLabel": "Filtrar por data",
+ "CommunityChannelsStrings.filterByStatusLabel": "Filtrar por status",
+ "CommunityChannelsStrings.filterLabel": "filtrar",
+ "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Corrija o licenciamento antes de enviar uma nova versão.",
+ "CommunityChannelsStrings.flaggedNotification": "Alterações necessárias para a versão {channelVersion}",
+ "CommunityChannelsStrings.flaggedStatus": "Precisa de alterações",
+ "CommunityChannelsStrings.getDraftTokenAction": "Copiar token para rascunho de canal",
+ "CommunityChannelsStrings.goToMyChannelsAction": "Ir para Meus canais",
+ "CommunityChannelsStrings.hideCriteriaAction": "Ocultar critérios",
+ "CommunityChannelsStrings.hideVersions": "Ocultar versões",
+ "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - este canal não pode ser distribuído por Kolibri. Se você não pode alterar a licença, remova todos os recursos com \"{licenseNames}\" antes de enviar novamente.",
+ "CommunityChannelsStrings.incompatibleLicensesDetected": "Licenças incompatíveis detectadas.",
+ "CommunityChannelsStrings.incompleteResourcesDescription1": "Conteúdos incompletos não poderão ser publicados e disponibilizados para uso na Plataforma de Aprendizagem Kolibri.",
+ "CommunityChannelsStrings.incompleteResourcesWarning": "{{count, number} {count, plural, one {recurso incompleto} other {recursos incompletos}}",
+ "CommunityChannelsStrings.internalNotesLabel": "Notas do administrador (apenas uso interno)",
+ "CommunityChannelsStrings.invalidLicensingReason": "Licenças inválidas ou não conformes",
+ "CommunityChannelsStrings.invalidMetadataReason": "Metadados inválidos ou faltando",
+ "CommunityChannelsStrings.languageLabel": "Idioma",
+ "CommunityChannelsStrings.languagesLabel": "Idiomas",
+ "CommunityChannelsStrings.lessDetailsButton": "Ocultar detalhes",
+ "CommunityChannelsStrings.licenseCheckPassed": "Passou a verificação da licença.",
+ "CommunityChannelsStrings.licensesLabel": "Licenças",
+ "CommunityChannelsStrings.loadError": "Ocorreu um erro ao carregar os canais.",
+ "CommunityChannelsStrings.loadingVersionHistory": "Carregando histórico de versões",
+ "CommunityChannelsStrings.moreDetails": "Após a aprovação do seu envio, o canal estará disponível para outros usuários do Kolibri Studio na página \"Biblioteca da comunidade\".",
+ "CommunityChannelsStrings.moreDetailsButton": "Mais detalhes",
+ "CommunityChannelsStrings.needKolibriVersionToImport": "Você precisará do Kolibri versão 0.19.4 ou superior para importar canais da Biblioteca da comunidade.",
+ "CommunityChannelsStrings.needsChangesPrimaryInfo": "Sua versão enviada anteriormente precisa de alterações. Certifique-se de que você abordou todos os comentários antes de reenviar.",
+ "CommunityChannelsStrings.newLabel": "Novo",
+ "CommunityChannelsStrings.newNotificationsNotice": "Novas notificações disponíveis.",
+ "CommunityChannelsStrings.nextPageAction": "Próximo",
+ "CommunityChannelsStrings.noCommunityChannels": "Nenhum canal foi publicado na Biblioteca da comunidade.",
+ "CommunityChannelsStrings.noResultsWithFilters": "Nenhum canal corresponde aos filtros selecionados.",
+ "CommunityChannelsStrings.noVersionsAvailable": "Nenhum histórico de versão disponível",
+ "CommunityChannelsStrings.nonePrimaryInfo": "Estamos convidando membros da comunidade Kolibri a enviar canais que criaram para aprender offline em configurações de baixo recurso. ",
+ "CommunityChannelsStrings.notPublishedWarningDescription": "Publique no Studio primeiro, depois envie para a biblioteca da comunidade.",
+ "CommunityChannelsStrings.notPublishedWarningTitle": "Este canal ainda não está publicado no Kolibri Studio",
+ "CommunityChannelsStrings.notificationsLabel": "Notificações",
+ "CommunityChannelsStrings.otherIssuesReason": "Outros problemas",
+ "CommunityChannelsStrings.pageIndicator": "{currentPage} de {totalPages}",
+ "CommunityChannelsStrings.pendingStatus": "Enviado",
+ "CommunityChannelsStrings.portabilityIssuesReason": "Problemas de portabilidade",
+ "CommunityChannelsStrings.previewYourDraftTitle": "Visualizar seu projeto de canal no Kolibri",
+ "CommunityChannelsStrings.previousPageAction": "Pergunta anterior",
+ "CommunityChannelsStrings.publicWarningDescription": "Não é possível enviar canais públicos à Biblioteca da comunidade.",
+ "CommunityChannelsStrings.publicWarningTitle": "Este canal é atualmente público na biblioteca do Kolibri.",
+ "CommunityChannelsStrings.publishAction": "Publicar",
+ "CommunityChannelsStrings.publishChannel": "Publicar canal",
+ "CommunityChannelsStrings.publishChannelDescription": "Para ver seu canal no Kolibri, importe usando o token do canal.",
+ "CommunityChannelsStrings.publishChannelMode": "Publicar canal",
+ "CommunityChannelsStrings.publishDraftDescription": "Seu canal será salvo como rascunho para que você faça revisões e verificações de qualidade no rascunho, sem alterar a versão atual disponível para usuários Kolibri. Para ver esse rascunho de canal no Kolibri, importe usando o token de canal de rascunho.",
+ "CommunityChannelsStrings.publishDraftMode": "Publicar rascunho de canal",
+ "CommunityChannelsStrings.publishedVersionLabel": "Versão publicada:",
+ "CommunityChannelsStrings.publishingInfo": "Você está publicando: Versão {version}",
+ "CommunityChannelsStrings.publishingMessage": "O canal está sendo publicado",
+ "CommunityChannelsStrings.qualityAssuranceReason": "Problemas de garantia de qualidade",
+ "CommunityChannelsStrings.reasonLabel": "Motivo: {reason}",
+ "CommunityChannelsStrings.resubmitAction": "Enviar novamente",
+ "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} também é publicado na Biblioteca da comunidade.",
+ "CommunityChannelsStrings.resubmitModalBodySecond": "Gostaria de reenviar esta versão com as mudanças que você fez para revisão de Biblioteca da comunidade?",
+ "CommunityChannelsStrings.resubmitModalTitle": "Reenviar canal para revisão da biblioteca da comunidade?",
+ "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# resultado encontrado} other {# resultados encontrados}}",
+ "CommunityChannelsStrings.retry": "Tentar novamente",
+ "CommunityChannelsStrings.reviewAction": "Revisar",
+ "CommunityChannelsStrings.saveDraft": "Salvar rascunho",
+ "CommunityChannelsStrings.searchLabel": "Buscar",
+ "CommunityChannelsStrings.searchNotificationsLabel": "Pesquisar notificações",
+ "CommunityChannelsStrings.seeAllVersions": "Ver todas versões",
+ "CommunityChannelsStrings.showMore": "Mostrar mais",
+ "CommunityChannelsStrings.showOlderAction": "Mostrar mais antigo",
+ "CommunityChannelsStrings.specialPermissionsDetected": "Licenças de Permissões especiais detectadas",
+ "CommunityChannelsStrings.submissionCreationNotification": "Seu envio para a Biblioteca da comunidade foi bem-sucedido e está sob revisão.",
+ "CommunityChannelsStrings.submissionNotesLabel": "Notas de envio",
+ "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) enviado {channelVersion}",
+ "CommunityChannelsStrings.submitButton": "Enviar para revisão",
+ "CommunityChannelsStrings.submitToCommunityLibrary": "Enviar para a Biblioteca da comunidade",
+ "CommunityChannelsStrings.submittedPrimaryInfo": "Uma versão anterior ainda está pendente de revisão. Os revisores verão o envio mais recente por padrão.",
+ "CommunityChannelsStrings.submittedSnackbar": "Canal enviado à Biblioteca da comunidade",
+ "CommunityChannelsStrings.submittingSnackbar": "Enviando canal para Biblioteca da comunidade...",
+ "CommunityChannelsStrings.supersededStatus": "Substituído",
+ "CommunityChannelsStrings.thisMonthLabel": "Este mês",
+ "CommunityChannelsStrings.thisWeekLabel": "Esta semana",
+ "CommunityChannelsStrings.thisYearLabel": "Este ano",
+ "CommunityChannelsStrings.todayLabel": "Hoje",
+ "CommunityChannelsStrings.unreadNotificationsLabel": "Não lida",
+ "CommunityChannelsStrings.versionDescriptionLabel": "Descrição da versão",
+ "CommunityChannelsStrings.versionLabel": "Versão {version}",
+ "CommunityChannelsStrings.versionNotesLabel": "Descreva o que há de novo nesta versão do canal",
+ "CommunityChannelsStrings.viewCriteriaAction": "Ver critérios",
+ "CommunityChannelsStrings.viewMoreAction": "Ver mais",
+ "CommunityChannelsStrings.whatCanYouDoHere": "O que você pode fazer aqui",
+ "CommunityChannelsStrings.whatCanYouDoHereItem1": "Procurar canais por país, categoria e idioma",
+ "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copiar um token de canal para usar no Kolibri",
+ "CommunityChannelsStrings.whatCanYouDoHereItem3": "Ver detalhes do canal incluindo descrição e metadados",
+ "CommunityChannelsStrings.whatIsCommunityLibrary": "O que é a Biblioteca da comunidade?",
"CommunityStandardsModal.communityStandardsHeader": "Normas comunitárias",
"CommunityStandardsModal.coreValuesLink": "Saiba mais sobre os valores fundamentais da Learning Equality",
"CommunityStandardsModal.description": "A Learning Equality é uma organização sem fins lucrativos dedicada a possibilitar o acesso igualitário a experiências de educação de qualidade. Junto à nossa declaração de valores fundamentais, estas normas comunitárias pretendem promover um ambiente solidário e inclusivo para nossos usuários.",
@@ -819,40 +950,11 @@
"DeleteAccountForm.emailInvalidText": "O e-mail não corresponde ao e-mail da sua conta",
"DeleteAccountForm.fieldRequired": "Campo obrigatório",
"DeleteAccountForm.ok": "OK",
- "DetailsPanel.AVERAGE": "Médio",
- "DetailsPanel.LARGE": "Grande",
- "DetailsPanel.SMALL": "Pequeno",
- "DetailsPanel.VERY_LARGE": "Muito grande",
- "DetailsPanel.VERY_SMALL": "Muito pequeno",
- "DetailsPanel.aggregatorToolTip": "Website ou organização hospedando este conjunto de conteúdos, mas não necessariamente o criador ou detentor dos direitos autorais",
- "DetailsPanel.aggregatorsLabel": "Agregadores",
- "DetailsPanel.assessmentsIncludedText": "Avaliações",
- "DetailsPanel.authorToolTip": "Pessoa ou organização que criou este conteúdo",
- "DetailsPanel.authorsLabel": "Autores",
- "DetailsPanel.categoriesHeading": "Categorias",
- "DetailsPanel.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri",
- "DetailsPanel.coachHeading": "Materiais para professores",
- "DetailsPanel.containsContentHeading": "Contém conteúdo de",
- "DetailsPanel.containsHeading": "Contém",
- "DetailsPanel.copyrightHoldersLabel": "Detentores dos direitos autorais",
- "DetailsPanel.creationHeading": "Criado em",
- "DetailsPanel.currentVersionHeading": "Versão publicada",
- "DetailsPanel.languagesHeading": "Idiomas",
- "DetailsPanel.levelsHeading": "Níveis",
- "DetailsPanel.licensesLabel": "Licenças",
- "DetailsPanel.primaryLanguageHeading": "Idioma principal",
- "DetailsPanel.providerToolTip": "Organização que encomendou ou distribui o conteúdo",
- "DetailsPanel.providersLabel": "Provedores",
- "DetailsPanel.publishedHeading": "Publicado em",
- "DetailsPanel.resourceHeading": "Conteúdos totais",
- "DetailsPanel.sampleFromChannelHeading": "Amostra de conteúdo deste canal",
- "DetailsPanel.sampleFromTopicHeading": "Amostra de conteúdo deste tema",
- "DetailsPanel.sizeHeading": "Tamanho do canal",
- "DetailsPanel.sizeText": "{text} ({size})",
- "DetailsPanel.subtitlesHeading": "Legendas",
- "DetailsPanel.tagsHeading": "Etiquetas comuns",
- "DetailsPanel.tokenHeading": "Token do canal",
- "DetailsPanel.unpublishedText": "Não publicado",
+ "DeleteChannelModal.cancel": "Cancelar",
+ "DeleteChannelModal.channelDeletedSnackbar": "Canal excluído",
+ "DeleteChannelModal.deleteChannel": "Excluir canal",
+ "DeleteChannelModal.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.",
+ "DeleteChannelModal.deleteTitle": "Excluir este canal",
"DetailsTabView.aggregatorLabel": "Agregador",
"DetailsTabView.aggregatorToolTip": "Website ou organização hospedando este conjunto de conteúdos, mas não necessariamente o criador ou detentor dos direitos autorais",
"DetailsTabView.assessmentOptionsLabel": "Opções de avaliação",
@@ -1008,10 +1110,8 @@
"ForgotPassword.forgotPasswordPrompt": "Por favor, digite seu endereço de e-mail para receber as instruções de redefinição de senha",
"ForgotPassword.forgotPasswordTitle": "Redefinir sua senha",
"ForgotPassword.submitButton": "Enviar",
- "FormulasMenu.btnLabelInsert": "Inserir",
- "FormulasMenu.formulasMenuTitle": "Caracteres especiais",
"FormulasStrings.addition": "Addition",
- "FormulasStrings.advancedCategory": "Advanced",
+ "FormulasStrings.advancedCategory": "Avançado",
"FormulasStrings.alpha": "alpha",
"FormulasStrings.and": "And",
"FormulasStrings.angle": "Angle",
@@ -1029,7 +1129,7 @@
"FormulasStrings.congruentTo": "Congruent to",
"FormulasStrings.conjugate": "Conjugate",
"FormulasStrings.conjugateTranspose": "Conjugate transpose",
- "FormulasStrings.contains": "Contains",
+ "FormulasStrings.contains": "Contém",
"FormulasStrings.contourIntegral": "Contour integral",
"FormulasStrings.coproduct": "Coproduct",
"FormulasStrings.definition": "Definition",
@@ -1061,7 +1161,7 @@
"FormulasStrings.fraction": "Fraction",
"FormulasStrings.gamma": "gamma",
"FormulasStrings.gammaCapital": "Gamma",
- "FormulasStrings.geometryCategory": "Geometry",
+ "FormulasStrings.geometryCategory": "Geometria",
"FormulasStrings.givenThat": "Given that/Such that",
"FormulasStrings.greaterThan": "Greater than",
"FormulasStrings.greaterThanOrEqual": "Greater than or equal to",
@@ -1078,7 +1178,7 @@
"FormulasStrings.lambda": "lambda",
"FormulasStrings.lambdaCapital": "Lambda",
"FormulasStrings.left": "Left",
- "FormulasStrings.leftArrow": "Left arrow",
+ "FormulasStrings.leftArrow": "Seta para a esquerda",
"FormulasStrings.leftCeiling": "Left ceiling",
"FormulasStrings.leftDouble": "Left (double)",
"FormulasStrings.leftFloor": "Left floor",
@@ -1134,7 +1234,7 @@
"FormulasStrings.reducibleTo": "Reducible to",
"FormulasStrings.rho": "rho",
"FormulasStrings.right": "Right",
- "FormulasStrings.rightArrow": "Right arrow",
+ "FormulasStrings.rightArrow": "Seta para a direita",
"FormulasStrings.rightCeiling": "Right ceiling",
"FormulasStrings.rightDouble": "Right (double)",
"FormulasStrings.rightFloor": "Right floor",
@@ -1155,13 +1255,13 @@
"FormulasStrings.southeast": "Southeast",
"FormulasStrings.southwest": "Southwest",
"FormulasStrings.spade": "Spade",
- "FormulasStrings.squareRoot": "Square root",
+ "FormulasStrings.squareRoot": "Raiz quadrada",
"FormulasStrings.subscript": "Subscript",
"FormulasStrings.subset": "Subset",
"FormulasStrings.subsetOrEqual": "Subset or equal",
"FormulasStrings.subtraction": "Subtraction",
"FormulasStrings.sum": "Sum",
- "FormulasStrings.superscript": "Superscript",
+ "FormulasStrings.superscript": "Exponente",
"FormulasStrings.superset": "Superset",
"FormulasStrings.supersetOrEqual": "Superset or equal",
"FormulasStrings.symmetricDifference": "Symmetric difference",
@@ -1204,16 +1304,6 @@
"HintsEditor.newHintBtnLabel": "Nova dica",
"HintsEditor.noHintsPlaceholder": "A pergunta não tem dicas",
"ImageOnlyThumbnail.thumbnail": "{title} miniatura",
- "ImagesMenu.acceptsText": "Tipos suportados de arquivos: {acceptedFormats}",
- "ImagesMenu.altTextHint": "A descrição da imagem é necessária para possibilitar que alunos com deficiência visual respondam a perguntas, e também é exibida quando a imagem falha ao carregar",
- "ImagesMenu.altTextLabel": "Descrição da imagem",
- "ImagesMenu.btnLabelCancel": "Cancelar",
- "ImagesMenu.btnLabelInsert": "Inserir",
- "ImagesMenu.currentImageDefaultText": "Imagem atual",
- "ImagesMenu.defaultDropText": "Arraste e solte uma imagem aqui ou envie-a manualmente",
- "ImagesMenu.imageHeader": "Enviar imagem",
- "ImagesMenu.selectFile": "Selecionar arquivo",
- "ImagesMenu.selectFileButton": "Selecionar arquivo",
"ImportFromChannelsModal.addButton": "Adicionar",
"ImportFromChannelsModal.addedText": "Adicionado",
"ImportFromChannelsModal.importAction": "Importar",
@@ -1223,6 +1313,7 @@
"ImportFromChannelsModal.reviewAction": "Revisar",
"ImportFromChannelsModal.reviewTitle": "Seleção de conteúdos",
"InfoModal.close": "Fechar",
+ "InfoModal.open": "Mais informações sobre as licenças",
"InheritAncestorMetadataModal.applyResourceDetailsTitle": "Aplicar detalhes da pasta '{folder}'",
"InheritAncestorMetadataModal.cancelAction": "Cancelar",
"InheritAncestorMetadataModal.categories": "Categorias: {categories}",
@@ -1255,17 +1346,48 @@
"MainNavigationDrawer.helpLink": "Ajuda e suporte",
"MainNavigationDrawer.logoutLink": "Terminar sessão",
"MainNavigationDrawer.settingsLink": "Configurações",
- "MarkdownEditor.bold": "Negrito (Ctrl+B)",
- "MarkdownEditor.formulas": "Inserir fórmula (Ctrl+F)",
- "MarkdownEditor.image": "Inserir imagem (Ctrl+P)",
- "MarkdownEditor.italic": "Itálico (Ctrl+I)",
- "MarkdownEditor.minimize": "Minimizar (Ctrl+M)",
- "MarkdownImageField.editImageOption": "Editar",
- "MarkdownImageField.removeImageOption": "Remover",
- "MarkdownImageField.resizeImageOption": "Redimensionar",
"MasteryCriteriaGoal.labelText": "Objetivo",
"MasteryCriteriaMofNFields.mHint": "Respostas corretas são necessárias",
"MasteryCriteriaMofNFields.nHint": "Respostas recentes",
+ "MathLiveA11yStrings.accented": "acentuado",
+ "MathLiveA11yStrings.array": "array",
+ "MathLiveA11yStrings.box": "caixa",
+ "MathLiveA11yStrings.chemicalFormula": "fórmula química",
+ "MathLiveA11yStrings.crossOut": "riscar",
+ "MathLiveA11yStrings.deleted": "excluído: ",
+ "MathLiveA11yStrings.delimiter": "delimitador",
+ "MathLiveA11yStrings.denominator": "denominador",
+ "MathLiveA11yStrings.endOf": "{spokenText}; fim de {relationName}",
+ "MathLiveA11yStrings.endOfMathfield": "{spokenText}; fim do campo matemático",
+ "MathLiveA11yStrings.error": "erro",
+ "MathLiveA11yStrings.extensibleSymbol": "símbolo extensível",
+ "MathLiveA11yStrings.first": "primeiro",
+ "MathLiveA11yStrings.fraction": "fração",
+ "MathLiveA11yStrings.group": "grupo",
+ "MathLiveA11yStrings.index": "índice",
+ "MathLiveA11yStrings.latex": "LaTeX",
+ "MathLiveA11yStrings.line": "linha",
+ "MathLiveA11yStrings.mathField": "campo matemático",
+ "MathLiveA11yStrings.mathfield": "campo matemático",
+ "MathLiveA11yStrings.numerator": "numerador",
+ "MathLiveA11yStrings.operator": "operador",
+ "MathLiveA11yStrings.outOf": "de {relationName};",
+ "MathLiveA11yStrings.overUnder": "superior-inferior",
+ "MathLiveA11yStrings.parent": "elemento pai",
+ "MathLiveA11yStrings.placeholder": "placeholder",
+ "MathLiveA11yStrings.prompt": "prompt",
+ "MathLiveA11yStrings.radicand": "radicando",
+ "MathLiveA11yStrings.rule": "regra",
+ "MathLiveA11yStrings.selected": "selecionado: ",
+ "MathLiveA11yStrings.space": "espaço",
+ "MathLiveA11yStrings.spacing": "espaçamento",
+ "MathLiveA11yStrings.squareRoot": "raiz quadrada",
+ "MathLiveA11yStrings.startOf": "início de {relationName}: ",
+ "MathLiveA11yStrings.subscript": "subscrito",
+ "MathLiveA11yStrings.subscriptSuperscript": "subscrito-sobrescrito",
+ "MathLiveA11yStrings.superscript": "exponente",
+ "MathLiveA11yStrings.superscriptAndSubscript": "sobrescrito e subscrito",
+ "MathLiveA11yStrings.text": "texto",
"MessageLayout.backToLogin": "Continuar para a página de login",
"MoveModal.addTopic": "Adicionar nova pasta",
"MoveModal.cancel": "Cancelar",
@@ -1296,7 +1418,7 @@
"PasswordField.passwordLabel": "Senha",
"PasswordInstructionsSent.passwordInstructionsHeader": "Instruções enviadas. Obrigado!",
"PasswordInstructionsSent.passwordInstructionsText": "Se uma conta com o e-mail fornecido já existe, você receberá as instruções em breve. Se não encontrar um e-mail nosso, por favor, verifique a sua pasta de spam.",
- "PermissionsError.goToHomePageAction": "Ir para a página inicial",
+ "PermissionsError.backToHome": "Voltar à página inicial",
"PermissionsError.permissionDeniedHeader": "Você se esqueceu de fazer o login?",
"PoliciesModal.checkboxText": "Eu concordo com os termos acima",
"PoliciesModal.closeButton": "Fechar",
@@ -1306,6 +1428,7 @@
"PrivacyPolicyModal.updatedPrivacyHeader": "Política de privacidade atualizada",
"ProgressBar.progressText": "{percent}%",
"ProgressModal.defaultErrorText": "Falha na última tentativa de publicação",
+ "ProgressModal.draftHeader": "Salvando rascunho...",
"ProgressModal.lastPublished": "Publicado em {last_published}",
"ProgressModal.publishHeader": "Publicando canal",
"ProgressModal.syncError": "Falha na última tentativa de sincronização",
@@ -1346,6 +1469,19 @@
"RelatedResourcesTab.showPreviewBtnLabel": "Mostrar",
"RelatedResourcesTab.tooManyNextStepsWarning": "Limite o número de próximos passos para criar uma experiência de aprendizagem mais guiada",
"RelatedResourcesTab.tooManyPreviousStepsWarning": "Limite o número de passos anteriores para criar uma experiência de aprendizagem mais guiada",
+ "RemoveChannelFromListModal.cancel": "Cancelar",
+ "RemoveChannelFromListModal.channelRemovedSnackbar": "Canal removido",
+ "RemoveChannelFromListModal.removeBtn": "Remover",
+ "RemoveChannelFromListModal.removePrompt": "Você tem acesso somente de visualização para este canal. Confirme que você deseja removê-lo da sua lista de canais.",
+ "RemoveChannelFromListModal.removeTitle": "Remover da lista de canais",
+ "RemoveChannelModal.cancel": "Cancelar",
+ "RemoveChannelModal.deleteChannel": "Excluir canal",
+ "RemoveChannelModal.deleteChannelWithCLWarning": "Este canal foi compartilhado com a Biblioteca da comunidade. Sua exclusão não o removerá da Biblioteca da comunidade — ele ainda pode ser aprovado ou permanecer disponível.",
+ "RemoveChannelModal.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.",
+ "RemoveChannelModal.deleteTitle": "Excluir este canal",
+ "RemoveChannelModal.removeBtn": "Remover",
+ "RemoveChannelModal.removePrompt": "Você tem acesso somente de visualização para este canal. Confirme que você deseja removê-lo da sua lista de canais.",
+ "RemoveChannelModal.removeTitle": "Remover da lista de canais",
"ReportErrorModal.closeAction": "Fechar",
"ReportErrorModal.emailDescription": "Entre em contato com o nosso suporte técnico detalhando o erro e faremos o possível para ajudar.",
"ReportErrorModal.emailPrompt": "Enviar um e-mail para os desenvolvedores",
@@ -1533,8 +1669,11 @@
"SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Muito avançado para o nível de conhecimento de alunos que estou procurando",
"SearchRecommendationsStrings.tooBasicForLearnersLabel": "Muito básico para o nível de conhecimento de alunos que estou procurando",
"SearchRecommendationsStrings.tryAgainLink": "Tentar novamente",
+ "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Experimente a nova funcionalidade de \"Recomendações\"!",
+ "SearchRecommendationsStrings.trySearchRecommendationsText": "Com base no título e na descrição da pasta em que você está trabalhando, sugerimos recursos relevantes da biblioteca do Kolibri. Escolha \"Importar de canais\" em qualquer pasta de seus canais para ver as recomendações.",
"SearchRecommendationsStrings.undoAction": "Desfazer",
"SearchRecommendationsStrings.viewMoreLink": "Ver mais",
+ "SearchRecommendationsStrings.viewRecommendationsButton": "Ver recomendações",
"SearchResultsList.failedToLoad": "Falha ao carregar os resultados da busca",
"SearchResultsList.resultsPerPageLabel": "Resultados por página",
"SearchResultsList.saveSearchAction": "Salvar busca",
@@ -1588,10 +1727,121 @@
"Storage.spaceUsedOfMax": "{qty} de {max}",
"Storage.storagePercentageUsed": "{qty}% do armazenamento usado",
"Storage.videoFiles": "Os arquivos de vídeo devem ser comprimidos para maximizar o espaço de armazenamento e garantir distribuição off-line e uma reprodução fluidas. Uma vez compactado, o armazenamento total necessário pode ser inferior ao que você estimou originalmente.",
+ "StudioChannelCard.channelLanguageNotSetIndicator": "Nenhum idioma definido",
+ "StudioChannelCard.details": "Detalhes",
+ "StudioChannelCard.lastPublished": "Publicado em {last_published}",
+ "StudioChannelCard.lastUpdated": "Atualizado {updated}",
+ "StudioChannelCard.multipleCountries": "Vários países",
+ "StudioChannelCard.resourceCount": "{count, plural, one {}\n =1 {# conteúdo}\n other {# conteúdos}}",
+ "StudioChannelCard.selectChannel": "Selecionar {name}",
+ "StudioChannelCard.unpublishedText": "Não publicado",
+ "StudioChannelsPage.invitations": "Você tem {count, plural, one {}\n =1 {# convite}\n other {# convites}}",
+ "StudioChannelsPage.noChannelsFound": "Nenhum canal encontrado",
+ "StudioCollectionsTable.aboutChannelSets": "Sobre os conjuntos",
+ "StudioCollectionsTable.aboutChannelSetsLink": "Saiba mais sobre coleções",
+ "StudioCollectionsTable.addChannelSetTitle": "Novo conjunto",
+ "StudioCollectionsTable.cancel": "Cancelar",
+ "StudioCollectionsTable.cancelButtonLabel": "Fechar",
+ "StudioCollectionsTable.channelNumber": "Número de canais",
+ "StudioCollectionsTable.channelSetsDescriptionText": "Um conjunto contém vários canais do Kolibri Studio que podem ser importados de uma vez ao Kolibri com um único token de conjunto.",
+ "StudioCollectionsTable.channelSetsDisclaimer": "Você precisará da versão 0.12.0 ou superior do Kolibri para importar conjuntos de canais",
+ "StudioCollectionsTable.channelSetsInstructionsText": "Você pode fazer um conjunto selecionando os canais que quer importar juntos.",
+ "StudioCollectionsTable.collectionDeleted": "Coleção excluída",
+ "StudioCollectionsTable.copiedTokenId": "Token copiado",
+ "StudioCollectionsTable.copyFailed": "Falha ao copiar",
+ "StudioCollectionsTable.copyToken": "Copiar token",
+ "StudioCollectionsTable.delete": "Excluir conjunto",
+ "StudioCollectionsTable.deleteChannelSetText": "Tem certeza que deseja excluir esta coleção?",
+ "StudioCollectionsTable.deleteChannelSetTitle": "Excluir conjunto",
+ "StudioCollectionsTable.deleteError": "Erro ao excluir coleção",
+ "StudioCollectionsTable.edit": "Editar conjunto",
+ "StudioCollectionsTable.noChannelSetsFound": "Você pode integrar vários canais para criar um conjunto. O conjunto inteiro pode então ser importado ao Kolibri de uma vez por meio de um token de conjunto.",
+ "StudioCollectionsTable.options": "Opções",
+ "StudioCollectionsTable.pageTitle": "Conjuntos",
+ "StudioCollectionsTable.saving": "Salvando",
+ "StudioCollectionsTable.tableCaption": "Lista de coleções",
+ "StudioCollectionsTable.title": "Nome do conjunto",
+ "StudioCollectionsTable.token": "ID do token",
+ "StudioCopyToken.copiedTokenId": "Token copiado",
+ "StudioCopyToken.copyFailed": "Falha ao copiar",
+ "StudioCopyToken.token": "Token",
+ "StudioCopyToken.tooltipText": "Copiar token para importar o canal no Kolibri",
+ "StudioDetailsPanel.AVERAGE": "Médio",
+ "StudioDetailsPanel.LARGE": "Grande",
+ "StudioDetailsPanel.SMALL": "Pequeno",
+ "StudioDetailsPanel.VERY_LARGE": "Muito grande",
+ "StudioDetailsPanel.VERY_SMALL": "Muito pequeno",
+ "StudioDetailsPanel.aggregatorToolTip": "Website ou organização hospedando este conjunto de conteúdos, mas não necessariamente o criador ou detentor dos direitos autorais",
+ "StudioDetailsPanel.aggregatorsLabel": "Agregadores",
+ "StudioDetailsPanel.assessmentsIncludedText": "Avaliações",
+ "StudioDetailsPanel.authorToolTip": "Pessoa ou organização que criou este conteúdo",
+ "StudioDetailsPanel.authorsLabel": "Autores",
+ "StudioDetailsPanel.categoriesHeading": "Categorias",
+ "StudioDetailsPanel.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri",
+ "StudioDetailsPanel.coachHeading": "Materiais para professores",
+ "StudioDetailsPanel.containsContentHeading": "Contém conteúdo de",
+ "StudioDetailsPanel.containsHeading": "Contém",
+ "StudioDetailsPanel.copyrightHoldersLabel": "Detentores dos direitos autorais",
+ "StudioDetailsPanel.creationHeading": "Criado em",
+ "StudioDetailsPanel.currentVersionHeading": "Versão publicada",
+ "StudioDetailsPanel.languagesHeading": "Idiomas",
+ "StudioDetailsPanel.levelsHeading": "Níveis",
+ "StudioDetailsPanel.licensesLabel": "Licenças",
+ "StudioDetailsPanel.primaryLanguageHeading": "Idioma principal",
+ "StudioDetailsPanel.providerToolTip": "Organização que encomendou ou distribui o conteúdo",
+ "StudioDetailsPanel.providersLabel": "Provedores",
+ "StudioDetailsPanel.publishedHeading": "Publicado em",
+ "StudioDetailsPanel.resourceHeading": "Conteúdos totais",
+ "StudioDetailsPanel.sampleFromChannelHeading": "Amostra de conteúdo deste canal",
+ "StudioDetailsPanel.sizeHeading": "Tamanho do canal",
+ "StudioDetailsPanel.sizeText": "{text} ({size})",
+ "StudioDetailsPanel.subtitlesHeading": "Legendas",
+ "StudioDetailsPanel.tagsHeading": "Etiquetas comuns",
+ "StudioDetailsPanel.tokenHeading": "Token do canal",
+ "StudioDetailsPanel.unpublishedText": "Não publicado",
+ "StudioImmersiveModal.close": "Fechar",
+ "StudioMessageLayout.backToLogin": "Continuar para a página de login",
+ "StudioMyChannels.copyToken": "Copiar token do canal",
+ "StudioMyChannels.deleteChannel": "Excluir canal",
+ "StudioMyChannels.editChannel": "Editar detalhes do canal",
+ "StudioMyChannels.goToWebsite": "Ir para o website de origem",
+ "StudioMyChannels.moreOptions": "Mais opções",
+ "StudioMyChannels.newChannel": "Novo canal",
+ "StudioMyChannels.title": "Meus canais",
+ "StudioMyChannels.viewContent": "Visualizar canal no Kolibri",
"StudioOfflineAlert.offlineText": "Você parece estar desconectado. Suas mudanças serão salvas quando a sua conexão voltar.",
"StudioOfflineAlert.onlineText": "Você está on-line novamente.",
+ "StudioStarredChannels.copyToken": "Copiar token do canal",
+ "StudioStarredChannels.deleteChannel": "Excluir canal",
+ "StudioStarredChannels.editChannel": "Editar detalhes do canal",
+ "StudioStarredChannels.goToWebsite": "Ir para o website de origem",
+ "StudioStarredChannels.moreOptions": "Mais opções",
+ "StudioStarredChannels.removeChannel": "Remover canal",
+ "StudioStarredChannels.title": "Canais favoritos",
+ "StudioStarredChannels.viewContent": "Visualizar canal no Kolibri",
"StudioTree.missingTitle": "Falta o título",
"StudioTree.optionsTooltip": "Opções",
+ "StudioViewOnlyChannels.copyToken": "Copiar token do canal",
+ "StudioViewOnlyChannels.goToWebsite": "Ir para o website de origem",
+ "StudioViewOnlyChannels.moreOptions": "Outras opções",
+ "StudioViewOnlyChannels.removeChannel": "Remover canal",
+ "StudioViewOnlyChannels.title": "Canais de somente visualização",
+ "StudioViewOnlyChannels.viewContent": "Visualizar canal no Kolibri",
+ "SubscriptionCard.annualPrice": "${price, number}/ano",
+ "SubscriptionCard.cancelNotice": "Sua assinatura expira em {date, date, medium}. Após esta data, o armazenamento será removido.",
+ "SubscriptionCard.dismiss": "Descartar",
+ "SubscriptionCard.genericError": "Houve um problema na conexão com o provedor de pagamento. Tente novamente.",
+ "SubscriptionCard.instantUpgrade": "Atualize o armazenamento agora",
+ "SubscriptionCard.manageSubscription": "Gerenciar assinatura",
+ "SubscriptionCard.renewalNotice": "A renovação automática de sua assinatura será em {date, date, medium}.",
+ "SubscriptionCard.storageAmount": "Armazenamento (GB)",
+ "SubscriptionCard.storageIncluded": "{size} incluído em sua assinatura",
+ "SubscriptionCard.storageRange": "Digite um número entre 1 e 50",
+ "SubscriptionCard.subscriptionActive": "Assinatura de armazenamento ativa",
+ "SubscriptionCard.subscriptionCanceling": "Assinatura cancelada",
+ "SubscriptionCard.upgradeDescription": "Comprar armazenamento adicional por US$ 15/GB por ano.",
+ "SubscriptionCard.upgradeNow": "Atualizar agora",
+ "SubscriptionCard.upgradeSuccess": "Armazenamento aumentado para {size}",
"SubtitlesList.acceptedFormatsTooltip": "Formatos suportados: {extensions}",
"SubtitlesList.addSubtitleText": "Adicionar legendas",
"SubtitlesList.subtitlesHeader": "Legendas",
@@ -1620,7 +1870,7 @@
"TechnicalTextBlock.copiedToClipboardConfirmation": "Copiado para a área de transferência",
"TechnicalTextBlock.copiedToClipboardFailure": "Falha ao copiar para área de transferência",
"TechnicalTextBlock.copyToClipboardButtonPrompt": "Copiar para área de transferência",
- "TermsOfServiceModal.ToSHeader": "Terms of Service",
+ "TermsOfServiceModal.ToSHeader": "Termos de serviço",
"TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions",
"TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;",
"TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and",
@@ -1651,7 +1901,7 @@
"TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.",
"TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality",
"TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.",
- "TermsOfServiceModal.communityStandardsHeader": "Community Standards",
+ "TermsOfServiceModal.communityStandardsHeader": "Normas comunitárias",
"TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards",
"TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.",
"TermsOfServiceModal.definitionsHeader": "Definitions",
@@ -1678,7 +1928,7 @@
"TermsOfServiceModal.licensingList1Item2": "If you are not yourself the copyright holder, you must have the rights to distribute the uploaded Content, either through explicit written permission from the copyright holder, or as allowed by the terms of the license under which the Content has been released.",
"TermsOfServiceModal.licensingList1Item3": "If you are the copyright holder of the uploaded content, then by marking the Content you upload with a particular license, you are agreeing for the Content to be distributed and used under the terms of that license in perpetuity.",
"TermsOfServiceModal.licensingList2Item1": "Descriptive metadata: This includes primary metadata associated with a single piece of Content, for example, titles, descriptions, and other elements which constitute a definitive part of the Content regardless of which system it appears on. These metadata elements will fall under the same copyright and licensing as the Content itself.",
- "TermsOfServiceModal.licensingList2Item2": "Organizational metadata: This defines how a piece of content may be used, aids with discovery, and places it within some broader structure of relations on the Service, for example, tags, curation into folders (including the titles of those folders), and other elements pertaining to the display and ordering of Content on the system itself. By using the Service, you agree that work you do to generate organizational metadata elements are released into the Public Domain, and may be made available for others to use, without any claim to copyright or restricted licensing. We may also share, leverage and distribute this organizational metadata. This is so that we can benefit others and improve the impact of our platforms.",
+ "TermsOfServiceModal.licensingList2Item2": "Metadados organizacionais definem como um conteúdo educacional pode ser usado, ajudam a descoberta do material, e criam mais estruturas e melheroes relações com o Serviço, por exemplo, etiquetas, curadoria em pastas (incluindo os títulos das pastas), e outros elementos relacionados `a apresentação e `a ordem do Conteúdo no próprio sistema. Ao usar o Serviço, todos os metadados organizacionais que você venha a ter criado passam a ser de Domínio Público, podendo ser disponibilizados para outras pessoas. Você abdica de qualquer reivindicação de direitos autorais ou licença restrita. Também fica a nosso critério a distribuição desses metadados para que possamos beneficiar a todos e melhorar o impacto de nossas plataformas.",
"TermsOfServiceModal.licensingP1": "The Service allows you to upload and distribute Content. When you do, the following terms apply:",
"TermsOfServiceModal.licensingP2": "We follow a policy of making content, including its associated metadata, as open as possible while following the appropriate copyright laws. With this in mind, we distinguish between:",
"TermsOfServiceModal.miscellaneousHeader": "Miscellaneous",
@@ -1690,7 +1940,7 @@
"TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.",
"TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights",
"TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.",
- "TermsOfServiceModal.updatedToSHeader": "Updated terms of service",
+ "TermsOfServiceModal.updatedToSHeader": "Termos de serviço atualizados",
"TermsOfServiceModal.userContentHeader": "User-Generated Content",
"TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.",
"TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.",
@@ -1715,82 +1965,101 @@
"TextArea.fieldRequiredMessage": "Campo obrigatório",
"TextField.fieldRequiredMessage": "Campo obrigatório",
"Thumbnail.thumbnail": "{title} miniatura",
+ "ThumbnailGenerator.closeButtonLabel": "OK",
"ThumbnailGenerator.generatedDefaultFilename": "Miniatura gerada",
"ThumbnailGenerator.thumbnailGenerationFailedHeader": "Não foi possível gerar miniatura",
"ThumbnailGenerator.thumbnailGenerationFailedText": "Ocorreu um erro ao gerar uma miniatura",
- "TipTapEditorStrings.addLink": "Add link",
- "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load",
- "TipTapEditorStrings.altTextLabel": "Alt text (Optional)",
- "TipTapEditorStrings.altTextPlaceholder": "Describe your image...",
- "TipTapEditorStrings.bold": "Strong",
- "TipTapEditorStrings.bulletList": "Bullet list",
- "TipTapEditorStrings.cancel": "Cancel",
- "TipTapEditorStrings.cancelLoading": "Cancel loading",
- "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.",
- "TipTapEditorStrings.close": "Close",
- "TipTapEditorStrings.closeModal": "Close modal",
- "TipTapEditorStrings.codeBlock": "Code block",
- "TipTapEditorStrings.copy": "Copy",
- "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions",
- "TipTapEditorStrings.copyLink": "Copy link",
- "TipTapEditorStrings.defaultImageName": "Image",
- "TipTapEditorStrings.edit": "Edit",
- "TipTapEditorStrings.editImage": "Edit image",
- "TipTapEditorStrings.editLink": "Edit link",
- "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.",
+ "TipTapEditorStrings.TipTapEditorLabel": "editor de texto - Pressione Enter para começar a editar",
+ "TipTapEditorStrings.TipTapViewerLabel": "conteúdo do editor de texto",
+ "TipTapEditorStrings.addLink": "Adicionar link",
+ "TipTapEditorStrings.alignLeft": "Alinhar à esquerda",
+ "TipTapEditorStrings.alignRight": "Alinhar à direita",
+ "TipTapEditorStrings.altTextDescription": "O texto alternativo é necessário para permitir que alunos com deficiência visual respondam às perguntas, e também é exibido quando a imagem não carregar",
+ "TipTapEditorStrings.altTextLabel": "Texto alternativo (opcional)",
+ "TipTapEditorStrings.altTextPlaceholder": "Descreva sua imagem...",
+ "TipTapEditorStrings.bold": "Negrito",
+ "TipTapEditorStrings.bulletList": "Lista com marcadores",
+ "TipTapEditorStrings.cancel": "Cancelar",
+ "TipTapEditorStrings.cancelLoading": "Cancelar carregamento",
+ "TipTapEditorStrings.clearFormatting": "Limpar formatação",
+ "TipTapEditorStrings.clipboardAccessFailed": "Não foi possível acessar a área de transferência. Tente copiar novamente.",
+ "TipTapEditorStrings.close": "Fechar",
+ "TipTapEditorStrings.closeModal": "Fechar modal",
+ "TipTapEditorStrings.codeBlock": "Bloco de código",
+ "TipTapEditorStrings.collapseFormattingBar": "Minimizar barra de formatação",
+ "TipTapEditorStrings.copy": "Copiar",
+ "TipTapEditorStrings.copyAndPasteActions": "Ações de copiar e colar",
+ "TipTapEditorStrings.copyLink": "Copiar o link",
+ "TipTapEditorStrings.decreaseFormatSize": "Reduzir tamanho do formato",
+ "TipTapEditorStrings.defaultImageName": "Imagem",
+ "TipTapEditorStrings.edit": "Editar",
+ "TipTapEditorStrings.editImage": "Editar imagem",
+ "TipTapEditorStrings.editLink": "Editar link",
+ "TipTapEditorStrings.editorControls": "Controles do editor",
+ "TipTapEditorStrings.errorUploadingImage": "Erro ao fazer upload da imagem",
+ "TipTapEditorStrings.expandFormattingBar": "Expandir barra de formatação",
+ "TipTapEditorStrings.failedToProcessImage": "Falha ao processar o arquivo de imagem.",
"TipTapEditorStrings.fileSizeUnit": "MB.",
- "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ",
- "TipTapEditorStrings.formatHeader1": "Header 1",
- "TipTapEditorStrings.formatHeader2": "Header 2",
- "TipTapEditorStrings.formatHeader3": "Header 3",
+ "TipTapEditorStrings.fileTooLarge": "O arquivo é muito grande. O tamanho máximo é ",
+ "TipTapEditorStrings.formatHeader1": "Cabeçalho 1",
+ "TipTapEditorStrings.formatHeader2": "Cabeçalho 2",
+ "TipTapEditorStrings.formatHeader3": "Cabeçalho 3",
"TipTapEditorStrings.formatNormal": "Normal",
- "TipTapEditorStrings.formatOptions": "Format options",
- "TipTapEditorStrings.formatSmall": "Small",
- "TipTapEditorStrings.formulasMenuTitle": "Special Characters",
- "TipTapEditorStrings.goToLink": "Go to link",
- "TipTapEditorStrings.historyActions": "History actions",
- "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually",
- "TipTapEditorStrings.imagePreview": "Image preview",
- "TipTapEditorStrings.insert": "Insert",
- "TipTapEditorStrings.insertImage": "Insert image",
- "TipTapEditorStrings.insertLink": "Insert link",
- "TipTapEditorStrings.insertTools": "Insert tools",
- "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ",
- "TipTapEditorStrings.italic": "Italic",
+ "TipTapEditorStrings.formatOptions": "Opções de formatação",
+ "TipTapEditorStrings.formatSize": "Tamanho do formato",
+ "TipTapEditorStrings.formatSmall": "Pequeno",
+ "TipTapEditorStrings.formulasMenuTitle": "Caracteres especiais",
+ "TipTapEditorStrings.goToLink": "Ir para o link",
+ "TipTapEditorStrings.historyActions": "Ações do histórico",
+ "TipTapEditorStrings.imageDropZoneText": "Arraste e solte uma imagem aqui ou envie manualmente",
+ "TipTapEditorStrings.imagePreview": "Pré-visualização da imagem",
+ "TipTapEditorStrings.increaseFormatSize": "Aumentar tamanho do formato",
+ "TipTapEditorStrings.insert": "Inserir",
+ "TipTapEditorStrings.insertContent": "Inserir conteúdo",
+ "TipTapEditorStrings.insertContentMenu": "Inseir menu de conteúdo",
+ "TipTapEditorStrings.insertContentOption": "Inserir opção de conteúdo",
+ "TipTapEditorStrings.insertImage": "Inserir imagem",
+ "TipTapEditorStrings.insertLink": "Inserir link",
+ "TipTapEditorStrings.insertTools": "Inserir ferramentas",
+ "TipTapEditorStrings.invalidFileType": "Tipo de arquivo inválido. Use: ",
+ "TipTapEditorStrings.italic": "Itálico",
"TipTapEditorStrings.link": "Link",
- "TipTapEditorStrings.linkActions": "Link actions",
- "TipTapEditorStrings.listFormatting": "List formatting",
- "TipTapEditorStrings.mathFormula": "Math formula",
- "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.",
- "TipTapEditorStrings.noFileProvided": "No file provided.",
- "TipTapEditorStrings.numberedList": "Numbered list",
- "TipTapEditorStrings.opensInNewTab": "(opens in new tab)",
- "TipTapEditorStrings.paste": "Paste",
- "TipTapEditorStrings.pasteOptions": "Paste options",
- "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu",
- "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting",
- "TipTapEditorStrings.redo": "Redo",
- "TipTapEditorStrings.remove": "Remove",
- "TipTapEditorStrings.removeImage": "Remove image",
- "TipTapEditorStrings.removeLink": "Remove link",
- "TipTapEditorStrings.replaceFile": "Replace file",
- "TipTapEditorStrings.save": "Save",
- "TipTapEditorStrings.saveChanges": "Save changes",
- "TipTapEditorStrings.scriptFormatting": "Script formatting",
- "TipTapEditorStrings.selectFile": "Select file",
- "TipTapEditorStrings.selectFileToUpload": "Select file to upload",
- "TipTapEditorStrings.strikethrough": "Strikethrough",
- "TipTapEditorStrings.subscript": "Subscript",
- "TipTapEditorStrings.superscript": "Superscript",
- "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp",
- "TipTapEditorStrings.text": "Text",
- "TipTapEditorStrings.textFormatOptions": "Text format options",
- "TipTapEditorStrings.textFormattingOptions": "Text formatting options",
- "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar",
- "TipTapEditorStrings.textStyleFormatting": "Text style formatting",
- "TipTapEditorStrings.underline": "Underline",
- "TipTapEditorStrings.undo": "Undo",
- "TipTapEditorStrings.uploadImage": "Upload image",
+ "TipTapEditorStrings.linkActions": "Ações do link",
+ "TipTapEditorStrings.listFormatting": "Formatação de lista",
+ "TipTapEditorStrings.loadingFormulas": "Carregando editor de matemática",
+ "TipTapEditorStrings.mathFormula": "Fórmula matemática",
+ "TipTapEditorStrings.moreButtonText": "Mais",
+ "TipTapEditorStrings.multipleFilesDroppedWarning": "Vários arquivos foram descartados. Apenas o primeiro arquivo foi selecionado.",
+ "TipTapEditorStrings.noEnoughStorageSpace": "Não há espaço suficiente disponível. O tamanho do arquivo excede o armazenamento restante.",
+ "TipTapEditorStrings.noFileProvided": "Nenhum arquivo fornecido.",
+ "TipTapEditorStrings.numberedList": "Lista numerada",
+ "TipTapEditorStrings.opensInNewTab": "(abre em nova aba)",
+ "TipTapEditorStrings.paste": "Colar",
+ "TipTapEditorStrings.pasteOptions": "Opções de colar",
+ "TipTapEditorStrings.pasteOptionsMenu": "Menu opções de colar",
+ "TipTapEditorStrings.pasteWithoutFormatting": "Colar sem formatação",
+ "TipTapEditorStrings.redo": "Refazer",
+ "TipTapEditorStrings.remove": "Remover",
+ "TipTapEditorStrings.removeImage": "Remover imagem",
+ "TipTapEditorStrings.removeLink": "Remover link",
+ "TipTapEditorStrings.replaceFile": "Substituir arquivo",
+ "TipTapEditorStrings.save": "Salvar",
+ "TipTapEditorStrings.saveChanges": "Salvar alterações",
+ "TipTapEditorStrings.scriptFormatting": "Formatação de script",
+ "TipTapEditorStrings.selectFile": "Selecionar arquivo",
+ "TipTapEditorStrings.selectFileToUpload": "Selecione um arquivo para upload",
+ "TipTapEditorStrings.strikethrough": "Tachado",
+ "TipTapEditorStrings.subscript": "Subscrito",
+ "TipTapEditorStrings.superscript": "Exponente",
+ "TipTapEditorStrings.supportedFileTypes": "Tipos de arquivo suportados: { extensions }",
+ "TipTapEditorStrings.text": "Texto",
+ "TipTapEditorStrings.textFormatOptions": "Opções de formato texto",
+ "TipTapEditorStrings.textFormattingOptions": "Opções de formatação de texto",
+ "TipTapEditorStrings.textFormattingToolbar": "Barra de formatação de texto",
+ "TipTapEditorStrings.textStyleFormatting": "Formatação de estilo texto",
+ "TipTapEditorStrings.underline": "Sublinhar",
+ "TipTapEditorStrings.undo": "Desfazer",
+ "TipTapEditorStrings.uploadImage": "Enviar imagem",
"TitleStrings.catalogTitle": "Catálogo da Biblioteca de Conteúdos Kolibri",
"TitleStrings.defaultTitle": "Kolibri Studio",
"TitleStrings.tabTitle": "{title} - {site}",
@@ -1814,25 +2083,26 @@
"TreeView.showSidebar": "Mostrar a barra lateral",
"TreeView.updatedResourcesReadyForReview": "Conteúdos atualizados estão prontos para revisão",
"TreeViewBase.apiGenerated": "Gerado pela API",
- "TreeViewBase.cancel": "Cancelar",
"TreeViewBase.channelDeletedSnackbar": "Canal excluído",
"TreeViewBase.channelDetails": "Ver detalhes do canal",
"TreeViewBase.deleteChannel": "Excluir canal",
- "TreeViewBase.deleteChannelButton": "Excluir canal",
- "TreeViewBase.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.",
- "TreeViewBase.deleteTitle": "Excluir este canal",
"TreeViewBase.editChannel": "Editar detalhes do canal",
"TreeViewBase.emptyChannelTooltip": "Você não pode publicar um canal vazio",
"TreeViewBase.getToken": "Obter token",
"TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {conteúdo está incompleto e não pode ser publicado} other {conteúdos estão incompletos e não podem ser publicados}}",
+ "TreeViewBase.inviteCollaborators": "Convidar colaboradores",
"TreeViewBase.noChangesText": "Nenhuma alteração encontrada no canal",
"TreeViewBase.noLanguageSetError": "Campo obrigatório",
"TreeViewBase.openTrash": "Abrir lixeira",
"TreeViewBase.publishButton": "Publicar",
"TreeViewBase.publishButtonTitle": "Tornar este canal disponível para importação no Kolibri",
"TreeViewBase.shareChannel": "Compartilhar canal",
+ "TreeViewBase.shareMenuButton": "Compartilhar",
+ "TreeViewBase.shareToken": "Compartilhar token",
+ "TreeViewBase.submitToCommunityLibrary": "Enviar para a Biblioteca da comunidade",
"TreeViewBase.syncChannel": "Sincronizar conteúdos",
"TreeViewBase.viewOnly": "Para visualizar",
+ "Uploader.closeButtonLabel": "OK",
"Uploader.listDelimiter": ", ",
"Uploader.maxFileSizeText": "{count, plural, one {}\n =1 {# arquivo não será enviado.}\n other {# arquivos não serão enviados.}} O tamanho do(s) arquivo(s) deve ser menor que {size}",
"Uploader.noStorageHeader": "Espaço insuficiente",
@@ -1906,4 +2176,4 @@
"sharedVue.masteryModelRequired": "O critério de domínio é obrigatório",
"sharedVue.shortActivityLteThirty": "O número deve ser igual ou menor que 30",
"sharedVue.titleRequired": "O título é obrigatório"
-}
+}
\ No newline at end of file
diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po
index dba8cd2438..ab71e4d939 100644
--- a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po
+++ b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kolibri-studio\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-09-03 19:45+0000\n"
-"PO-Revision-Date: 2025-09-12 13:59\n"
+"POT-Creation-Date: 2026-04-16 00:57+0000\n"
+"PO-Revision-Date: 2026-04-24 16:20\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
@@ -14,8 +14,8 @@ msgstr ""
"X-Crowdin-Project: kolibri-studio\n"
"X-Crowdin-Project-ID: 286000\n"
"X-Crowdin-Language: pt-BR\n"
-"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n"
-"X-Crowdin-File-ID: 4886\n"
+"X-Crowdin-File: /unstable/django.po\n"
+"X-Crowdin-File-ID: 4322\n"
#: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8
#: contentcuration/settings.py:271
@@ -26,25 +26,25 @@ msgstr "Árabe"
msgid "The site is currently in read-only mode. Please try again later."
msgstr "No momento, o site está em modo somente leitura. Por favor, tente novamente mais tarde."
-#: contentcuration/models.py:342
+#: contentcuration/models.py:356
msgid "Not enough space. Check your storage under Settings page."
msgstr "Espaço insuficiente. Verifique seu armazenamento na página Configurações."
-#: contentcuration/models.py:374 contentcuration/models.py:386
+#: contentcuration/models.py:440 contentcuration/models.py:452
msgid "Out of storage! Request more space under Settings > Storage."
msgstr "Sem armazenamento! Solicite mais espaço em Configurações > Armazenamento."
-#: contentcuration/models.py:2155
+#: contentcuration/models.py:2531
msgid " (Original)"
msgstr " (Original)"
-#: contentcuration/models.py:3297
+#: contentcuration/models.py:3848
msgid "Created DateTime"
-msgstr "Data/Hora de criação"
+msgstr ""
-#: contentcuration/models.py:3299
+#: contentcuration/models.py:3850
msgid "Datetime field when the custom_metadata for task was created in UTC"
-msgstr "Campo data-hora quando o metadado personalizado para tarefa foi criado em UTC"
+msgstr ""
#: contentcuration/settings.py:269
msgid "English"
@@ -719,7 +719,7 @@ msgstr "Estamos com problemas com um serviço de terceiros. Isso significa que o
msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here"
msgstr "Estamos com problemas com o nosso centro de dados. Isso significa que você pode ter problemas de conexão enquanto usa o Studio. Agradecemos pela paciência enquanto esses problemas são resolvidos. Para saber o status do serviço, por favor visite aqui"
-#: contentcuration/utils/publish.py:101
+#: contentcuration/utils/publish.py:103
msgid "Kolibri Studio Channel Published"
msgstr "Canal do Kolibri Studio publicado"
@@ -731,14 +731,15 @@ msgstr "Relatório de problemas do Kolibri Studio"
msgid "Kolibri Studio account deleted"
msgstr "Conta do Kolibri Studio excluída"
-#: kolibri_public/views.py:223
+#: kolibri_public/views.py:323
msgid "Resource"
msgstr "Conteúdo"
-#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94
+#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167
msgid "API version is unavailable"
msgstr "A versão da API está indisponível"
-#: kolibri_public/views_v1.py:97
+#: kolibri_public/views_v1.py:170
msgid "No channel matching {} found"
msgstr "Nenhum canal correspondente a {} encontrado"
+
diff --git a/crowdin.yaml b/crowdin.yaml
deleted file mode 100644
index e47a0ea800..0000000000
--- a/crowdin.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-project_identifier_env: CROWDIN_PROJECT
-api_key_env: CROWDIN_API_KEY
-base_path: .
-
-files:
- - source: "/contentcuration/locale/en/LC_FRONTEND_MESSAGES/*.json"
- translation: "/contentcuration/locale/%locale_with_underscore%/LC_FRONTEND_MESSAGES/%original_file_name%"
-
- - source: "/contentcuration/locale/en/LC_MESSAGES/*.po"
- translation: "/contentcuration/locale/%locale_with_underscore%/LC_MESSAGES/%original_file_name%"
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 0000000000..e302fc92ce
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,45 @@
+#
+# Crowdin CLI configuration for Kolibri Studio
+# See https://crowdin.github.io/crowdin-cli/configuration for more information
+# See https://support.crowdin.com/developer/configuration-file/ for all available options
+#
+
+#
+# Your Crowdin credentials
+#
+"project_id": "286000"
+"base_path": "."
+"base_url": "https://api.crowdin.com"
+
+#
+# Defines whether to preserve the original directory structure in the Crowdin project
+#
+"preserve_hierarchy": false
+
+#
+# Files configuration.
+#
+files: [
+ # CSV translation files
+ {
+ "source": "/contentcuration/locale/en/LC_MESSAGES/*.csv",
+ "first_line_contains_header": true,
+ "scheme" : "identifier,source_phrase,context,translation",
+ "update_option": "update_as_unapproved",
+ "translation": "/contentcuration/locale/%locale_with_underscore%/LC_MESSAGES/%original_file_name%",
+ "languages_mapping": &language_mapping {
+ "locale_with_underscore": {
+ "ar": "ar",
+ "fr": "fr_FR",
+ "hi": "hi_IN",
+ },
+ },
+ },
+ # PO translation files
+ {
+ "source": "/contentcuration/locale/en/LC_MESSAGES/*.po",
+ "update_option": "update_as_unapproved",
+ "translation": "/contentcuration/locale/%locale_with_underscore%/LC_MESSAGES/%original_file_name%",
+ "languages_mapping": *language_mapping,
+ },
+]
diff --git a/docker-compose.alt.yml b/docker-compose.alt.yml
deleted file mode 100644
index acf01a97e3..0000000000
--- a/docker-compose.alt.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-version: '3.4'
-
-# Alternate config that maps services to host ports so you can run everything but the Django
-# and webpack servers in Docker
-#
-# Usage:
-# docker-compose -f docker-compose.yml -f docker-compose.alt.yml up minio postgres redis
-
-services:
- postgres:
- ports:
- - "5432:5432"
-
- redis:
- ports:
- - "6379:6379"
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
new file mode 100644
index 0000000000..8ac4459c0d
--- /dev/null
+++ b/docker-compose.prod.yml
@@ -0,0 +1,24 @@
+services:
+ studio-app:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.prod
+ image: learningequality/studio-app-prod
+ ports:
+ - "8080:8081"
+ volumes:
+ - .:/src
+ environment:
+ MPLBACKEND: ps
+ SHELL: /bin/bash
+ AWS_S3_ENDPOINT_URL: http://minio:9000
+ AWS_BUCKET_NAME: content
+ DATA_DB_HOST: postgres
+ DJANGO_SETTINGS_MODULE: contentcuration.settings
+ RUN_MODE: docker-compose
+ CELERY_TIMEZONE: America/Los_Angeles
+ CELERY_REDIS_DB: 0
+ CELERY_BROKER_ENDPOINT: redis
+ CELERY_RESULT_BACKEND_ENDPOINT: redis
+ CELERY_REDIS_PASSWORD: ""
+ REMAP_SIGTERM: "SIGQUIT"
diff --git a/docker-compose.yml b/docker-compose.yml
index 719fc797ed..dd3341f83e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3.4'
-
x-studio-environment:
&studio-environment
MPLBACKEND: ps
@@ -15,7 +13,7 @@ x-studio-environment:
CELERY_RESULT_BACKEND_ENDPOINT: redis
CELERY_REDIS_PASSWORD: ""
REMAP_SIGTERM: "SIGQUIT"
- PROBER_STUDIO_BASE_URL: http://studio-app:8080/{path}
+ WEBPACK_DEV_HOST: 0.0.0.0
x-studio-worker:
&studio-worker
@@ -77,12 +75,16 @@ services:
POSTGRES_USER: learningequality
POSTGRES_PASSWORD: kolibri
POSTGRES_DB: kolibri-studio
+ ports:
+ - "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data/pgdata
- .docker/postgres:/docker-entrypoint-initdb.d
redis:
image: redis:6.0.9
+ ports:
+ - "6379:6379"
volumes:
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
index f11338a199..b45370df4d 100644
--- a/docker/Dockerfile.dev
+++ b/docker/Dockerfile.dev
@@ -1,4 +1,4 @@
-FROM python:3.10-slim-bookworm
+FROM ghcr.io/astral-sh/uv:python3.10-trixie-slim
# Set the timezone
RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
@@ -10,26 +10,28 @@ WORKDIR /src
# System packages ##############################################################
-ENV DEBIAN_FRONTEND noninteractive
+ENV DEBIAN_FRONTEND=noninteractive
# Default Python file.open file encoding to UTF-8 instead of ASCII, workaround for le-utils setup.py issue
-ENV LANG C.UTF-8
+ENV LANG=C.UTF-8
+
RUN apt-get update && \
- apt-get -y install \
- curl fish man \
- python3-pip python3-dev \
- gcc libpq-dev libssl-dev libffi-dev make git gettext libjpeg-dev ffmpeg
-
-# Pin, Download and install node 18.x
-RUN apt-get update \
- && apt-get install -y ca-certificates curl gnupg \
- && mkdir -p /etc/apt/keyrings \
- && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
- && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
- && echo "Package: nodejs" >> /etc/apt/preferences.d/preferences \
- && echo "Pin: origin deb.nodesource.com" >> /etc/apt/preferences.d/preferences \
- && echo "Pin-Priority: 1001" >> /etc/apt/preferences.d/preferences\
- && apt-get update \
- && apt-get install -y nodejs
+ apt-get -y install --no-install-recommends \
+ curl \
+ fish \
+ man \
+ gcc \
+ libpq-dev \
+ libssl-dev \
+ libffi-dev \
+ make \
+ git \
+ gettext \
+ libjpeg-dev \
+ ffmpeg \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy Node.js from its image (does a merge)
+COPY --from=node:20-trixie-slim /usr/local /usr/local
################################################################################
@@ -42,11 +44,9 @@ RUN pnpm install
# Python packages ##############################################################
COPY requirements.txt requirements-dev.txt /src/
-RUN pip install --no-cache-dir --upgrade pip
# install pinned deps from pip-tools into system
-RUN pip install -r requirements.txt
-RUN pip install -r requirements-dev.txt
+RUN uv pip sync --system requirements.txt requirements-dev.txt
################################################################################
diff --git a/docs/dependencies.md b/docs/dependencies.md
index afa1e0f205..2c3ecbf385 100644
--- a/docs/dependencies.md
+++ b/docs/dependencies.md
@@ -1,11 +1,11 @@
# Adding or updating dependencies
-We use `pip-tools` to ensure all our dependencies use the same versions on all deployments.
+We use `uv` to manage Python dependencies and generate requirements files.
To add a dependency, add it to either `requirements.in` or `requirements-dev.in`, then
-run `pip-compile requirements[-dev].in` to generate the .txt file. Please make sure that
+run `uv pip compile requirements[-dev].in` to generate the .txt file. Please make sure that
both the `.in` and `.txt` file changes are part of the commit when updating dependencies.
-To update a dependency, use `pip-compile --upgrade-package [package-name] requirements[-dev].in`
+To update a dependency, use `uv pip compile --upgrade-package [package-name] requirements[-dev].in`
-For more details, please see the [pip-tools docs on Github](https://github.com/jazzband/pip-tools).
+For more details, please see the [uv documentation](https://docs.astral.sh/uv/).
diff --git a/docs/host_services_setup.md b/docs/host_services_setup.md
index 74aa267cbf..0034b140ea 100644
--- a/docs/host_services_setup.md
+++ b/docs/host_services_setup.md
@@ -3,7 +3,7 @@
This guide will walk through setting up Kolibri Studio for local development, where you'll run Studio's Python apps and all of Studio's services on your host machine, without the need for docker.
## Prerequisites
-For detailed instructions on installing and configuring Volta, pyenv, and pyenv-virtualenv, please see the [Prerequisites](./local_dev_host.md#prerequisites) section in our Local Development with host guide.
+For detailed instructions on installing and configuring Volta and uv, please see the [Prerequisites](./local_dev_host.md#prerequisites) section in our Local Development with host guide.
## Install system dependencies and services
Studio requires some background services to be running:
diff --git a/docs/local_dev_docker.md b/docs/local_dev_docker.md
index 3d89e3a38b..e30a5ab5ea 100644
--- a/docs/local_dev_docker.md
+++ b/docs/local_dev_docker.md
@@ -5,10 +5,10 @@ The following guide utilizes docker and docker-compose to run select services re
**Note:** If you are developing on Windows, it is recommended to use WSL (Windows Subsystem for Linux). Please follow the [WSL setup guide](./local_dev_wsl.md) for detailed instructions.
## Prerequisites
-For detailed instructions on installing and configuring Volta, pyenv, and pyenv-virtualenv, please see the [Prerequisites](./local_dev_host.md#prerequisites) section in our Local Development with host guide.
+For detailed instructions on installing and configuring Volta and uv, please see the [Prerequisites](./local_dev_host.md#prerequisites) section in our Local Development with host guide.
## Build your python virtual environment
-For complete instructions on installing Python 3.10.13, creating and activating the virtual environment, and installing Studio’s Python dependencies, please refer to the [Build Your Python Virtual Environment](./local_dev_host.md#build-your-python-virtual-environment) section in our Local Development with host guide.
+For complete instructions on creating and activating the virtual environment, and installing Studio’s Python dependencies, please refer to the [Build Your Python Virtual Environment](./local_dev_host.md#build-your-python-virtual-environment) section in our Local Development with host guide.
### A note about dependencies on Apple Silicon M1+
diff --git a/docs/local_dev_host.md b/docs/local_dev_host.md
index 548aa05d91..94b571aebe 100644
--- a/docs/local_dev_host.md
+++ b/docs/local_dev_host.md
@@ -6,7 +6,7 @@ This guide will walk through setting up Kolibri Studio for local development, wh
## Prerequisites
- [volta](https://docs.volta.sh/guide/getting-started)
-- [pyenv](https://kolibri-dev.readthedocs.io/en/develop/howtos/installing_pyenv.html) and [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv#installation)
+- [uv](https://docs.astral.sh/uv/) - Python package installer and virtual environment manager
## Install system dependencies and services
Studio requires some background services to be running:
@@ -75,29 +75,25 @@ exit # leave the postgres account
```
## Build your python virtual environment
-To determine what version of Python studio needs, you can check the `runtime.txt` file:
-```bash
-$ cat runtime.txt
-# This is the required version of Python to run Studio currently.
-# This is determined by the default Python 3 version that is installed
-# inside Ubuntu Bionic, which is used to build images for Studio.
-# We encode it here so that it can be picked up by Github's dependabot
-# to manage automated package upgrades.
-python-3.10.13
-```
-So to install python 3.10.13 through `pyenv` and set up a virtual environment:
+
+Studio uses [uv](https://docs.astral.sh/uv/) for Python dependency management and virtual environments. To set up your development environment:
+
```bash
-pyenv install 3.10.13
-pyenv virtualenv 3.10.13 studio-py3.10
-pyenv activate studio-py3.10
+# Create a virtual environment with uv (this will use the system Python or uv-managed Python)
+uv venv --seed
+
+# Activate the virtual environment
+source .venv/bin/activate
```
+
Now you may install Studio's Python dependencies:
```bash
-pip install -r requirements.txt -r requirements-dev.txt
+uv pip sync requirements.txt requirements-dev.txt
```
+
To deactivate the virtual environment, when you're finished developing on Studio for the time being:
```bash
-pyenv deactivate
+deactivate
```
### A note about `psycopg2`
diff --git a/docs/local_dev_wsl.md b/docs/local_dev_wsl.md
index d94f308553..f5405cb01a 100644
--- a/docs/local_dev_wsl.md
+++ b/docs/local_dev_wsl.md
@@ -53,7 +53,7 @@ git clone https://github.com/$USERNAME/studio.git
Replace `$USERNAME` with your GitHub username.
## Install Prerequisites
-For detailed instructions on installing and configuring Volta, pyenv, and pyenv-virtualenv, please see the [Prerequisites](./local_dev_host.md#prerequisites) section in our Local Development with host guide.
+For detailed instructions on installing and configuring Volta and uv, please see the [Prerequisites](./local_dev_host.md#prerequisites) section in our Local Development with host guide.
## Install System Dependencies and Services
@@ -171,7 +171,7 @@ Now that you have your project open in VS Code, you can run the same commands yo
2. **Activate the Python Virtual Environment**:
```sh
- pyenv activate studio-py3.10
+ source .venv/bin/activate
```
3. **Run the Services**:
diff --git a/docs/markdown_editor_viewer.md b/docs/markdown_editor_viewer.md
deleted file mode 100644
index 0f8c6d58dc..0000000000
--- a/docs/markdown_editor_viewer.md
+++ /dev/null
@@ -1,98 +0,0 @@
-
-# Markdown Editor/Viewer
-
-We use TOAST UI (TUI) Editor v2 as a basis for our markdown [editor](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue) and [viewer](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue).
-
-- [Documentation](https://ui.toast.com/tui-editor/)
-- [GitHub](https://github.com/nhn/tui.editor)
-- [API documentation](https://nhn.github.io/tui.editor/latest/)
-
-
-## WYSIWYG/Markdown
-
-TUI Editor provides both WYSIWYG and markdown mode. Currently, we use WYSIWYG mode only. Implementation-wise that means that although we need to define conversions both ways (we store markdown on our servers), there is currently no emphasis on making markdown editor work completely because it's not visible to users at all. Some additional steps might be needed (e.g. initialization of MathQuill fields).
-
-However, one of the reasons to choose this library was its potential to scale (maybe to allow users familiar with markdown syntax to use it in the future).
-
-**Development tip**: As mentioned, our custom logic might not work entirely in markdown mode. However, when developing conversions, it might be helpful to set `hideModeSwitch` option to `false` when initializing the editor so you can see whether your basic conversion logic is working both ways properly.
-
-
-## Viewer
-
-Besides the editor, TUI also provides the [viewer](https://github.com/nhn/tui.editor/blob/master/apps/editor/docs/viewer.md) that is a leaner version of the editor. Currently, we use it for assessment item's questions, answers, and hints preview.
-
-
-## Under the hood
-
-TUI editor uses the following 3rd party libraries and exposes their API for public use. We use some of them for our custom plugins:
-
-**WYSIWYG mode** is built around [Squire](https://github.com/neilj/Squire). Sometimes TUI's editor API is sufficient though there are some cases when we need to access [Squire's API](https://github.com/neilj/Squire#api) directly. That can be done via [`getSquire()`](https://nhn.github.io/tui.editor/latest/ToastUIEditor#getSquire) method:
-
-```javascript
- import Editor from '@toast-ui/editor';
-
- const editor = new Editor()
- const squire = editor.getSquire()
-```
-
-**Markdown mode** is built on top of [CodeMirror](https://codemirror.net/). We currently don't need to do that though it's API can be similarly accessed via [`getCodeMirror()`](https://nhn.github.io/tui.editor/latest/ToastUIEditor#getCodeMirror) if needed in the future.
-
-**Conversions from markdown to HTML** are processed by TUI editor's own markdown parser [ToastMark](https://github.com/nhn/tui.editor/tree/master/libs/toastmark). It can be extended by using [Custom HTML Renderer](https://github.com/nhn/tui.editor/blob/master/apps/editor/docs/custom-html-renderer.md).
-
-**Conversions from HTML to markdown** are processed by [to-mark](https://github.com/nhn/tui.editor/tree/master/libs/to-mark), another TUI editor's own library. Default conversion can be overwritten by defining a custom convertor and passing it into `customConvertor` parameter of [initialization`options`](https://nhn.github.io/tui.editor/latest/ToastUIEditor) . Unfortunately, it seems that there is no way to extend the default convertor using TUI Editor's public API at this point. That would be ideal for our use-case when we don't need to define the whole conversion logic but rather process some additional conversions (e.g. formulas). However, it can be extended using [this hackish solution](https://github.com/nhn/tui.editor/issues/615#issuecomment-527802641) which is how we currently extend conversions in this direction until there will be public API available.
-
-
-## Custom plugins
-
-Our custom plugins are located in [plugins directory](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins).
-
-### [Formulas editor plugin](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas)
-
-#### Adding new formulas
-
-1. A new formula's LaTeX representation is inserted as a new HTML element using `getSquire().insertHTML()`. It is also assigned a special class to denote that it is a new math field so that later we know which fields should be initialized as MathQuill static math fields.
-
-2. HTML is converted to markdown (conversions logic will be described later)
-
-3. All new math fields are initialized as MathQuill static math fields
-
-#### Editing existing formulas
-
-The steps are the same as when adding a new formula, except that instead of inserting a new formula element with `getSquire().insertHTML()`, an active element being edited is replaced by a new HTML using `parentNode.replaceChild()`.
-
-#### MathQuill
-
-We use [MathQuill's static math fields](http://docs.mathquill.com/en/latest/Getting_Started/#static-math-rendering) to render formulas in a list of all formulas in the formulas menu and in the editor. [Editable math fields](http://docs.mathquill.com/en/latest/Getting_Started/#editable-math-fields) are used in the edit part of the formulas menu.
-
-##### Customizations
-
-There is one customization in MathQuill code related to formulas logic: When initializing MathQuill fields (MathQuill replaces an element with its own HTML during that process), we add `data-formula` attribute to the root math element. Its value is the original formula's LaTeX representation. This attribute is used as a basis for conversion from HTML to markdown.
-
-**Important**
-All MathQuill customizations are saved in [this commit](https://github.com/learningequality/studio/commit/9c85577761a75d1c3c216496f4e3373e57623699). There's a need to be careful to reflect them if we upgrade MathQuill one day (or create MathQuill fork for the sake of clarity if there's a need to upgrade more often or add more customizations).
-
-#### [HTML to Markdown conversion](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-html-to-md.js)
-
-All elements in the input HTML containing `data-formula` attribute are replaced by a value saved in that attribute and wrapped in double `$`.
-
-#### [Markdown to HTML conversion](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js)
-
-All markdown substrings wrapped in double `$` are converted to `span` element and assigned `math-field` class. We use this class to know which elements should be initialized with MathQuill.
-
-This conversion is needed for rendering content in WYSIWYG after the first load (and eventually in the future if we allow users to switch to markdown mode on the fly) because we store markdown on our servers.
-
-
-### [Image upload plugin](../contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload)
-
-#### Adding/editing images
-
-New images can be added in two ways: An image can be dropped into the editor's
-content area, or it can be uploaded via the images menu.
-
-If a new image is dropped into the content area, then the images menu opens
-with the image and takes over the file upload process.
-
-After an image has been successfully uploaded using the images menu, a dummy
-image element is inserted into the editor's content area, and a Vue image
-field component is mounted on it. This component is responsible for resizing,
-editing, and removing an image.
diff --git a/docs/rich_text_editor.md b/docs/rich_text_editor.md
index a924925da9..c08bba23c6 100644
--- a/docs/rich_text_editor.md
+++ b/docs/rich_text_editor.md
@@ -1,277 +1,60 @@
-# Rich Text Editor Documentation
+## Rich Text Editor Documentation
-This Component replaces Studio’s text editor with a future-ready implementation, deliberately scoped to immediate needs:
-- Swap Toast UI while preserving Markdown storage
-- Support for core formatting
-- Support for more advanced formats (code and math blocks, image uploading)
+Studio has a Rich text Editor that is currently being used in the exercise editor: (questions / answers / hints)
-It uses TipTap which is a headless, framework-agnostic rich-text editor built on top of ProseMirror.
-[https://tiptap.dev/docs](https://tiptap.dev/docs)
+We use [TipTap](https://tiptap.dev/) that is a headless framework based on ProseMirror. It was built to replace a past long-lived Toast UI (TUI) based editor; that influenced some implementation decisions to maintain backward compatibility.
----
-
-Currently the editor is accessible through hidden routes:
-- `http://localhost:8080/en/channels//#/editor-dev` which requires a valid channel id first
-- `http://localhost:8080/#editor-dev`
-
-## Current Folder Structure
-
-```
-TipTapEditor/
-├── assets/
-│ └── # icons
-└── TipTapEditor/
-| ├── components/
-| │ ├── toolbar/
-| │ ├── ToolbarButton.vue
-| │ ├── FormatDropdown.vue
-| │ ├── PasteDropdown.vue
-| │ └── ToolbarDivider.vue
-| │ ├── EditorToolbar.vue
-| | └── EditorContentWrapper.vue
-| ├── composables/
-| │ ├── useDropdowns.js
-| │ ├── useEditor.js
-| │ └── useToolbarActions.js
-| ├── extensions/
-| │ ├── SmallTextExtension.js
-| └── TipTapEditor.vue # Main container
-└── TipTapEditorStrings.js
-```
-## Current Key Features
-
-- **Rich Text Formatting**: Bold, italic, underline, strikethrough
-- **Typography**: Multiple heading levels (H1, H2, H3), normal text, and small text
-- **Lists**: Bullet lists and numbered lists
-- **Advanced Clipboard**: Copy with formatting, paste with/without formatting
-- **History**: Undo/redo functionality
-- **Accessibility**: Full keyboard navigation and ARIA support
-- **RTL Support**: Right-to-left text direction support
-- **Internationalization**: Built-in string management system
-- **Custom Extensions**: Extensible architecture for additional features
-
-## Core Components
-
-### 1. TipTapEditor.vue
-
-**Main container component that orchestrates the entire editor**
-
-- **Purpose**: Acts as the root component that provides editor context to all child components
-- **Key Features**:
- - Uses composition API with `useEditor` composable
- - Provides editor instance and ready state to child components via Vue's provide/inject
- - Contains global typography styles for the editor content
-- **Dependencies**:
- - `EditorToolbar.vue`
- - `EditorContentWrapper.vue`
- - `useEditor` composable
-
-```vue
-// Example usage
-
-```
-
-### 2. EditorToolbar.vue
-
-**Main toolbar component containing all editing controls**
-
-- **Purpose**: Renders the complete toolbar with all formatting options
-- **Structure**: Organized into logical groups with dividers:
- - History actions (undo/redo)
- - Format dropdown
- - Text formatting (bold, italic, underline, strikethrough)
- - Copy/paste controls
- - List formatting
- - Script formatting (subscript, superscript)
- - Insert tools (image, link, math, code)
-- **Accessibility**: Full ARIA support with role groups and labels
-- **Dependencies**: Uses `useToolbarActions` composable for all action handlers
-
-### 3. EditorContentWrapper.vue
-
-**Wrapper for the actual editor content area**
-
-- **Purpose**: Provides the editable content area with proper styling
-- **Features**:
- - Proper padding and spacing
- - Typography styles for all content types
- - RTL text direction support
-- **Styling**: Deep selectors for ProseMirror content styling
-
-## Toolbar Components
-
-### 4. ToolbarButton.vue
-
-**Reusable button component for toolbar actions**
-
-- **Props**:
- - `title`: Button tooltip and accessibility label
- - `icon`: Path to button icon
- - `rtlIcon`: Optional RTL-specific icon
- - `isActive`: Boolean indicating if button is in active state
- - `isAvailable`: Boolean controlling button availability
- - `shouldFlipInRtl`: Boolean for RTL icon flipping
-- **Features**:
- - Automatic RTL icon switching
- - Disabled state handling
- - Keyboard navigation (Enter/Space)
- - Focus management with outline styles
- - Active state styling
-
-### 5. FormatDropdown.vue
-
-**Dropdown for text format selection (Normal, Small, H1, H2, H3)**
-
-- **Features**:
- - Dynamic format detection and display
- - Live preview of formats in dropdown
- - Full keyboard navigation (arrows, Enter, Escape, Home, End)
- - Focus management
- - ARIA menu implementation
-- **Format Options**:
- - Small text (12px)
- - Normal paragraph (16px)
- - Header 3 (18px)
- - Header 2 (24px)
- - Header 1 (32px)
-
-### 6. PasteDropdown.vue
-
-**Split button for paste operations**
-
-- **Structure**:
- - Main paste button (standard paste with formatting)
- - Dropdown arrow for paste options
-- **Options**:
- - Paste (with formatting)
- - Paste without formatting (plain text)
-- **Features**:
- - Clipboard API integration
- - HTML and plain text handling
- - Keyboard navigation
- - Split button interaction pattern
-
-### 7. ToolbarDivider.vue
+Currently editor code lives in: https://github.com/learningequality/studio/tree/unstable/contentcuration/contentcuration/frontend/shared/views/TipTapEditor
-**Visual separator between toolbar groups**
+Another point that had an impact on our architectural decisions is that there are future plans to extract the editor to be part of Kolibri-Design-System to be used in Kolibri too. That meant we had to keep the editor as decoupled from the rest of the codebase as much as possible.
-- Simple component providing consistent spacing and visual separation
-- Helps organize toolbar into logical sections
-
-## Composables (Business Logic)
-
-### 8. useEditor.js
-
-**Core editor initialization and lifecycle management**
-
-- **Purpose**: Creates and manages the TipTap editor instance
-- **Extensions Used**:
- - StarterKit (basic functionality)
- - Underline extension
- - Custom Small text extension
-- **Lifecycle**: Handles editor creation on mount and cleanup on unmount
-- **Returns**: Editor instance and ready state
-
-```javascript
-const { editor, isReady } = useEditor()
-```
-
-### 9. useToolbarActions.js
-
-**All toolbar action handlers and state management**
-
-- **Individual Handlers**: Each formatting action (bold, italic, etc.)
-- **Action Groups**: Organized arrays of related actions
-- **Features**:
- - Copy with HTML and plain text formats
- - Intelligent paste handling
- - Active state detection for buttons
- - Button availability checking (undo/redo)
- - Format application logic
-- **Internationalization**: Uses translator function for button labels
-
-### 10. useDropdowns.js
-
-**Dropdown state management and interaction logic**
-
-- **State Management**:
- - Current format detection
- - Dropdown open/close states
- - Format options configuration
-- **Format Detection**: Real-time monitoring of cursor position to update selected format
-- **Event Handling**: Click outside detection for dropdown closing
-- **Editor Integration**: Listens to editor transactions for format updates
-
-## Extensions
-
-### 11. SmallTextExtension.js
-
-**Custom TipTap extension for small text formatting**
-
-- **Type**: Block node extension
-- **Features**:
- - Creates `` HTML elements
- - Block-level content with inline children
- - Custom CSS class (`small-text`)
- - Keyboard shortcut (Mod+Shift+S)
-- **Commands**:
- - `setSmall()`: Convert current block to small text
- - `toggleSmall()`: Toggle between small text and paragraph
- - `unsetSmall()`: Convert back to paragraph
-- **Priority**: High priority (1000) for proper node precedence
-
->[!NOTE]
-> Why a Node instead of a Mark?
-> - Semantic structure: ` `is semantically a block-level structure in our context, representing a complete unit of small text rather than just formatted text spans.
-> - Content integrity: As a Node, we can ensure the entire content maintains consistent styling and behavior.
-> - Block-level control: Using a Node allows us to treat small text as a distinct content block that can be manipulated as a whole unit in the editor.
-> - DOM structure: We want a proper `` element in the output HTML rather than just applying a class or style to spans of text.
-
-
-### 12. TipTapEditorStrings.js
-
-**Centralized string management for internationalization**
-
-- **Structure**:
- - Namespace-based organization
- - Message key-value pairs
- - Translator factory function
-- **Coverage**: All user-facing strings including:
- - Button labels and tooltips
- - Format options
- - Accessibility labels
- - Dropdown options
-- **Usage**: Provides `getTranslator()` function for components
-
->[!NOTE]
->Uses Lazy Loading pattern to be able to use it without the need for it to be inside a vue lie cycle hook.
-## Styling Architecture
-
-asheres to [the suggested figma design](https://www.figma.com/design/uw8lx88ZKZU8X7kN9SdLeo/Rich-text-editor---GSOC-2025?node-id=377-633&t=0XAXleYjjGY2Fxzc-0)
-### Component Styles
-
-- Scoped styles for component isolation
-- Deep selectors for ProseMirror content
-- CSS custom properties for theming
-- Focus management with outline styles
+## Useful Links
+- Original figma design [link](https://www.figma.com/design/uw8lx88ZKZU8X7kN9SdLeo/Rich-text-editor---GSOC-2025?node-id=377-422&p=f&t=HIkJ8pF9xudcOnLd-0)
+- Original Tracking issue for creating the editor [link](https://github.com/learningequality/studio/issues/5049)
+- Tiptap basic editor [docs](https://tiptap.dev/docs/editor/getting-started/overview)
+---
+## Custom extensions
+For non-text elements, we create [custom extensions optionally with their node views](https://tiptap.dev/docs/editor/extensions/custom-extensions/node-views/vue).
+We currently have custom extensions for:
+- images
+- formulas
+- syntax highlighted code blocks
+- links
+- `` text nodes
+
+### How to add a custom plugin?
+This is a very high level guide, you'll still need to check the docs but make sure you check all the boxes in this list:
+1. Create a new file in
+ `TipTapEditor/extensions/`
+2. Define your node or mark using TipTap’s `Node.create()` or `Mark.create()`.
+3. Add the new extension to the editor’s extension list in `TipTapEditor/composables/useEditor.js`.
+4. If your node needs Markdown support, update the custom serializer in `TipTapEditor/utils/MarkdownSerializer.js` and don't forget to update the tests accordingly!
+---
+## Content Conversion Flow
+The old content API saved markdown in the database, the following data conversion flow maintains backward compatibility by implementing dual conversion between the strcutured JSON format TipTap uses and markdown.
-### Accessibility Features
+We support the conversion for:
+- Standard Markdown elements previously handled by the ToastUI editor and its Showdown converter.
+- A specific, legacy format for custom nodes, particularly for Images `()` and Math Formulas `$$latex$$`
-- High contrast focus indicators
-- ARIA roles and properties
-- Keyboard navigation support
-- Screen reader friendly labels
+The formats for the custom nodes are adapted from the old editor's standard syntax conversion.
+We have our own custom markdown serializer for that too! The following graph illustrates the whole flow.
+
-## Technical Specifications
+---
+## Mobile View
+As per the figma design, the mobile view is different from the desktop design to a point where it can't just be fixed with just CSS tweaks or media queries. We did some thinking&research and decided to take a Conditional Toolbar Layout approach where We've created different components for different screen sizes.
-### Dependencies
+That means, if you add a new button in the desktop's toolbar, you'll have to add it to the Mobile's toolbar component too, and make sure you keep the functionality extracted in a reusable way so you only repeat the template logic and not the whole javascript!
-- **TipTap**: Core editor framework
-- **TipTap Extensions**: StarterKit, Underline
-- **Clipboard API**: Modern clipboard operations
+As per the Figma design, the **mobile view** differs significantly from the desktop layout — more than what simple CSS tweaks or media queries can handle.
-### Browser Support
+We decided to take a **Conditional Toolbar Layout** approach:
+- Different toolbar components are used for desktop and mobile.
+- The logic (commands, editor state, etc.) is shared and reusable.
+- Only the **template structure** differs.
-- Modern browsers with Clipboard API support
-- RTL text direction support
-- Keyboard navigation compatibility
+>[!TIP]
+>That means:
+> If you add a new button to the desktop toolbar, you’ll also need to add it to the mobile toolbar component.
+> Keep the functionality extracted and reusable, so you only duplicate the **template**, not the **JavaScript logic**.
diff --git a/docs/stripe_integration.md b/docs/stripe_integration.md
new file mode 100644
index 0000000000..4f4b5f1544
--- /dev/null
+++ b/docs/stripe_integration.md
@@ -0,0 +1,151 @@
+# Stripe Subscription Integration
+
+Studio uses [Stripe](https://stripe.com/) to offer paid storage upgrades via subscriptions. This document covers the architecture, configuration, and testing workflow.
+
+## Architecture Overview
+
+The integration uses Stripe's recommended approach for subscriptions:
+
+- **Checkout Sessions** (`mode: "subscription"`) handle the initial payment flow
+- **Per-unit pricing** at $15/GB/year — the user selects how many GB they want, which becomes the `quantity` on the Stripe line item
+- **Customer Portal** lets users manage their subscription (cancel, update payment method, change quantity)
+- **Webhooks** keep Studio in sync with Stripe's subscription lifecycle events
+- **Dynamic payment methods** are configured in the Stripe Dashboard (not hardcoded)
+
+### Data Model
+
+`UserSubscription` is a separate model (one-to-one with `User`) that tracks:
+
+- `stripe_customer_id` — Stripe's customer reference
+- `stripe_subscription_id` — Stripe's subscription reference
+- `stripe_subscription_status` — current status (`active`, `trialing`, `canceled`, etc.)
+- `subscription_disk_space` — additional bytes granted by the subscription
+
+The user's effective storage quota is `disk_space + subscription_disk_space` (computed by `User.get_effective_disk_space()`), so admin-granted quotas are preserved. Storage is derived from the Stripe subscription quantity: `quantity_gb * 1 GB`.
+
+### API Endpoints
+
+| Endpoint | Method | Auth | Purpose |
+|---|---|---|---|
+| `/api/stripe/create-checkout-session/` | POST | Login required | Creates a Checkout Session, returns redirect URL |
+| `/api/stripe/create-portal-session/` | POST | Login required | Creates a Customer Portal session |
+| `/api/stripe/subscription-status/` | GET | Login required | Returns current user's subscription status |
+| `/api/stripe/webhook/` | POST | CSRF exempt | Receives Stripe webhook events |
+
+### Webhook Events Handled
+
+| Event | Behavior |
+|---|---|
+| `checkout.session.completed` | Activates subscription, grants storage |
+| `customer.subscription.updated` | Syncs status; revokes storage if no longer `active`/`trialing` |
+| `customer.subscription.deleted` | Marks as canceled, revokes storage (fires after period ends) |
+
+## Configuration
+
+### Environment Variables
+
+The integration uses **separate environment variables for test and live keys**, selected automatically based on `BRANCH_ENVIRONMENT`:
+
+- **Production** (`BRANCH_ENVIRONMENT=master`): reads `STRIPE_LIVE_*` variables
+- **All other environments** (QA, staging, dev): reads `STRIPE_TEST_*` variables
+
+This means it is not possible for a non-production server to accidentally process real payments, even if it shares the same settings file.
+
+| Variable | Description |
+|---|---|
+| `STRIPE_TEST_SECRET_KEY` | Stripe sandbox secret key (`sk_test_...`) — shared across all non-production deployments |
+| `STRIPE_TEST_WEBHOOK_SECRET` | Webhook signing secret for this deployment's test endpoint — **unique per deployment** |
+| `STRIPE_TEST_PRICE_ID` | Price ID for the sandbox subscription product — shared across all non-production deployments |
+| `STRIPE_LIVE_SECRET_KEY` | Stripe live mode secret key (`sk_live_...`) |
+| `STRIPE_LIVE_WEBHOOK_SECRET` | Webhook signing secret for the production endpoint |
+| `STRIPE_LIVE_PRICE_ID` | Price ID for the live subscription product |
+
+**Note on webhook secrets:** The secret key and price ID can be shared across QA/staging deployments since they belong to the same Stripe sandbox. However, each deployment needs its **own webhook endpoint** in Stripe (since each has a different URL), and therefore its own `STRIPE_TEST_WEBHOOK_SECRET`.
+
+### Stripe Dashboard Setup
+
+#### Sandbox (test) setup
+
+1. Create a [Stripe sandbox](https://dashboard.stripe.com/test/developers) for non-production testing
+2. In the sandbox, create a Product with a **Volume tiered recurring Price** at $15/unit/year with a single tier (each unit = 1 GB). Note: per-unit pricing at $15 hits Stripe's minimum price-per-unit threshold, so use Volume tiered pricing with one tier as a workaround.
+3. Enable desired payment methods in Dashboard > Settings > Payment methods
+4. Configure the Customer Portal in Dashboard > Settings > Customer portal (enable "Update subscriptions" for quantity changes and "Cancel subscriptions")
+5. Create a **separate webhook endpoint for each deployment** (Developers > Event destinations):
+ - `https:///api/stripe/webhook/`
+ - `https:///api/stripe/webhook/`
+ - Subscribe each to: `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted`
+ - Each endpoint gets its own signing secret (`whsec_...`)
+
+#### Production setup
+
+1. Create a Product and Price in live mode with the same structure as the sandbox
+2. Create a single webhook endpoint for the production domain
+3. Store the live keys via the secret management system (accessed via `get_secret()` in `production_settings.py`)
+
+## Testing
+
+### QA / Staging Servers
+
+QA and staging servers automatically use Stripe **sandbox** keys (via `STRIPE_TEST_*` env vars). Sandbox mode is fully functional but never charges real money.
+
+Set `STRIPE_TEST_SECRET_KEY` and `STRIPE_TEST_PRICE_ID` from the sandbox (shared across deployments), and set `STRIPE_TEST_WEBHOOK_SECRET` from the webhook endpoint created for that specific deployment.
+
+### Test Card Numbers
+
+Stripe provides [test card numbers](https://docs.stripe.com/testing#cards) for simulating different scenarios:
+
+| Card Number | Scenario |
+|---|---|
+| `4242 4242 4242 4242` | Payment succeeds |
+| `4000 0000 0000 0341` | Payment is declined |
+| `4000 0000 0000 3220` | Requires 3D Secure authentication |
+| `4000 0000 0000 3063` | Requires 3D Secure, then is declined |
+
+Use any future expiry date, any 3-digit CVC, and any postal code.
+
+### Local Development
+
+For local development, use the [Stripe CLI](https://docs.stripe.com/stripe-cli) instead of creating a webhook endpoint in the dashboard. The CLI forwards sandbox events directly to your local server without needing a public URL.
+
+#### Setup
+
+1. Install the Stripe CLI: `brew install stripe/stripe-cli/stripe` (macOS) or see [install docs](https://docs.stripe.com/stripe-cli#install)
+2. Set `STRIPE_TEST_SECRET_KEY` and `STRIPE_TEST_PRICE_ID` in your environment
+3. Run the combined dev server:
+
+```bash
+make devserver-stripe
+```
+
+This authenticates the Stripe CLI using your `STRIPE_TEST_SECRET_KEY`, starts the webhook listener, automatically extracts the signing secret, and launches `pnpm devserver` with everything configured. No separate `stripe login` or manual webhook secret needed.
+
+#### Manual setup
+
+If you prefer to run things separately:
+
+```bash
+# Terminal 1: start the Stripe listener
+stripe listen --forward-to localhost:8080/api/stripe/webhook/
+# Copy the whsec_... secret it prints
+
+# Terminal 2: start the dev server with the secret
+STRIPE_TEST_WEBHOOK_SECRET=whsec_... pnpm devserver
+```
+
+#### Testing the full flow
+
+With the listener running, open Studio in your browser and go through the upgrade flow. The CLI will forward the resulting webhook events to your local server in real time.
+
+You can also trigger events manually in a separate terminal to test specific scenarios:
+
+```bash
+stripe trigger checkout.session.completed
+stripe trigger customer.subscription.updated
+stripe trigger customer.subscription.deleted
+```
+
+### Unit Tests
+
+Backend tests are in:
+- `contentcuration/tests/test_user.py` (`UserEffectiveDiskSpaceTest`) — disk space calculation tests
+- `contentcuration/tests/views/test_subscription.py` — view/webhook tests
diff --git a/integration_testing/features/manage-resources/bulk-editing/bul-edit-multiple-resources.feature b/integration_testing/features/manage-resources/bulk-editing/bulk-edit-multiple-resources.feature
similarity index 100%
rename from integration_testing/features/manage-resources/bulk-editing/bul-edit-multiple-resources.feature
rename to integration_testing/features/manage-resources/bulk-editing/bulk-edit-multiple-resources.feature
diff --git a/integration_testing/features/manage-resources/create-exercise.feature b/integration_testing/features/manage-resources/create-exercise.feature
index 739839a59d..2122861d17 100755
--- a/integration_testing/features/manage-resources/create-exercise.feature
+++ b/integration_testing/features/manage-resources/create-exercise.feature
@@ -4,78 +4,121 @@ Feature: Create an exercise
Given I am signed in to Studio
And I am at the channel editor page
- Scenario: Create an exercise with questions of type *Single choice*
+ Scenario: Create a new exercise (Completion: When goal is met - Goal: 100% correct)
When I click the *Add* button
And I select the *New exercise* option
Then I see the *Details* tab of the *New exercise* modal
- When I fill in the required fields (*Title*, *Completion* and *Goal*)
+ When I fill in all of the required fields
+ And I set the completion criteria to *When goal is met - Goal: 100% correct)* #repeat with the rest of the available goal types
And I click the *Questions* tab
- Then I see the *New question* button
+ Then I see the text: *Exercise has no questions*
+ And I see a *New question* button
When I click the *New question* button
Then I see the question editor
- And I see that *Single choice* is the default *Response type*
- When I fill in the question text in the question editor field
- And I add an image
- And I provide answers
- And I select one answer as the correct answer
- And I provide a hint
- Then I've completed the creation of the question of type *Single choice* #repeat the same steps to add as many questions as necessary
- When I click the *Finish* button
- Then I am returned to the main topic tree view
- And I can see the newly created exercise
+ When I add one or several questions of the desired type
+ And I add answers and hints as necessary
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
- Scenario: Create an exercise with questions of type *Multiple choice*
+ Scenario: Create a new exercise - practice quiz
When I click the *Add* button
And I select the *New exercise* option
Then I see the *Details* tab of the *New exercise* modal
- When I fill in the required fields (*Title*, *Completion* and *Goal*)
+ When I fill in all of the required fields
+ And I set the completion criteria to *Practice quiz*
And I click the *Questions* tab
- Then I see the *New question* button
+ Then I see the text: *Exercise has no questions*
+ And I see a *New question* button
When I click the *New question* button
Then I see the question editor
- When I select the *Multiple choice* option from the *Response type* drop-down
- And I fill in the question text in the question editor field
- And I add an image
- And I provide answers
- And I select at least one answer as the correct answer
- And I provide a hint
- Then I've completed the creation of the question of type *Multiple choice* #repeat the same steps to add as many questions as necessary
- When I click the *Finish* button
- Then I am returned to the main topic tree view
- And I can see the newly created exercise
+ When I add one or several questions of the desired type
+ And I add answers and hints as necessary
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
- Scenario: Create an exercise with questions of type *Numeric input*
+ Scenario: Create a new exercise - survey
When I click the *Add* button
And I select the *New exercise* option
Then I see the *Details* tab of the *New exercise* modal
- When I fill in the required fields (*Title*, *Completion* and *Goal*)
+ When I fill in all of the required fields
+ And I set the completion criteria to *Survey*
And I click the *Questions* tab
- Then I see the *New question* button
+ Then I see the text: *Exercise has no questions*
+ And I see a *New question* button
When I click the *New question* button
Then I see the question editor
+ When I add one or several questions of the desired type
+ And I add answers and hints as necessary
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Existing and newly created exercises behave consistently
+ When I open a published channel containing previously created exercises with various completion types and questions
+ Then I can see all exercises displayed in the channel editor page
+ And exercises which were previously marked as *Incomplete* are still marked as *Incomplete*
+ And exercises without an *Incomplete* indicator are still displayed without it
+ And existing and newly created exercises look and function the same
+
+ Scenario: Create an exercise with questions of type *Single choice*
+ Given I am at the *Questions* tab
+ When I click the *New question* button
+ Then I see the question editor
+ And I see that *Single choice* is the default *Response type*
+ When I fill in the question text in the question editor field using all of the available editor options such as adding and resizing an image, editing the text or inserting formulas
+ And I provide answers
+ And I select one answer as the correct answer
+ And I provide a hint
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Create an exercise with questions of type *Multiple choice*
+ Given I am at the question editor
+ When I select the *Multiple choice* option from the *Response type* drop-down
+ And I fill in the question text in the question editor field using all of the available editor options such as adding and resizing an image, editing the text or inserting formulas
+ And I provide answers
+ And I select one answer as the correct answer
+ And I provide a hint
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Create an exercise with questions of type *Numeric input*
+ Given I am at the question editor
When I select the *Numeric input* option from the *Response type* drop-down
- And I fill in the question text in the question editor field
+ And I fill in the question text in the question editor field using all of the available editor options such as adding and resizing an image, editing the text or inserting formulas
And I provide answers
+ And I select one answer as the correct answer
And I provide a hint
- Then I've completed the creation of the question of type *Numeric input* #repeat the same steps to add as many questions as necessary
- When I click the *Finish* button
- Then I am returned to the main topic tree view
- And I can see the newly created exercise
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
Scenario: Create an exercise with questions of type *True/False*
- When I click the *Add* button
- And I select the *New exercise* option
- Then I see the *Details* tab of the *New exercise* modal
- When I fill in the required fields (*Title*, *Completion* and *Goal*)
- And I click the *Questions* tab
- Then I see the *New question* button
- When I click the *New question* button
- Then I see the question editor
+ Given I am at the question editor
When I select the *True/False* option from the *Response type* drop-down
- And I fill in the question text in the question editor field
- And I select either *True* or *False* as the correct answer
+ And I fill in the question text in the question editor field using all of the available editor options such as adding and resizing an image, editing the text or inserting formulas
+ And I select one answer as the correct answer
And I provide a hint
- Then I've completed the creation of the question of type *True/False* #repeat the same steps to add as many questions as necessary
- When I click the *Finish* button
- Then I am returned to the main topic tree view
- And I can see the newly created exercise
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Create an exercise with questions of type *Free response*
+ Given I am at the question editor for an exercise of type Survey
+ When I select the *Free response* option from the *Response type* drop-down
+ And I fill in the question text in the question editor field using all of the available editor options such as adding and resizing an image, editing the text or inserting formulas
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
diff --git a/integration_testing/features/manage-resources/restore-resources-from-trash.feature b/integration_testing/features/manage-resources/restore-resources-from-trash.feature
index ce3b55061a..05b4dbbd33 100755
--- a/integration_testing/features/manage-resources/restore-resources-from-trash.feature
+++ b/integration_testing/features/manage-resources/restore-resources-from-trash.feature
@@ -2,7 +2,7 @@ Feature: Restore resources from trash
Background:
Given I am signed in to Studio
- And I am at the channel editing page
+ And I am at the channel editor page
And I have already removed some resources
And I have opened the trash page
diff --git a/integration_testing/features/manage-channels/sync-channel.feature b/integration_testing/features/manage-resources/sync-resources.feature
similarity index 78%
rename from integration_testing/features/manage-channels/sync-channel.feature
rename to integration_testing/features/manage-resources/sync-resources.feature
index 3023eba6fc..3874a234dc 100755
--- a/integration_testing/features/manage-channels/sync-channel.feature
+++ b/integration_testing/features/manage-resources/sync-resources.feature
@@ -1,5 +1,5 @@
Feature: Sync resources
- Studio users need to be able to sync and update the resources in their channels that have been imported from other channels, but have been modified since the original import.
+ Studio users need to be able to sync and update the resources in their channels which have been imported from other channels, but have been modified since the original import.
Background:
Given I am signed in to Studio
@@ -10,12 +10,14 @@ Feature: Sync resources
Given there is a new version of the resource file in
When I click the *···* button in the top-right corner
And I select the *Sync resources* option
- Then I see *Sync resources* modal window
- And I see options to sync files, resource details, titles and description, assessment details
+ Then I see the *Sync resources* modal window
+ And I see the *Select what you would like to sync* section with options to sync files, resource details, titles and description, assessment details
When I select the Files checkbox
And I click the *Continue* button
Then I see the *Confirm sync* modal
- When I click *Sync*
+ And I see the following info: You are about to sync and update the following: Files
+ And I see a warning that this will overwrite any changes I've made to copied or imported resources
+ When I click the *Sync* button
Then the modal closes
When after a period of time I refresh the page
And inspect the updated resource(s)
@@ -24,7 +26,7 @@ Feature: Sync resources
Scenario: Sync resource details
Given there is a new version of the resource file in
And I am at the *Sync resources* modal window
- When I select the *Tags* checkbox
+ When I select the *Resource details* checkbox
And click the *Continue* button
Then I see the *Confirm sync* modal
When I click *Sync*
@@ -60,11 +62,11 @@ Feature: Sync resources
Scenario: Edited resource metadata is reverted after syncing
Given I have edited some of the resource's metadata (title, description or tags) after importing from
And I am at the *Sync resources* modal window
- When I select the *Resource details* or *Titles and descriptions* checkbox
+ When I select the *Resource details* and *Titles and descriptions* checkboxes
And click the *Continue* button
Then I see the *Confirm sync* modal
When I click *Sync*
Then the modal closes
When after a period of time I refresh the page
And inspect the updated resource
- Then I see that my previous edits of the title, description or tags for the resource have been reverted to reflect those on the
+ Then I see that my previous edits of the title, description or tags for the resource have been reverted to reflect those in
diff --git a/integration_testing/features/manage-resources/upload-files.feature b/integration_testing/features/manage-resources/upload-files.feature
index af25b4059a..e487001dcb 100755
--- a/integration_testing/features/manage-resources/upload-files.feature
+++ b/integration_testing/features/manage-resources/upload-files.feature
@@ -15,7 +15,7 @@ Feature: Upload files
Then I see a file explorer window
When I select a supported file for upload
And I click the *Open* button
- Then I see the *Edit files* modal
+ Then I see the *Edit files* modal #if I am uploading resources to a folder with metadata then I will first see the *Apply details from the folder *
When I fill in all the required fields
And I click the *Finish* button
Then I am returned to the main topic tree view
@@ -27,7 +27,7 @@ Feature: Upload files
Then I see a file explorer window
When I select several supported files for upload
And I click the *Open* button
- Then I see the *Edit files* modal
+ Then I see the *Edit files* modal #if I am uploading resources to a folder with metadata then I will first see the *Apply details from the folder *
When I fill in all the required fields for each file
And I click the *Finish* button
Then I am returned to the main topic tree view
@@ -39,7 +39,7 @@ Feature: Upload files
Then I see a file explorer window
When I select several supported files for upload
And I click the *Open* button
- Then I see the *Edit files* modal
+ Then I see the *Edit files* modal #if I am uploading resources to a folder with metadata then I will first see the *Apply details from the folder *
When I fill in all the required fields for each file
And I click the *Finish* button
Then I am returned to the main topic tree view
@@ -48,7 +48,7 @@ Feature: Upload files
Scenario: Upload more files by drag and drop
Given I am at the *Edit files* modal after having imported some files
When I drag and drop files
- Then I see the *Edit files* modal
+ Then I see the *Edit files* modal with the newly uploaded files #if I am uploading resources to a folder with metadata then I will first see the *Apply details from the folder *
When I fill in all the required fields for each file
And I click the *Finish* button
Then I am returned to the main topic tree view
@@ -63,4 +63,4 @@ Feature: Upload files
Then I am back at the *Upload files* modal
When I close the modal
Then I am returned to the main topic tree view
- And I can see the uploaded files are not present
+ And I can see the uploaded files are not present
diff --git a/integration_testing/features/studio-critical-workflows.feature b/integration_testing/features/studio-critical-workflows.feature
index f321872b7f..dd00bf8c7d 100644
--- a/integration_testing/features/studio-critical-workflows.feature
+++ b/integration_testing/features/studio-critical-workflows.feature
@@ -4,6 +4,7 @@ Feature: Kolibri Studio critical workflows
Background:
Given Kolibri Studio is accessible at https://studio.learningequality.org/ or any of the test environments
And I am at Kolibri Studio's sign-in page
+ And I already have several testing accounts with different channels and resources
Scenario: Create an account and sign in with the created account
When I click the *Create an account* button
@@ -37,7 +38,7 @@ Feature: Kolibri Studio critical workflows
Given I am not signed in to Studio
And I am at Kolibri Studio's sign-in page
When I click the *Explore without an account* link
- Then I see the *Content Library* page with the available public channels
+ Then I see the *Content library* page with the available public channels
And I can filter the search results by keyword, language, license, format, resources for coach, available captions and subtitles
And I can view or download the channel summary
@@ -58,14 +59,16 @@ Feature: Kolibri Studio critical workflows
Then the interface language changes to the selected language
Scenario: Open and close the sidebar menu
+ Given I am signed-in to Kolibri Studio
When I click the hamburger menu button in the upper left screen corner
Then I see the sidebar menu
- And I can see the following options: *Channels*, *Settings*, *Change language*, *Help and support*, *Sign out*, the LE logo, *© 2025 Learning Equality", *Give feedback*
+ And I can see the following options: *Channels*, *Settings*, *Change language*, *Help and support*, *Sign out*, the LE logo, *© 2026 Learning Equality*, *Give feedback*
And I can click any of the options inside
When I click the *X* button, or anywhere on the browser screen
Then I don't see the sidebar menu anymore
Scenario: Open and close the user menu
+ Given I am signed-in to Kolibri Studio
When I click the user menu button in the upper right screen corner
Then I see the user menu
And I can see the following options: *Settings*, *Change language*, *Help and support*, *Sign out*
@@ -75,7 +78,7 @@ Feature: Kolibri Studio critical workflows
Scenario: Create a new channel
Given I am signed in to Studio
- And I am at *My Channels* tab
+ And I am at *My channels* tab
When I click the *New channel* button
Then I see the *New channel* page
When I upload an image file as a channel thumbnail (optional)
@@ -84,7 +87,7 @@ Feature: Kolibri Studio critical workflows
And I enter channel description (optional)
And I fill in the default copyright fields (optional)
And I click the *Create* button
- Then I am at the channel editor view
+ Then I am at the channel editor page
And I see the title of the channel to the left
And I see a disabled *Publish* button
And I see *Click "ADD" to start building your channel Create, upload, or import resources from other channels*
@@ -92,7 +95,7 @@ Feature: Kolibri Studio critical workflows
Scenario: Edit channel details
Given I am signed in to Studio
- And I am at the channel editor view
+ And I am at the channel editor page
When I click the pencil icon next to the channel name
Then I see a modal window with the channel details
And I see the details for the channel - channel name, language, channel description etc.
@@ -132,13 +135,34 @@ Feature: Kolibri Studio critical workflows
Then I am back at the channel editor page
And I can see the uploaded files
- Scenario: Create a new exercise
+ Scenario: Create a new exercise (Completion: When goal is met - Goal: 100% correct)
+ Given I am signed in to Studio
+ And I am at the channel editor page
+ When I click the *Add* button
+ And I select the *New exercise* option
+ Then I see the *Details* tab of the *New exercise* modal
+ When I fill in all of the required fields
+ And I set the completion criteria to *When goal is met - Goal: 100% correct*
+ And I click the *Questions* tab
+ Then I see the text: *Exercise has no questions*
+ And I see a *New question* button
+ When I click the *New question* button
+ Then I see the question editor
+ When I add one or several questions of the desired type
+ And I add answers and hints as necessary
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Create a new exercise - practice quiz
Given I am signed in to Studio
And I am at the channel editor page
When I click the *Add* button
And I select the *New exercise* option
Then I see the *Details* tab of the *New exercise* modal
When I fill in all of the required fields
+ And I set the completion criteria to *Practice quiz*
And I click the *Questions* tab
Then I see the text: *Exercise has no questions*
And I see a *New question* button
@@ -149,6 +173,35 @@ Feature: Kolibri Studio critical workflows
And I click the *Finish* button
Then I am back at the channel editor page
And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Create a new exercise - survey
+ Given I am signed in to Studio
+ And I am at the channel editor page
+ When I click the *Add* button
+ And I select the *New exercise* option
+ Then I see the *Details* tab of the *New exercise* modal
+ When I fill in all of the required fields
+ And I set the completion criteria to *Survey*
+ And I click the *Questions* tab
+ Then I see the text: *Exercise has no questions*
+ And I see a *New question* button
+ When I click the *New question* button
+ Then I see the question editor
+ When I add one or several questions of the desired type
+ And I add answers and hints as necessary
+ And I click the *Finish* button
+ Then I am back at the channel editor page
+ And I can see the newly added exercise
+ And I see a small green dot indicating that the exercise is unpublished
+
+ Scenario: Existing and newly created exercises behave consistently
+ Given I am signed in to Studio
+ When I open a published channel containing previously created exercises with various completion types and questions
+ Then I can see all exercises displayed in the channel editor page
+ And exercises which were previously marked as *Incomplete* are still marked as *Incomplete*
+ And exercises without an *Incomplete* indicator are still displayed without it
+ And existing and newly created exercises look and function the same
Scenario: Import content from another channel
Given I am signed in to Studio
@@ -167,6 +220,8 @@ Feature: Kolibri Studio critical workflows
And I click the *Import* button
Then I am back at the channel editor page
And I see the *Copying* indicator for each folder or resource which is being copied
+ When after a period of time I refresh the page
+ Then I can see the imported folders and resources in the channel editor page
Scenario: Publish a channel
Given I am signed in to Studio
@@ -179,10 +234,28 @@ Feature: Kolibri Studio critical workflows
When I fill in the required fields
And I click *Publish*
Then I see the *Publishing channel* progress indicator at the top right
- When the the channel has been published successfully
+ When the channel has been published successfully
Then I see the *Published N seconds ago* text
And I receive a confirmation email that the channel has been published successfully
+ Scenario: Publish a channel with incomplete resources
+ Given I am signed in to Studio
+ And I am at the channel editor page
+ And I have write access to the channel
+ And the channel contains unpublished resources or edits plus one or several incomplete resources
+ When I click the *Publish* button at the top right corner
+ Then the *Publish modal* appears
+ And I see a warning that incomplete resources will not be published and made available for download in Kolibri.
+ When I click *Continue*
+ Then I see the *Describe what's new in this channel version* field and the *Language* drop-down
+ When I fill in the required fields
+ And I click *Publish*
+ Then I see the *Publishing channel* progress indicator at the top right
+ When the channel has been published successfully
+ Then I see the *Published N seconds ago* text
+ And I see a yellow exclamation icon with the following text *N resources are incomplete and cannot be published*
+ And I receive a confirmation email that the channel has been published successfully
+
Scenario: Invite collaborators with *Can edit* permissions
Given I am signed in to Studio
And I am at the channel editor page
@@ -192,14 +265,39 @@ Feature: Kolibri Studio critical workflows
When I type the email of the person I want to invite
And I don't change the preselected *Can edit* option in the drop-down
And I click the *Send invitation* button
- Then the collaborator will be notified on their *My Channels* page, where they can accept or reject the pending invitation
- And the collaborator will receive an email allowing them to accept/reject the pending invitation
+ Then the collaborator will be notified on their *My channels* page, where they can accept or decline the pending invitation
+ And the collaborator will receive an email allowing them to accept/decline the pending invitation
+ When I sign in to Studio as the collaborator
+ Then I can accept the pending invitation
+ When I make some changes to the channel or resources
+ And I click the *Publish* button
+ Then I can publish the channel
+ And any changes made to the channel are visible by the other collaborators
+
+ Scenario: Invite collaborators with *Can view* permissions
+ Given I am signed in to Studio
+ And I am at the channel editor page
+ When I click the *...* (options) button in the topbar
+ And select the *Share channel* option
+ Then I am at the *Sharing* tab for the channel
+ When I type the email of the person I want to invite
+ And I select the *Can view* option from the drop-down
+ And I click the *Send invitation* button
+ Then the collaborator will be notified on their *View-only* page, where they can accept or decline the pending invitation
+ And the collaborator will receive an email allowing them to accept/decline the pending invitation
+ When I sign in to Studio as the collaborator
+ And I accept the pending invitation
+ And I open the channel editor page
+ Then I can see the channel marked as *View-only*
+ And I can see the available folders and resources
+ And I can't add or edit folders and resources
+ And I can't publish the channel
Scenario: Sync resources
Given I am signed in to Studio
And I am at the channel editor page for
And there is a resource in the that has been imported from
- And there is new version of the resource file in the
+ And there is a new version of the resource in
When I click the *···* button in the top-right corner
And I select the *Sync resources* option
Then I see *Sync resources* modal window
@@ -250,6 +348,17 @@ Feature: Kolibri Studio critical workflows
And I see the *Undo* button
And I no longer see the resource
+ Scenario: Removed folder and resources can be restored
+ Given I am signed in to Studio
+ And I am at the channel editor page
+ And there are available folders and resources of different types
+ When I check the checkbox of a folder or a resource
+ And I click the *Remove* button
+ Then I can see the *Sent to trash* snackbar message
+ And I see the *Undo* button
+ When I click the *Undo* button
+ Then I can still see the folder or resource in the channel editor page
+
Scenario: Copy multiple resources
Given I am signed in to Studio
And I am at the channel editor page
@@ -289,13 +398,13 @@ Feature: Kolibri Studio critical workflows
Scenario: Delete a channel
Given I am signed in to Studio
And I have permissions to edit
- And I am on *My Channels* tab
+ And I am on *My channels* tab
When I click the *Options* button of a channel #the three dots to the right
Then I see a *Delete channel* option
When I click the *Delete channel* option
And I click the *Delete channel* button
Then I see a message that the channel is deleted
- And the deleted channel is no longer displayed at *My Channels* page
+ And the deleted channel is no longer displayed at *My channels* page
Feature: View and edit account information
Given I am signed in to Studio
@@ -317,6 +426,7 @@ Feature: Kolibri Studio critical workflows
And I can see the *Request more space* section
Scenario: Submit more space request
+ Given I am signed-in to Kolibri Studio
When I fill in all the space request text fields
And I click the *Send request* submit button
Then I see all the text fields clear
@@ -338,7 +448,7 @@ Feature: Kolibri Studio critical workflows
And I see the newly created collection
And I see the number of channels in that collection
- Scenario: Explore the content library
+ Scenario: Explore the Content library
Given I am signed in to Studio
When I go to the *Content library*
Then I see a page with the available public channels
@@ -346,3 +456,11 @@ Feature: Kolibri Studio critical workflows
And I can view or download the channel summary
When I click on a channel card
Then I can see all of the channel's resources in *View-only* format
+
+ Scenario: Sign out and confirm that pages visible only to a signed-in user are no longer accessible
+ Given I am signed in to Studio
+ When I click the user profile icon
+ And I click *Sign out*
+ Then I am at Kolibri Studio's sign-in page
+ When I click the browser's *Back* button
+ Then I can't access any of the pages I've visited as a signed-in user
diff --git a/jest_config/jest.conf.js b/jest_config/jest.conf.js
index 20987fa663..d797b317c3 100644
--- a/jest_config/jest.conf.js
+++ b/jest_config/jest.conf.js
@@ -19,6 +19,7 @@ module.exports = {
'shared/urls': path.resolve(__dirname, './globalMocks/urls.js'),
'^dexie$': require.resolve('dexie'),
'^mathlive$': 'identity-obj-proxy',
+ '^@tiptap/extension-code-block-lowlight$': '/node_modules/@tiptap/extension-code-block-lowlight/dist/index.js',
},
testEnvironment: 'jsdom',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
@@ -27,10 +28,10 @@ module.exports = {
},
transform: {
'^.+\\.js$': '/node_modules/babel-jest',
- '.*\\.(vue)$': '/node_modules/vue-jest',
+ '.*\\.(vue)$': '/node_modules/@vue/vue2-jest',
},
transformIgnorePatterns: [
- '/node_modules/.pnpm/(?!(vuetify|epubjs|kolibri-design-system|kolibri-constants|axios|lowlight|@tiptap|tiptap|prosemirror-.*|unified|unist-.*|hast-.*|bail|trough|vfile.*|remark-.*|rehype-.*|mdast-.*|devlop))',
+ '/node_modules/.pnpm/(?!(vuetify|epubjs|kolibri-design-system|kolibri-constants|axios|lowlight|@tiptap|tiptap|prosemirror-.*|unified|unist-.*|hast-.*|bail|trough|vfile.*|remark-.*|rehype-.*|mdast-.*|devlop|marked))',
],
snapshotSerializers: ['/node_modules/jest-serializer-vue'],
setupFilesAfterEnv: ['/jest_config/setup.js'],
diff --git a/jest_config/setup.js b/jest_config/setup.js
index 15307f4570..bfff655eef 100644
--- a/jest_config/setup.js
+++ b/jest_config/setup.js
@@ -7,16 +7,13 @@ import VueRouter from 'vue-router';
import Vuex from 'vuex';
import KThemePlugin from 'kolibri-design-system/lib/KThemePlugin';
import '@testing-library/jest-dom';
-import 'shared/i18n/setup';
// Polyfill structured clone for indexeddb with JSDOM
import "core-js/stable/structured-clone";
// Polyfill indexeddb
import 'fake-indexeddb/auto';
// Polyfill webstreams
import {ReadableStream, WritableStream, TransformStream, CountQueuingStrategy} from 'web-streams-polyfill';
-import jquery from 'jquery';
-window.jQuery = window.$ = jquery;
window.ReadableStream = global.ReadableStream = ReadableStream;
window.WritableStream = global.WritableStream = WritableStream;
window.TransformStream = global.TransformStream = TransformStream;
@@ -62,8 +59,6 @@ Vue.use(AnalyticsPlugin, { dataLayer: [] });
// Register global components
Vue.component('ActionLink', ActionLink);
-i18nSetup(true);
-
Vue.config.silent = true;
Vue.config.devtools = false;
Vue.config.productionTip = false;
@@ -95,6 +90,16 @@ jest.setTimeout(10000); // 10 sec
Object.defineProperty(window, 'scrollTo', { value: () => {}, writable: true });
+
resetJestGlobal();
setupSchema();
+
+// Use of setImmediate by fake-indexeddb makes tests fail with inactive or premature transaction
+// commit errors. This has something to do with microtasks, but since our code works correctly
+// in the browser, this seems specific to node.js and how fake-indexeddb works.
+global.setImmediate = global.setImmediate || ((fn, ...args) => global.setTimeout(fn, 0, ...args));
+
+module.exports = async function () {
+ await i18nSetup(true);
+};
diff --git a/k8s/images/app/Dockerfile b/k8s/images/app/Dockerfile
deleted file mode 120000
index 4750df1fc3..0000000000
--- a/k8s/images/app/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-docker/Dockerfile.prod
\ No newline at end of file
diff --git a/k8s/images/nginx/Dockerfile b/k8s/images/nginx/Dockerfile
deleted file mode 120000
index a4867c19b0..0000000000
--- a/k8s/images/nginx/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-docker/Dockerfile.nginx.prod
\ No newline at end of file
diff --git a/package.json b/package.json
index 5a08477887..9bc4525541 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,8 @@
"lint-frontend:format": "pnpm run lint-frontend --write",
"lint-frontend:watch": "pnpm run lint-frontend --monitor",
"lint-frontend:watch:format": "pnpm run lint-frontend --monitor --write",
- "makemessages": "kolibri-tools i18n-extract-messages --namespace contentcuration --searchPath contentcuration/contentcuration/frontend",
- "combineprofiles": "node ./node_modules/kolibri-tools/lib/combineStringProfiles.js ./contentcuration/locale/en/LC_MESSAGES/profiles/",
- "transfercontext": "kolibri-tools i18n-transfer-context --namespace studio --searchPath contentcuration/contentcuration/frontend; pnpm lint-all:fix",
+ "makemessages": "kolibri-i18n extract-messages --namespace contentcuration --searchPath contentcuration/contentcuration/frontend",
+ "transfercontext": "kolibri-i18n transfer-context --namespace studio --searchPath contentcuration/contentcuration/frontend; pnpm lint-all:fix",
"build": "webpack --env prod --config webpack.config.js",
"postgres": "pg_ctl -D /usr/local/var/postgresql@9.6 start || true",
"redis": "redis-server /usr/local/etc/redis.conf || true",
@@ -34,7 +33,7 @@
"storybook": "start-storybook",
"storybook:debug": "start-storybook --debug-webpack",
"storybook:build": "build-storybook",
- "update-languages": "kolibri-tools i18n-code-gen --output-dir contentcuration/contentcuration/frontend/shared/i18n/ --lang-info contentcuration/contentcuration/language_info.json"
+ "update-languages": "kolibri-i18n code-gen --output-dir contentcuration/contentcuration/frontend/shared/i18n/ --lang-info contentcuration/contentcuration/language_info.json"
},
"repository": {
"type": "git",
@@ -53,21 +52,19 @@
},
"homepage": "https://github.com/learningequality/studio#readme",
"dependencies": {
- "@sentry/vue": "^9.40.0",
- "@tiptap/core": "^2.14.0",
- "@tiptap/extension-code-block-lowlight": "^2.23.0",
- "@tiptap/extension-link": "^2.23.1",
- "@tiptap/extension-subscript": "^2.14.0",
- "@tiptap/extension-superscript": "^2.26.1",
- "@tiptap/extension-underline": "^2.14.0",
- "@tiptap/starter-kit": "^2.13.0",
- "@tiptap/vue-2": "^2.13.0",
- "@toast-ui/editor": "^2.3.1",
- "ajv": "^8.12.0",
- "axios": "^1.11.0",
- "broadcast-channel": "^5.1.0",
- "codemirror": "5.58.2",
- "core-js": "^3.44.0",
+ "@sentry/vue": "^10.6.0",
+ "@tiptap/core": "^3.13.0",
+ "@tiptap/extension-code-block-lowlight": "^3.13.0",
+ "@tiptap/extension-link": "^3.13.0",
+ "@tiptap/extension-subscript": "^3.13.0",
+ "@tiptap/extension-superscript": "^3.13.0",
+ "@tiptap/extension-text-align": "^3.18.0",
+ "@tiptap/starter-kit": "^3.13.0",
+ "@tiptap/vue-2": "^3.13.0",
+ "ajv": "^8.18.0",
+ "axios": "^1.15.0",
+ "broadcast-channel": "^7.1.0",
+ "core-js": "^3.47.0",
"crc-32": "^1.2.2",
"dexie": "^3.2.6",
"dexie-observable": "3.0.0-beta.11",
@@ -77,23 +74,22 @@
"html2canvas": "^1.0.0-rc.5",
"i18n-iso-countries": "^7.11.3",
"intl": "1.2.5",
- "jquery": "^2.2.4",
"jspdf": "https://github.com/parallax/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e",
"jszip": "^3.10.1",
"kolibri-constants": "^0.2.12",
- "kolibri-design-system": "5.3.0",
- "lodash": "^4.17.21",
+ "kolibri-design-system": "5.6.0",
+ "lodash": "^4.18.1",
"lowlight": "^3.3.0",
+ "marked": "^16.1.1",
"material-icons": "0.3.1",
- "mathlive": "^0.105.3",
+ "mathlive": "^0.108.2",
"mutex-js": "^1.1.5",
"node-vibrant": "^4.0.3",
"pako": "^2.1.0",
"papaparse": "^5.4.1",
"pdfjs-dist": "^2.16.105",
- "qs": "^6.11.2",
+ "qs": "^6.14.2",
"regenerator-runtime": "^0.14.1",
- "showdown": "^2.1.0",
"spark-md5": "^3.0.0",
"store2": "^2.14.4",
"string-strip-html": "8.3.0",
@@ -105,48 +101,51 @@
"vue-router": "3.6.5",
"vuetify": "^1.5.24",
"vuex": "^3.0.1",
- "workbox-core": "^7.3.0",
- "workbox-precaching": "^7.3.0",
+ "workbox-core": "^7.4.0",
+ "workbox-precaching": "^7.4.0",
"workbox-routing": "^7.3.0",
"workbox-strategies": "^7.3.0",
"workbox-window": "^7.3.0"
},
"devDependencies": {
- "@babel/core": "^7.28.0",
- "@babel/plugin-syntax-import-assertions": "^7.27.1",
- "@babel/plugin-transform-runtime": "^7.28.0",
- "@babel/preset-env": "^7.28.0",
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-syntax-import-assertions": "^7.28.6",
+ "@babel/plugin-transform-runtime": "^7.29.0",
+ "@babel/preset-env": "^7.29.2",
+ "@crowdin/cli": "^4.14.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@testing-library/vue": "^5",
"@vue/test-utils": "^1.3.6",
+ "@vue/vue2-jest": "29.2.6",
"aphrodite": "https://github.com/learningequality/aphrodite.git",
"autoprefixer": "^10.4.19",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^29.7.0",
- "babel-loader": "^9.2.1",
+ "babel-loader": "^10.0.0",
"circular-dependency-plugin": "^5.2.0",
"css-loader": "7.1.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-webpack": "0.13.10",
"eslint-plugin-import": "^2.31.0",
- "eslint-plugin-jest": "^28.11.0",
+ "eslint-plugin-jest": "^29.1.0",
"eslint-plugin-jest-dom": "^5.5.0",
"eslint-plugin-kolibri": "^0.18.0",
"eslint-plugin-vue": "^9.32.0",
- "fake-indexeddb": "^5.0.2",
+ "fake-indexeddb": "^6.2.5",
"file-loader": "^6.2.0",
"flush-promises": "^1.0.2",
"identity-obj-proxy": "^3.0.0",
- "jest": "^29.7.0",
- "jest-cli": "^29.7.0",
- "jest-each": "^30.0.5",
- "jest-environment-jsdom": "^29.7.0",
+ "jest": "^30.3.0",
+ "jest-cli": "^30.3.0",
+ "jest-each": "^30.3.0",
+ "jest-environment-jsdom": "^30.3.0",
"jest-serializer-vue": "^3.1.0",
+ "kolibri-build": "1.0.0",
"kolibri-format": "1.0.1",
- "kolibri-tools": "0.18.2",
- "mini-css-extract-plugin": "^2.8.1",
+ "kolibri-i18n": "1.0.0",
+ "mini-css-extract-plugin": "^2.9.4",
"node-sass": "9.0.0",
"npm-run-all": "^4.1.3",
"postcss-html": "^1.8.0",
@@ -165,18 +164,18 @@
"stylelint-config-standard": "34.0.0",
"stylelint-csstree-validator": "3.0.0",
"stylelint-scss": "5.3.2",
- "stylus": "^0.63.0",
- "stylus-loader": "^8.1.0",
+ "stylus": "^0.64.0",
+ "stylus-loader": "^8.1.2",
"vue-jest": "^3.0.7",
"vue-loader": "15.11.1",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "2.7.16",
"web-streams-polyfill": "^4.0.0",
- "webpack": "^5.97.1",
+ "webpack": "^5.104.1",
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.2",
"webpack-merge": "^6.0.1",
- "workbox-webpack-plugin": "^7.3.0"
+ "workbox-webpack-plugin": "^7.4.0"
},
"false": {},
"engines": {
@@ -186,7 +185,7 @@
"> 1%",
"Firefox ESR"
],
- "packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184",
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
"volta": {
"node": "20.19.3",
"pnpm": "10.12.4"
@@ -197,6 +196,7 @@
"deasync",
"es5-ext",
"node-sass",
+ "unrs-resolver",
"vue-demi"
]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7131ed1f80..0093622756 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,50 +9,44 @@ importers:
.:
dependencies:
'@sentry/vue':
- specifier: ^9.40.0
- version: 9.40.0(vue@2.7.16)
+ specifier: ^10.6.0
+ version: 10.6.0(vue@2.7.16)
'@tiptap/core':
- specifier: ^2.14.0
- version: 2.23.1(@tiptap/pm@2.23.1)
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/pm@3.13.0)
'@tiptap/extension-code-block-lowlight':
- specifier: ^2.23.0
- version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/extension-code-block@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(highlight.js@11.11.1)(lowlight@3.3.0)
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/extension-code-block@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(highlight.js@11.11.1)(lowlight@3.3.0)
'@tiptap/extension-link':
- specifier: ^2.23.1
- version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
'@tiptap/extension-subscript':
- specifier: ^2.14.0
- version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
'@tiptap/extension-superscript':
- specifier: ^2.26.1
- version: 2.26.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-underline':
- specifier: ^2.14.0
- version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-text-align':
+ specifier: ^3.18.0
+ version: 3.18.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
'@tiptap/starter-kit':
- specifier: ^2.13.0
- version: 2.23.1
+ specifier: ^3.13.0
+ version: 3.13.0
'@tiptap/vue-2':
- specifier: ^2.13.0
- version: 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(vue@2.7.16)
- '@toast-ui/editor':
- specifier: ^2.3.1
- version: 2.5.4
+ specifier: ^3.13.0
+ version: 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(vue@2.7.16)
ajv:
- specifier: ^8.12.0
- version: 8.17.1
+ specifier: ^8.18.0
+ version: 8.18.0
axios:
- specifier: ^1.11.0
- version: 1.11.0
+ specifier: ^1.15.0
+ version: 1.15.0
broadcast-channel:
- specifier: ^5.1.0
- version: 5.5.1
- codemirror:
- specifier: 5.58.2
- version: 5.58.2
+ specifier: ^7.1.0
+ version: 7.1.0
core-js:
- specifier: ^3.44.0
- version: 3.44.0
+ specifier: ^3.47.0
+ version: 3.47.0
crc-32:
specifier: ^1.2.2
version: 1.2.2
@@ -80,9 +74,6 @@ importers:
intl:
specifier: 1.2.5
version: 1.2.5
- jquery:
- specifier: ^2.2.4
- version: 2.2.4
jspdf:
specifier: https://github.com/parallax/jsPDF.git#b7a1d8239c596292ce86dafa77f05987bcfa2e6e
version: jspdf-yworks@https://codeload.github.com/parallax/jsPDF/tar.gz/b7a1d8239c596292ce86dafa77f05987bcfa2e6e
@@ -93,20 +84,23 @@ importers:
specifier: ^0.2.12
version: 0.2.12
kolibri-design-system:
- specifier: 5.3.0
- version: 5.3.0
+ specifier: 5.6.0
+ version: 5.6.0
lodash:
- specifier: ^4.17.21
- version: 4.17.21
+ specifier: ^4.18.1
+ version: 4.18.1
lowlight:
specifier: ^3.3.0
version: 3.3.0
+ marked:
+ specifier: ^16.1.1
+ version: 16.1.1
material-icons:
specifier: 0.3.1
version: 0.3.1
mathlive:
- specifier: ^0.105.3
- version: 0.105.3
+ specifier: ^0.108.2
+ version: 0.108.2
mutex-js:
specifier: ^1.1.5
version: 1.1.5
@@ -123,14 +117,11 @@ importers:
specifier: ^2.16.105
version: 2.16.105
qs:
- specifier: ^6.11.2
- version: 6.14.0
+ specifier: ^6.14.2
+ version: 6.14.2
regenerator-runtime:
specifier: ^0.14.1
version: 0.14.1
- showdown:
- specifier: ^2.1.0
- version: 2.1.0
spark-md5:
specifier: ^3.0.0
version: 3.0.2
@@ -165,11 +156,11 @@ importers:
specifier: ^3.0.1
version: 3.6.2(vue@2.7.16)
workbox-core:
- specifier: ^7.3.0
- version: 7.3.0
+ specifier: ^7.4.0
+ version: 7.4.0
workbox-precaching:
- specifier: ^7.3.0
- version: 7.3.0
+ specifier: ^7.4.0
+ version: 7.4.0
workbox-routing:
specifier: ^7.3.0
version: 7.3.0
@@ -181,17 +172,20 @@ importers:
version: 7.3.0
devDependencies:
'@babel/core':
- specifier: ^7.28.0
- version: 7.28.0
+ specifier: ^7.29.0
+ version: 7.29.0
'@babel/plugin-syntax-import-assertions':
- specifier: ^7.27.1
- version: 7.27.1(@babel/core@7.28.0)
+ specifier: ^7.28.6
+ version: 7.28.6(@babel/core@7.29.0)
'@babel/plugin-transform-runtime':
- specifier: ^7.28.0
- version: 7.28.0(@babel/core@7.28.0)
+ specifier: ^7.29.0
+ version: 7.29.0(@babel/core@7.29.0)
'@babel/preset-env':
- specifier: ^7.28.0
- version: 7.28.0(@babel/core@7.28.0)
+ specifier: ^7.29.2
+ version: 7.29.2(@babel/core@7.29.0)
+ '@crowdin/cli':
+ specifier: ^4.14.1
+ version: 4.14.1(encoding@0.1.13)
'@testing-library/jest-dom':
specifier: ^6.6.3
version: 6.6.3
@@ -204,6 +198,9 @@ importers:
'@vue/test-utils':
specifier: ^1.3.6
version: 1.3.6(vue-template-compiler@2.7.16)(vue@2.7.16)
+ '@vue/vue2-jest':
+ specifier: 29.2.6
+ version: 29.2.6(@babel/core@7.29.0)(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(babel-jest@29.7.0(@babel/core@7.29.0))(ejs@3.1.10)(jest@30.3.0(@types/node@25.5.2))(lodash@4.18.1)(typescript@5.8.3)(vue-template-compiler@2.7.16)(vue@2.7.16)
aphrodite:
specifier: https://github.com/learningequality/aphrodite.git
version: https://codeload.github.com/learningequality/aphrodite/tar.gz/fdc8d7be8912a5cf17f74ff10f124013c52c3e32
@@ -212,19 +209,19 @@ importers:
version: 10.4.21(postcss@8.5.6)
babel-core:
specifier: 7.0.0-bridge.0
- version: 7.0.0-bridge.0(@babel/core@7.28.0)
+ version: 7.0.0-bridge.0(@babel/core@7.29.0)
babel-jest:
specifier: ^29.7.0
- version: 29.7.0(@babel/core@7.28.0)
+ version: 29.7.0(@babel/core@7.29.0)
babel-loader:
- specifier: ^9.2.1
- version: 9.2.1(@babel/core@7.28.0)(webpack@5.99.9)
+ specifier: ^10.0.0
+ version: 10.0.0(@babel/core@7.29.0)(webpack@5.104.1)
circular-dependency-plugin:
specifier: ^5.2.0
- version: 5.2.2(webpack@5.99.9)
+ version: 5.2.2(webpack@5.104.1)
css-loader:
specifier: 7.1.2
- version: 7.1.2(webpack@5.99.9)
+ version: 7.1.2(webpack@5.104.1)
eslint:
specifier: ^8.57.0
version: 8.57.1
@@ -233,13 +230,13 @@ importers:
version: 10.1.8(eslint@8.57.1)
eslint-import-resolver-webpack:
specifier: 0.13.10
- version: 0.13.10(eslint-plugin-import@2.32.0)(webpack@5.99.9)
+ version: 0.13.10(eslint-plugin-import@2.32.0)(webpack@5.104.1)
eslint-plugin-import:
specifier: ^2.31.0
version: 2.32.0(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1)
eslint-plugin-jest:
- specifier: ^28.11.0
- version: 28.14.0(eslint@8.57.1)(jest@29.7.0(@types/node@24.1.0))(typescript@5.8.3)
+ specifier: ^29.1.0
+ version: 29.1.0(eslint@8.57.1)(jest@30.3.0(@types/node@25.5.2))(typescript@5.8.3)
eslint-plugin-jest-dom:
specifier: ^5.5.0
version: 5.5.0(@testing-library/dom@9.3.4)(eslint@8.57.1)
@@ -250,11 +247,11 @@ importers:
specifier: ^9.32.0
version: 9.33.0(eslint@8.57.1)
fake-indexeddb:
- specifier: ^5.0.2
- version: 5.0.2
+ specifier: ^6.2.5
+ version: 6.2.5
file-loader:
specifier: ^6.2.0
- version: 6.2.0(webpack@5.99.9)
+ version: 6.2.0(webpack@5.104.1)
flush-promises:
specifier: ^1.0.2
version: 1.0.2
@@ -262,29 +259,32 @@ importers:
specifier: ^3.0.0
version: 3.0.0
jest:
- specifier: ^29.7.0
- version: 29.7.0(@types/node@24.1.0)
+ specifier: ^30.3.0
+ version: 30.3.0(@types/node@25.5.2)
jest-cli:
- specifier: ^29.7.0
- version: 29.7.0(@types/node@24.1.0)
+ specifier: ^30.3.0
+ version: 30.3.0(@types/node@25.5.2)
jest-each:
- specifier: ^30.0.5
- version: 30.0.5
+ specifier: ^30.3.0
+ version: 30.3.0
jest-environment-jsdom:
- specifier: ^29.7.0
- version: 29.7.0
+ specifier: ^30.3.0
+ version: 30.3.0
jest-serializer-vue:
specifier: ^3.1.0
version: 3.1.0
+ kolibri-build:
+ specifier: 1.0.0
+ version: 1.0.0(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(file-loader@6.2.0(webpack@5.104.1))(postcss@8.5.6)(typescript@5.8.3)(vue-template-compiler@2.7.16)(webpack@5.104.1)
kolibri-format:
specifier: 1.0.1
- version: 1.0.1(@testing-library/dom@9.3.4)(eslint-import-resolver-webpack@0.13.10)(jest@29.7.0(@types/node@24.1.0))(postcss@8.5.6)(typescript@5.8.3)
- kolibri-tools:
- specifier: 0.18.2
- version: 0.18.2(@testing-library/dom@9.3.4)(@types/node@24.1.0)(ejs@3.1.10)(eslint-import-resolver-webpack@0.13.10)(file-loader@6.2.0(webpack@5.99.9))(postcss@8.5.6)(typescript@5.8.3)(vue@2.7.16)
+ version: 1.0.1(@testing-library/dom@9.3.4)(eslint-import-resolver-webpack@0.13.10)(jest@30.3.0(@types/node@25.5.2))(postcss@8.5.6)(typescript@5.8.3)
+ kolibri-i18n:
+ specifier: 1.0.0
+ version: 1.0.0(@testing-library/dom@9.3.4)(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(eslint-import-resolver-webpack@0.13.10)(file-loader@6.2.0(webpack@5.104.1))(jest@30.3.0(@types/node@25.5.2))(postcss@8.5.6)(typescript@5.8.3)(vue-template-compiler@2.7.16)(webpack@5.104.1)
mini-css-extract-plugin:
- specifier: ^2.8.1
- version: 2.9.2(webpack@5.99.9)
+ specifier: ^2.9.4
+ version: 2.9.4(webpack@5.104.1)
node-sass:
specifier: 9.0.0
version: 9.0.0
@@ -299,7 +299,7 @@ importers:
version: 6.0.0(postcss@8.5.6)
postcss-loader:
specifier: ^8.1.1
- version: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.99.9)
+ version: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.104.1)
postcss-sass:
specifier: ^0.5.0
version: 0.5.0
@@ -308,10 +308,10 @@ importers:
version: 4.0.9(postcss@8.5.6)
sass-loader:
specifier: 16.0.5
- version: 16.0.5(node-sass@9.0.0)(webpack@5.99.9)
+ version: 16.0.5(node-sass@9.0.0)(webpack@5.104.1)
style-loader:
specifier: ^4.0.0
- version: 4.0.0(webpack@5.99.9)
+ version: 4.0.0(webpack@5.104.1)
stylelint:
specifier: ^15.11.0
version: 15.11.0(typescript@5.8.3)
@@ -340,17 +340,17 @@ importers:
specifier: 5.3.2
version: 5.3.2(stylelint@15.11.0(typescript@5.8.3))
stylus:
- specifier: ^0.63.0
- version: 0.63.0
+ specifier: ^0.64.0
+ version: 0.64.0
stylus-loader:
- specifier: ^8.1.0
- version: 8.1.1(stylus@0.63.0)(webpack@5.99.9)
+ specifier: ^8.1.2
+ version: 8.1.2(stylus@0.64.0)(webpack@5.104.1)
vue-jest:
specifier: ^3.0.7
- version: 3.0.7(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(vue-template-compiler@2.7.16)(vue@2.7.16)
+ version: 3.0.7(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(vue-template-compiler@2.7.16)(vue@2.7.16)
vue-loader:
specifier: 15.11.1
- version: 15.11.1(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(css-loader@7.1.2(webpack@5.99.9))(ejs@3.1.10)(lodash@4.17.21)(vue-template-compiler@2.7.16)(webpack@5.99.9)
+ version: 15.11.1(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(css-loader@7.1.2(webpack@5.104.1))(ejs@3.1.10)(lodash@4.18.1)(vue-template-compiler@2.7.16)(webpack@5.104.1)
vue-style-loader:
specifier: ^4.1.3
version: 4.1.3
@@ -361,20 +361,20 @@ importers:
specifier: ^4.0.0
version: 4.1.0
webpack:
- specifier: ^5.97.1
- version: 5.99.9(webpack-cli@6.0.1)
+ specifier: ^5.104.1
+ version: 5.104.1(webpack-cli@6.0.1)
webpack-cli:
specifier: ^6.0.1
- version: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ version: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1)
webpack-dev-server:
specifier: ^5.2.2
- version: 5.2.2(webpack-cli@6.0.1)(webpack@5.99.9)
+ version: 5.2.2(webpack-cli@6.0.1)(webpack@5.104.1)
webpack-merge:
specifier: ^6.0.1
version: 6.0.1
workbox-webpack-plugin:
- specifier: ^7.3.0
- version: 7.3.0(@types/babel__core@7.20.5)(webpack@5.99.9)
+ specifier: ^7.4.0
+ version: 7.4.0(@types/babel__core@7.20.5)(webpack@5.104.1)
packages:
@@ -384,57 +384,58 @@ packages:
'@adobe/css-tools@4.4.3':
resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==}
- '@ampproject/remapping@2.3.0':
- resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
- engines: {node: '>=6.0.0'}
-
'@apideck/better-ajv-errors@0.3.6':
resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==}
engines: {node: '>=10'}
peerDependencies:
ajv: '>=8'
- '@babel/code-frame@7.12.11':
- resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==}
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
- '@babel/code-frame@7.27.1':
- resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
- '@babel/compat-data@7.28.0':
- resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==}
+ '@babel/compat-data@7.29.0':
+ resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
engines: {node: '>=6.9.0'}
- '@babel/core@7.28.0':
- resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==}
+ '@babel/core@7.29.0':
+ resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
engines: {node: '>=6.9.0'}
- '@babel/generator@7.28.0':
- resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==}
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
engines: {node: '>=6.9.0'}
'@babel/helper-annotate-as-pure@7.27.3':
resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
engines: {node: '>=6.9.0'}
- '@babel/helper-compilation-targets@7.27.2':
- resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-create-class-features-plugin@7.27.1':
- resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==}
+ '@babel/helper-create-class-features-plugin@7.28.6':
+ resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/helper-create-regexp-features-plugin@7.27.1':
- resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==}
+ '@babel/helper-create-regexp-features-plugin@7.28.5':
+ resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/helper-define-polyfill-provider@0.6.5':
- resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==}
+ '@babel/helper-define-polyfill-provider@0.6.6':
+ resolution: {integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ '@babel/helper-define-polyfill-provider@0.6.8':
+ resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==}
peerDependencies:
'@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
@@ -442,16 +443,16 @@ packages:
resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-member-expression-to-functions@7.27.1':
- resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==}
+ '@babel/helper-member-expression-to-functions@7.28.5':
+ resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
engines: {node: '>=6.9.0'}
- '@babel/helper-module-imports@7.27.1':
- resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-module-transforms@7.27.3':
- resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==}
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -460,8 +461,8 @@ packages:
resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-plugin-utils@7.27.1':
- resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
engines: {node: '>=6.9.0'}
'@babel/helper-remap-async-to-generator@7.27.1':
@@ -470,8 +471,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/helper-replace-supers@7.27.1':
- resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==}
+ '@babel/helper-replace-supers@7.28.6':
+ resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -484,33 +485,34 @@ packages:
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@7.27.1':
- resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
- '@babel/helper-wrap-function@7.27.1':
- resolution: {integrity: sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==}
+ '@babel/helper-wrap-function@7.28.6':
+ resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==}
engines: {node: '>=6.9.0'}
- '@babel/helpers@7.27.6':
- resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==}
+ '@babel/helpers@7.28.6':
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
engines: {node: '>=6.9.0'}
- '@babel/highlight@7.25.9':
- resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==}
- engines: {node: '>=6.9.0'}
+ '@babel/parser@7.29.0':
+ resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
- '@babel/parser@7.28.0':
- resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==}
+ '@babel/parser@7.29.2':
+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1':
- resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==}
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5':
+ resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -533,8 +535,8 @@ packages:
peerDependencies:
'@babel/core': ^7.13.0
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1':
- resolution: {integrity: sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==}
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6':
+ resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -566,20 +568,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-flow@7.27.1':
- resolution: {integrity: sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-import-assertions@7.27.1':
- resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==}
+ '@babel/plugin-syntax-import-assertions@7.28.6':
+ resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-import-attributes@7.27.1':
- resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==}
+ '@babel/plugin-syntax-import-attributes@7.28.6':
+ resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -594,8 +590,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-jsx@7.27.1':
- resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
+ '@babel/plugin-syntax-jsx@7.28.6':
+ resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -642,8 +638,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-typescript@7.27.1':
- resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
+ '@babel/plugin-syntax-typescript@7.28.6':
+ resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -660,14 +656,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-async-generator-functions@7.28.0':
- resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==}
+ '@babel/plugin-transform-async-generator-functions@7.29.0':
+ resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-async-to-generator@7.27.1':
- resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==}
+ '@babel/plugin-transform-async-to-generator@7.28.6':
+ resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -678,44 +674,44 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-block-scoping@7.28.0':
- resolution: {integrity: sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==}
+ '@babel/plugin-transform-block-scoping@7.28.6':
+ resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-class-properties@7.27.1':
- resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==}
+ '@babel/plugin-transform-class-properties@7.28.6':
+ resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-class-static-block@7.27.1':
- resolution: {integrity: sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==}
+ '@babel/plugin-transform-class-static-block@7.28.6':
+ resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.12.0
- '@babel/plugin-transform-classes@7.28.0':
- resolution: {integrity: sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==}
+ '@babel/plugin-transform-classes@7.28.6':
+ resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-computed-properties@7.27.1':
- resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==}
+ '@babel/plugin-transform-computed-properties@7.28.6':
+ resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-destructuring@7.28.0':
- resolution: {integrity: sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==}
+ '@babel/plugin-transform-destructuring@7.28.5':
+ resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-dotall-regex@7.27.1':
- resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==}
+ '@babel/plugin-transform-dotall-regex@7.28.6':
+ resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -726,8 +722,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1':
- resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==}
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0':
+ resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -738,14 +734,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-explicit-resource-management@7.28.0':
- resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==}
+ '@babel/plugin-transform-explicit-resource-management@7.28.6':
+ resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-exponentiation-operator@7.27.1':
- resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==}
+ '@babel/plugin-transform-exponentiation-operator@7.28.6':
+ resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -756,12 +752,6 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-flow-strip-types@7.27.1':
- resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
'@babel/plugin-transform-for-of@7.27.1':
resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==}
engines: {node: '>=6.9.0'}
@@ -774,8 +764,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-json-strings@7.27.1':
- resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==}
+ '@babel/plugin-transform-json-strings@7.28.6':
+ resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -786,8 +776,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-logical-assignment-operators@7.27.1':
- resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==}
+ '@babel/plugin-transform-logical-assignment-operators@7.28.6':
+ resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -804,14 +794,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-modules-commonjs@7.27.1':
- resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==}
+ '@babel/plugin-transform-modules-commonjs@7.28.6':
+ resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-modules-systemjs@7.27.1':
- resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==}
+ '@babel/plugin-transform-modules-systemjs@7.29.0':
+ resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -822,8 +812,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-named-capturing-groups-regex@7.27.1':
- resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==}
+ '@babel/plugin-transform-named-capturing-groups-regex@7.29.0':
+ resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -834,20 +824,20 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-nullish-coalescing-operator@7.27.1':
- resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==}
+ '@babel/plugin-transform-nullish-coalescing-operator@7.28.6':
+ resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-numeric-separator@7.27.1':
- resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==}
+ '@babel/plugin-transform-numeric-separator@7.28.6':
+ resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-object-rest-spread@7.28.0':
- resolution: {integrity: sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==}
+ '@babel/plugin-transform-object-rest-spread@7.28.6':
+ resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -858,14 +848,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-optional-catch-binding@7.27.1':
- resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==}
+ '@babel/plugin-transform-optional-catch-binding@7.28.6':
+ resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-optional-chaining@7.27.1':
- resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==}
+ '@babel/plugin-transform-optional-chaining@7.28.6':
+ resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -876,14 +866,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-private-methods@7.27.1':
- resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==}
+ '@babel/plugin-transform-private-methods@7.28.6':
+ resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-private-property-in-object@7.27.1':
- resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==}
+ '@babel/plugin-transform-private-property-in-object@7.28.6':
+ resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -894,14 +884,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-regenerator@7.28.0':
- resolution: {integrity: sha512-LOAozRVbqxEVjSKfhGnuLoE4Kz4Oc5UJzuvFUhSsQzdCdaAQu06mG8zDv2GFSerM62nImUZ7K92vxnQcLSDlCQ==}
+ '@babel/plugin-transform-regenerator@7.29.0':
+ resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-regexp-modifiers@7.27.1':
- resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==}
+ '@babel/plugin-transform-regexp-modifiers@7.28.6':
+ resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
@@ -912,8 +902,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-runtime@7.28.0':
- resolution: {integrity: sha512-dGopk9nZrtCs2+nfIem25UuHyt5moSJamArzIoh9/vezUQPmYDOzjaHDCkAzuGJibCIkPup8rMT2+wYB6S73cA==}
+ '@babel/plugin-transform-runtime@7.29.0':
+ resolution: {integrity: sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -924,8 +914,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-spread@7.27.1':
- resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==}
+ '@babel/plugin-transform-spread@7.28.6':
+ resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -948,20 +938,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-typescript@7.28.0':
- resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
'@babel/plugin-transform-unicode-escapes@7.27.1':
resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-unicode-property-regex@7.27.1':
- resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==}
+ '@babel/plugin-transform-unicode-property-regex@7.28.6':
+ resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -972,20 +956,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-unicode-sets-regex@7.27.1':
- resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==}
+ '@babel/plugin-transform-unicode-sets-regex@7.28.6':
+ resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/preset-env@7.28.0':
- resolution: {integrity: sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/preset-flow@7.27.1':
- resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==}
+ '@babel/preset-env@7.29.2':
+ resolution: {integrity: sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -995,55 +973,83 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
- '@babel/preset-typescript@7.27.1':
- resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==}
+ '@babel/runtime@7.27.0':
+ resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- '@babel/register@7.27.1':
- resolution: {integrity: sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==}
+ '@babel/runtime@7.27.6':
+ resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- '@babel/runtime@7.23.2':
- resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==}
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
- '@babel/runtime@7.27.6':
- resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
+ '@babel/runtime@7.28.6':
+ resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
engines: {node: '>=6.9.0'}
- '@babel/template@7.27.2':
- resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'}
- '@babel/traverse@7.28.0':
- resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==}
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.28.0':
- resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==}
+ '@babel/types@7.29.0':
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
- '@cortex-js/compute-engine@0.28.0':
- resolution: {integrity: sha512-kGs74P4KVNLSqu+iVhLSAvodScZdVGoZI2kOsUjnBEFCFjlPJ1Nj5TpBz/4nwPT+viguB+g7VseXsmcxWRx23Q==}
+ '@cortex-js/compute-engine@0.30.2':
+ resolution: {integrity: sha512-Zx+iisk9WWdbxjm8EYsneIBszvjfUs7BHNwf1jBtSINIgfWGpHrTTq9vW0J59iGCFt6bOFxbmWyxNMRSmksHMA==}
engines: {node: '>=21.7.3', npm: '>=10.5.0'}
+ '@crowdin/cli@4.14.1':
+ resolution: {integrity: sha512-kPhXW4Cw8feITgN1ymH9UPScKoFj5IwIL3PbKSeF/UhQW9jjVsjlGVO5WM0QYy5kZYJWQ+4e81ciXFr5vyflqQ==}
+ hasBin: true
+
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
'@csstools/css-parser-algorithms@2.7.1':
resolution: {integrity: sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==}
engines: {node: ^14 || ^16 || >=18}
peerDependencies:
'@csstools/css-tokenizer': ^2.4.1
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
'@csstools/css-tokenizer@2.4.1':
resolution: {integrity: sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==}
engines: {node: ^14 || ^16 || >=18}
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
'@csstools/media-query-list-parser@2.1.13':
resolution: {integrity: sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==}
engines: {node: ^14 || ^16 || >=18}
@@ -1061,20 +1067,35 @@ packages:
resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==}
engines: {node: '>=14.17.0'}
+ '@discoveryjs/json-ext@1.0.0':
+ resolution: {integrity: sha512-dDlz3W405VMFO4w5kIP9DOmELBcvFQGmLoKSdIRstBDubKFYwaNHV1NnlzMCQpXQFGWVALmeMORAuiLx18AvZQ==}
+ engines: {node: '>=14.17.0'}
+
+ '@emnapi/core@1.9.2':
+ resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
+
+ '@emnapi/runtime@1.9.2':
+ resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
+
+ '@emnapi/wasi-threads@1.2.1':
+ resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
+
'@eslint-community/eslint-utils@4.7.0':
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ '@eslint-community/eslint-utils@4.9.0':
+ resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
- '@eslint/eslintrc@0.4.3':
- resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}
- engines: {node: ^10.12.0 || >=12.0.0}
-
'@eslint/eslintrc@2.1.4':
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1083,6 +1104,15 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
'@gar/promisify@1.1.3':
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
@@ -1091,19 +1121,10 @@ packages:
engines: {node: '>=10.10.0'}
deprecated: Use @eslint/config-array instead
- '@humanwhocodes/config-array@0.5.0':
- resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}
- engines: {node: '>=10.10.0'}
- deprecated: Use @eslint/config-array instead
-
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
- '@humanwhocodes/object-schema@1.2.1':
- resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
- deprecated: Use @eslint/object-schema instead
-
'@humanwhocodes/object-schema@2.0.3':
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
@@ -1112,6 +1133,14 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
+ '@isaacs/cliui@9.0.0':
+ resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
+ engines: {node: '>=18'}
+
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
'@istanbuljs/load-nyc-config@1.1.0':
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
engines: {node: '>=8'}
@@ -1120,50 +1149,64 @@ packages:
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
engines: {node: '>=8'}
- '@jest/console@29.7.0':
- resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/console@30.3.0':
+ resolution: {integrity: sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/core@29.7.0':
- resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/core@30.3.0':
+ resolution: {integrity: sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
- '@jest/environment@29.7.0':
- resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/diff-sequences@30.3.0':
+ resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/expect-utils@29.7.0':
- resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/environment-jsdom-abstract@30.3.0':
+ resolution: {integrity: sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ canvas: ^3.0.0
+ jsdom: '*'
+ peerDependenciesMeta:
+ canvas:
+ optional: true
- '@jest/expect@29.7.0':
- resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/environment@30.3.0':
+ resolution: {integrity: sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/fake-timers@29.7.0':
- resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/expect-utils@30.3.0':
+ resolution: {integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/get-type@30.0.1':
- resolution: {integrity: sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==}
+ '@jest/expect@30.3.0':
+ resolution: {integrity: sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/globals@29.7.0':
- resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/fake-timers@30.3.0':
+ resolution: {integrity: sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/get-type@30.1.0':
+ resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/globals@30.3.0':
+ resolution: {integrity: sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/pattern@30.0.1':
resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/reporters@29.7.0':
- resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/reporters@30.3.0':
+ resolution: {integrity: sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
@@ -1178,28 +1221,36 @@ packages:
resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/source-map@29.6.3':
- resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/snapshot-utils@30.3.0':
+ resolution: {integrity: sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/test-result@29.7.0':
- resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/source-map@30.0.1':
+ resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- '@jest/test-sequencer@29.7.0':
- resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/test-result@30.3.0':
+ resolution: {integrity: sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ '@jest/test-sequencer@30.3.0':
+ resolution: {integrity: sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/transform@29.7.0':
resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ '@jest/transform@30.3.0':
+ resolution: {integrity: sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
'@jest/types@29.6.3':
resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- '@jest/types@30.0.5':
- resolution: {integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==}
+ '@jest/types@30.3.0':
+ resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jimp/bmp@0.22.12':
@@ -1246,22 +1297,31 @@ packages:
'@jimp/utils@0.22.12':
resolution: {integrity: sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==}
- '@jridgewell/gen-mapping@0.3.12':
- resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==}
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
- '@jridgewell/source-map@0.3.10':
- resolution: {integrity: sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==}
+ '@jridgewell/source-map@0.3.11':
+ resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
'@jridgewell/sourcemap-codec@1.5.4':
resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==}
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
'@jsonjoy.com/base64@1.1.2':
resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==}
engines: {node: '>=10.0'}
@@ -1283,6 +1343,9 @@ packages:
'@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
+ '@napi-rs/wasm-runtime@0.2.12':
+ resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1319,8 +1382,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
- '@popperjs/core@2.11.8':
- resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+ '@pkgr/core@0.2.9':
+ resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
@@ -1365,8 +1429,8 @@ packages:
peerDependencies:
rollup: ^1.20.0||^2.0.0
- '@rollup/pluginutils@5.2.0':
- resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
+ '@rollup/pluginutils@5.3.0':
+ resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -1377,35 +1441,32 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
- '@rushstack/eslint-patch@1.12.0':
- resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==}
-
- '@sentry-internal/browser-utils@9.40.0':
- resolution: {integrity: sha512-Ajvz6jN+EEMKrOHcUv2+HlhbRUh69uXhhRoBjJw8sc61uqA2vv3QWyBSmTRoHdTnLGboT5bKEhHIkzVXb+YgEw==}
+ '@sentry-internal/browser-utils@10.6.0':
+ resolution: {integrity: sha512-pU0aUW1+wBQF4N4cd4aKX61GkiT2wKynsmNOUPR3vhhruuo4WG+GE8mSItO5+5ZyYCevzJ0aPl2QApQlfpnLvw==}
engines: {node: '>=18'}
- '@sentry-internal/feedback@9.40.0':
- resolution: {integrity: sha512-39UbLdGWGvSJ7bAzRnkv91cBdd6fLbdkLVVvqE2ZUfegm7+rH1mRPglmEhw4VE4mQfKZM1zWr/xus2+XPqJcYw==}
+ '@sentry-internal/feedback@10.6.0':
+ resolution: {integrity: sha512-oz5oG0R8/vaCImhLFA9jlSZFNlCwtpfyIUXuFW/pzR7OBQ8bnfxoZoM9U+vLpNNcChKojIPbs1/Vugg7MMxFzg==}
engines: {node: '>=18'}
- '@sentry-internal/replay-canvas@9.40.0':
- resolution: {integrity: sha512-GLoJ4R4Uipd7Vb+0LzSJA2qCyN1J6YalQIoDuOJTfYyykHvKltds5D8a/5S3Q6d8PcL/nxTn93fynauGEZt2Ow==}
+ '@sentry-internal/replay-canvas@10.6.0':
+ resolution: {integrity: sha512-ekRRvpKWW88vefQEx7EqBLnX+uSfHc6ovDLHdbYtYcT7sc4oWoDqwUN9FrDZxqzwdO0CBgC+iktKKewE5OFOzg==}
engines: {node: '>=18'}
- '@sentry-internal/replay@9.40.0':
- resolution: {integrity: sha512-WrmCvqbLJQC45IFRVN3k0J5pU5NkdX0e9o6XxjcmDiATKk00RHnW4yajnCJ8J1cPR4918yqiJHPX5xpG08BZNA==}
+ '@sentry-internal/replay@10.6.0':
+ resolution: {integrity: sha512-w5M+11k0WKT22h5M+Mio+H2sl3WYhRQkntlBrxt0SeevnNVbRODkmZOqTIUhvbGtBg3/yj1otqnsVEKanb0fSA==}
engines: {node: '>=18'}
- '@sentry/browser@9.40.0':
- resolution: {integrity: sha512-qz/1Go817vcsbcIwgrz4/T34vi3oQ4UIqikosuaCTI9wjZvK0HyW3QmLvTbAnsE7G7h6+UZsVkpO5R16IQvQhQ==}
+ '@sentry/browser@10.6.0':
+ resolution: {integrity: sha512-Nc50U5Zoyrw/Miz1gwvlyJsIYEdHWwperilM52cPvhYzPdeY31g5XPO/tCpcsgpfYdCjNYWcSwIrD/DCYn6YSA==}
engines: {node: '>=18'}
- '@sentry/core@9.40.0':
- resolution: {integrity: sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==}
+ '@sentry/core@10.6.0':
+ resolution: {integrity: sha512-9i0Yf0Px8sScUpGg5KPnn0PwrsO6zoPgp5W5dPp8j+dTmYAxPApoADP4IDF547lsXrm3oKEwEeqQ675xStOiwA==}
engines: {node: '>=18'}
- '@sentry/vue@9.40.0':
- resolution: {integrity: sha512-riYkgwaFhFiRr5EqL2+PWFPGlgSOlMe9LVqYKQepBbfT88sS82KJTcqafYAR5HOXJKvIz1IRbFmOJkQOILMayg==}
+ '@sentry/vue@10.6.0':
+ resolution: {integrity: sha512-fcN4kNYwTVROoeDwI0a8xiTLC9Iw5qB9r9dEo0odst/92Q7H2x84vjyC2i+eWmcbn4MLjoRvNqVnZRxeOD7JDw==}
engines: {node: '>=18'}
peerDependencies:
pinia: 2.x || 3.x
@@ -1417,14 +1478,14 @@ packages:
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
- '@sinclair/typebox@0.34.38':
- resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==}
+ '@sinclair/typebox@0.34.49':
+ resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==}
'@sinonjs/commons@3.0.1':
resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
- '@sinonjs/fake-timers@10.3.0':
- resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
+ '@sinonjs/fake-timers@15.3.0':
+ resolution: {integrity: sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg==}
'@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
@@ -1450,169 +1511,178 @@ packages:
vue: ^2.6.10
vue-template-compiler: ^2.6.10
- '@tiptap/core@2.23.1':
- resolution: {integrity: sha512-EURGKGsEPrwxvOPi9gA+BsczvsECJNV+xgTAGWHmEtU4YJ0AulYrCX3b7FK+aiduVhThIHDoG/Mmvmb/HPLRhQ==}
+ '@tiptap/core@3.13.0':
+ resolution: {integrity: sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==}
peerDependencies:
- '@tiptap/pm': ^2.7.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-blockquote@2.23.1':
- resolution: {integrity: sha512-GI3s+uFU88LWRaDG20Z9yIu2av3Usn8kw2lkm2ntwX1K6/mQBS/zkGhWr/FSwWOlMtTzYFxF4Ttb0e+hn67A/A==}
+ '@tiptap/extension-blockquote@3.13.0':
+ resolution: {integrity: sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-bold@2.23.1':
- resolution: {integrity: sha512-OM4RxuZeOqpYRN1G/YpXSE8tZ3sVtT2XlO3qKa74qf+htWz8W3x4X0oQCrHrRTDSAA1wbmeZU3QghAIHnbvP/A==}
+ '@tiptap/extension-bold@3.13.0':
+ resolution: {integrity: sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-bubble-menu@2.23.1':
- resolution: {integrity: sha512-tupuvrlZMTziVZXJuCVjLwllUnux/an9BtTYHpoRyLX9Hg0v7Kh39k9x58zJaoW8Q/Oc/qxPhbJpyOqhE1rLeg==}
+ '@tiptap/extension-bubble-menu@3.13.0':
+ resolution: {integrity: sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-bullet-list@2.23.1':
- resolution: {integrity: sha512-0g9U42m+boLJZP3x9KoJHDCp9WD5abaVdqNbTg9sFPDNsepb7Zaeu8AEB+yZLP/fuTI1I4ko6qkdr3UaaIYcmA==}
+ '@tiptap/extension-bullet-list@3.13.0':
+ resolution: {integrity: sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-code-block-lowlight@2.23.1':
- resolution: {integrity: sha512-EQwLyO1zKViSXcwdsnzurWOdVS1W3oiNuFnkxxSGtU8hlRbkDP4+dY8c/jRE2XejfJuxz7zY7iGg594d8+5/SA==}
+ '@tiptap/extension-code-block-lowlight@3.13.0':
+ resolution: {integrity: sha512-CesjEVUkDelSfRxauTkcrrVzzQAxEp5HYuGIXZtoFDmt2F2lYimlgJyvSxIbRqEw8sFQxvTEKlBg0EtF/BvCJg==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/extension-code-block': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/extension-code-block': ^3.13.0
+ '@tiptap/pm': ^3.13.0
highlight.js: ^11
lowlight: ^2 || ^3
- '@tiptap/extension-code-block@2.23.1':
- resolution: {integrity: sha512-eYzJVUR13BhSE/TYAMZihGBId+XiwhnTPqGcSFo+zx89It/vxwDLvAUn0PReMNI7ULKPTw8orUt2fVKSarb2DQ==}
+ '@tiptap/extension-code-block@3.13.0':
+ resolution: {integrity: sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
+
+ '@tiptap/extension-code@3.13.0':
+ resolution: {integrity: sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw==}
+ peerDependencies:
+ '@tiptap/core': ^3.13.0
+
+ '@tiptap/extension-document@3.13.0':
+ resolution: {integrity: sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-code@2.23.1':
- resolution: {integrity: sha512-3IOdE40m0UTR2+UXui69o/apLtutAbtzfgmMxD6q0qlRvVqz99QEfk9RPHDNlUqJtYCL4TD+sj7UclBsDdgVXA==}
+ '@tiptap/extension-dropcursor@3.13.0':
+ resolution: {integrity: sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/extensions': ^3.13.0
- '@tiptap/extension-document@2.23.1':
- resolution: {integrity: sha512-2nkIkGVsaMJkpd024E6vXK+5XNz8VOVWp/pM6bbXpuv0HnGPrfLdh4ruuFc+xTQ3WPOmpSu8ygtujt4I1o9/6g==}
+ '@tiptap/extension-floating-menu@3.13.0':
+ resolution: {integrity: sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@floating-ui/dom': ^1.0.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-dropcursor@2.23.1':
- resolution: {integrity: sha512-GyVp+o/RVrKlLdrQvtIpJGphFGogiPjcPCkAFcrfY1vDY1EYxfVZELC96gG1mUT1BO8FUD3hmbpkWi9l8/6O4A==}
+ '@tiptap/extension-gapcursor@3.13.0':
+ resolution: {integrity: sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/extensions': ^3.13.0
- '@tiptap/extension-floating-menu@2.23.1':
- resolution: {integrity: sha512-GMWkpH+p/OUOk1Y5UGOnKuHSDEVBN7DhYIJiWt5g9LK/mpPeuqoCmQg3RQDgjtZXb74SlxLK2pS/3YcAnemdfQ==}
+ '@tiptap/extension-hard-break@3.13.0':
+ resolution: {integrity: sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-gapcursor@2.23.1':
- resolution: {integrity: sha512-iP+TiFIGZEbOvYAs04pI14mLI4xqbt64Da91TgMF1FNZUrG+9eWKjqbcHLQREuK3Qnjn5f0DI4nOBv61FlnPmA==}
+ '@tiptap/extension-heading@3.13.0':
+ resolution: {integrity: sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-hard-break@2.23.1':
- resolution: {integrity: sha512-YF66EVxnBxt1bHPx6fUUSSXK1Vg+/9baJ0AfJ12hCSPCgSjUclRuNmWIH5ikVfByOmPV1xlrN9wryLoSEBcNRQ==}
+ '@tiptap/extension-horizontal-rule@3.13.0':
+ resolution: {integrity: sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-heading@2.23.1':
- resolution: {integrity: sha512-5BPoli9wudiAOgSyK8309jyRhFyu5vd02lNChfpHwxUudzIJ/L+0E6FcwrDcw+yXh23cx7F5SSjtFQ7AobxlDQ==}
+ '@tiptap/extension-italic@3.13.0':
+ resolution: {integrity: sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-history@2.23.1':
- resolution: {integrity: sha512-1rp2CRjM+P58oGEgeUUDSk0ch67ngIGbGJOOjiBGKU9GIVhI2j4uSwsYTAa9qYMjMUI6IyH1xJpsY2hLKcBOtg==}
+ '@tiptap/extension-link@3.13.0':
+ resolution: {integrity: sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-horizontal-rule@2.23.1':
- resolution: {integrity: sha512-uHEF0jpmhtgAxjKw8/s5ipEeTnu99f9RVMGAlmcthJ5Fx9TzH0MvtH4dtBNEu5MXC7+0bNsnncWo125AAbCohg==}
+ '@tiptap/extension-list-item@3.13.0':
+ resolution: {integrity: sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-italic@2.23.1':
- resolution: {integrity: sha512-a+cPzffaC/1AKMmZ1Ka6l81xmTgcalf8NXfBuFCUTf5r7uI9NIgXnLo9hg+jR9F4K+bwhC4/UbMvQQzAjh0c0A==}
+ '@tiptap/extension-list-keymap@3.13.0':
+ resolution: {integrity: sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-link@2.23.1':
- resolution: {integrity: sha512-zMD0V8djkvwRYACzd8EvFXXNLQH5poJt6aHC9/8uest6njRhlrRjSjwG5oa+xHW4A76XfAH0A5BPj6ZxZnAUQg==}
+ '@tiptap/extension-list@3.13.0':
+ resolution: {integrity: sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-list-item@2.23.1':
- resolution: {integrity: sha512-wVrRp6KAiyjFVFGmn+ojisP64Bsd+ZPdqQBYVbebBx1skZeW0uhG60d7vUkWHi0gCgxHZDfvDbXpfnOD0INRWw==}
+ '@tiptap/extension-ordered-list@3.13.0':
+ resolution: {integrity: sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-ordered-list@2.23.1':
- resolution: {integrity: sha512-Zp+qognyNgoaJ9bxkBwIuWJEnQ67RdsHXzv3YOdeGRbkUhd8LT6OL7P0mAuNbMBU8MwHxyJ7C7NsyzwzuVbFzA==}
+ '@tiptap/extension-paragraph@3.13.0':
+ resolution: {integrity: sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-paragraph@2.23.1':
- resolution: {integrity: sha512-LLEPizt1ALE7Ek6prlJ1uhoUCT8C/a3PdZpCh3DshM1L3Kv9TENlaJL2GhFl8SVUCwHmWHvXg30+4tIRFBedaQ==}
+ '@tiptap/extension-strike@3.13.0':
+ resolution: {integrity: sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-strike@2.23.1':
- resolution: {integrity: sha512-hAT9peYkKezRGp/EcPQKtyYQT+2XGUbb26toTr9XIBQIeQCuCpT+FirPrDMrMVWPwcJt7Rv+AzoVjDuBs9wE0A==}
+ '@tiptap/extension-subscript@3.13.0':
+ resolution: {integrity: sha512-8Lq1ATTDUyolue42UbWXAotHPY4Y0r6pMTJyZ9Dqxbv5VrlBk6XeApkGwq6etBXMUsENJycLHlBk3PVqhzGrfw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-subscript@2.23.1':
- resolution: {integrity: sha512-LFUwe90E90f38aES6ka0Jg7kUG3WTMq3M+7qRu6skEx4+izVB6ub5RTvA56trQlWefWiYeJZptf8xfIKdcwGSw==}
+ '@tiptap/extension-superscript@3.13.0':
+ resolution: {integrity: sha512-ljeaxgPy85IyRCYItKtd23fKmKlHbABq/sP4QGZ5D0PRYX5jF1dt8SEVVkDaoUu7YATRVa7MKl/NzKmTuVStjQ==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-superscript@2.26.1':
- resolution: {integrity: sha512-YTUmppwJchqXxE4nf+wTMuZuUU9/9ibg8p73rif6WxldjuH0RGZQRY8ad5Ha1c5clG+60e0nrXthqqLgvWfjtw==}
+ '@tiptap/extension-text-align@3.18.0':
+ resolution: {integrity: sha512-NEd2IUgOymKPmGOnxum4hLRbdQyBlK1Cmkt8QGIrmatovPrw2PtWmHVZ6foNChsi/r932dKVfqZ/uMUh8QUppQ==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.18.0
- '@tiptap/extension-text-style@2.23.1':
- resolution: {integrity: sha512-fZn1GePlL27pUFfKXKoRZo4L4pZP9dUjNNiS/eltLpbi/SenJ15UKhAoHtN1KQvNGJsWkYN49FjnnltU8qvQ+Q==}
+ '@tiptap/extension-text@3.13.0':
+ resolution: {integrity: sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-text@2.23.1':
- resolution: {integrity: sha512-XK0D/eyS1Vm5yUrCtkS0AfgyKLJqpi8nJivCOux/JLhhC4x87R1+mI8NoFDYZJ5ic/afREPSBB8jORqOi0qIHg==}
+ '@tiptap/extension-underline@3.13.0':
+ resolution: {integrity: sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-underline@2.23.1':
- resolution: {integrity: sha512-MTG+VlGStXD3uj7iPzZU8aJrqxoQyX45WX6xTouezaZzh/NQtTyVWwJqNGE7fsMhxirpJ+at0IZmqlDTjAhEDQ==}
+ '@tiptap/extensions@3.13.0':
+ resolution: {integrity: sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==}
peerDependencies:
- '@tiptap/core': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/pm@2.23.1':
- resolution: {integrity: sha512-iAx4rP0k4Xd9Ywh+Gpaz5IWfY2CYRpiwVXWekTHLlNRFtrVIWVpMxaQr2mvRU2g0Ca6rz5w3KzkHBMqrI3dIBA==}
+ '@tiptap/pm@3.13.0':
+ resolution: {integrity: sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==}
- '@tiptap/starter-kit@2.23.1':
- resolution: {integrity: sha512-rrImwzJbKSHoFa+WdNU4I0evXcMiQ4yRm737sxvNJwYItT6fXIxrbRT7nJDmtYu2TflcfT1KklEnSrzz1hhYRw==}
+ '@tiptap/starter-kit@3.13.0':
+ resolution: {integrity: sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg==}
- '@tiptap/vue-2@2.23.1':
- resolution: {integrity: sha512-AgbBm0PjYj+amuZZ4fZui3hrBexg1YVTw6ppDhZJL5pyY2PeTo4ag4tRoj+Z/CPS0Cxj3hC6OHgxrMdr6rYeCw==}
+ '@tiptap/vue-2@3.13.0':
+ resolution: {integrity: sha512-z0ptTUMhZIGPQSqjKXRxTjFxioBpv5BYcN5S2o7AVGa6P2vBecfkmRXg84Lxb3/USdF0k5Ru1zmVqweazNCCpA==}
peerDependencies:
- '@tiptap/core': ^2.7.0
- '@tiptap/pm': ^2.7.0
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
vue: ^2.6.0
- '@toast-ui/editor@2.5.4':
- resolution: {integrity: sha512-XsuYlPQxhec9dHQREFAigjE4enHSuGMF7D0YQ6wW7phmusvAu0FnJfZUPjJBoU/GKz7WP5U6fKU9/P+8j65D8A==}
-
'@tokenizer/token@0.3.0':
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
@@ -1628,6 +1698,9 @@ packages:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -1649,9 +1722,6 @@ packages:
'@types/bonjour@3.5.13':
resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==}
- '@types/codemirror@0.0.71':
- resolution: {integrity: sha512-b2oEEnno1LIGKMR7uBEsr40al1UijF1HEpRn0+Yf1xOLl24iQgB7DBpZVMM7y54G5wCNoclDrRO65E6KHPNO2w==}
-
'@types/connect-history-api-fallback@1.5.4':
resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==}
@@ -1697,8 +1767,8 @@ packages:
'@types/istanbul-reports@3.0.4':
resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
- '@types/jsdom@20.0.1':
- resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
+ '@types/jsdom@21.1.7':
+ resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -1734,12 +1804,12 @@ packages:
'@types/node@18.19.121':
resolution: {integrity: sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==}
- '@types/node@24.0.10':
- resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==}
-
'@types/node@24.1.0':
resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==}
+ '@types/node@25.5.2':
+ resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==}
+
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@@ -1776,9 +1846,6 @@ packages:
'@types/strip-json-comments@0.0.30':
resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
- '@types/tern@0.23.9':
- resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==}
-
'@types/tough-cookie@4.0.5':
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
@@ -1788,9 +1855,6 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
- '@types/web-bluetooth@0.0.20':
- resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
-
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
@@ -1800,46 +1864,152 @@ packages:
'@types/yargs@17.0.33':
resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}
- '@typescript-eslint/project-service@8.35.1':
- resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==}
+ '@types/yargs@17.0.35':
+ resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
+
+ '@typescript-eslint/project-service@8.47.0':
+ resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
+ typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/scope-manager@8.35.1':
- resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==}
+ '@typescript-eslint/scope-manager@8.47.0':
+ resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.35.1':
- resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==}
+ '@typescript-eslint/tsconfig-utils@8.47.0':
+ resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
+ typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/types@8.35.1':
- resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==}
+ '@typescript-eslint/types@8.47.0':
+ resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.35.1':
- resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==}
+ '@typescript-eslint/typescript-estree@8.47.0':
+ resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '>=4.8.4 <5.9.0'
+ typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/utils@8.35.1':
- resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==}
+ '@typescript-eslint/utils@8.47.0':
+ resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <5.9.0'
+ typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/visitor-keys@8.35.1':
- resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==}
+ '@typescript-eslint/visitor-keys@8.47.0':
+ resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
+ cpu: [x64]
+ os: [win32]
+
'@vibrant/color@4.0.0':
resolution: {integrity: sha512-S9ItdqS1135wTXoIIqAJu8df9dqlOo6Boc5Y4MGsBTu9UmUOvOwfj5b4Ga6S5yrLAKmKYIactkz7zYJdMddkig==}
@@ -1885,14 +2055,19 @@ packages:
vue: 2.x
vue-template-compiler: ^2.x
- '@vueuse/core@11.3.0':
- resolution: {integrity: sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==}
-
- '@vueuse/metadata@11.3.0':
- resolution: {integrity: sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==}
-
- '@vueuse/shared@11.3.0':
- resolution: {integrity: sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==}
+ '@vue/vue2-jest@29.2.6':
+ resolution: {integrity: sha512-nPu9IvnEkP0AEpo9ETOAk50uqyBa0QMJ9GnPYkC7EukFN1z29QKjyucICayMt8KuHJ9oYBca2TDMH40HowY9mQ==}
+ engines: {node: '>10'}
+ peerDependencies:
+ '@babel/core': 7.x
+ babel-jest: 29.x
+ jest: 29.x
+ typescript: '>= 4.3'
+ vue: ^2.x
+ vue-template-compiler: ^2.x
+ peerDependenciesMeta:
+ typescript:
+ optional: true
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -1967,7 +2142,7 @@ packages:
'@xmldom/xmldom@0.7.13':
resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==}
engines: {node: '>=10.0.0'}
- deprecated: this version is no longer supported, please update to at least 0.8.*
+ deprecated: this version has critical issues, please update to the latest version
'@xtuc/ieee754@1.2.0':
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -1979,10 +2154,6 @@ packages:
resolution: {integrity: sha512-I+Wi+qiE2kUXyrRhNsWv6XsjUTBJjSoVSctKNBfLG5zG/Xe7Rjbxf13+vqYHNTwHaFU+FtSlVxOCTiMEVtPv0A==}
deprecated: Use your platform's native atob() and btoa() methods instead
- abab@2.0.6:
- resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
- deprecated: Use your platform's native atob() and btoa() methods instead
-
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
@@ -1998,35 +2169,25 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
- accepts@2.0.0:
- resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
- engines: {node: '>= 0.6'}
-
acorn-globals@1.0.9:
resolution: {integrity: sha512-j3/4pkfih8W4NK22gxVSXcEonTpAHOHh0hu5BoZrKcOsW/4oBPxTi4Yk3SAj+FhC1f3+bRTkXdm4019gw1vg9g==}
- acorn-globals@7.0.1:
- resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
+ acorn-import-phases@1.0.4:
+ resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==}
+ engines: {node: '>=10.13.0'}
+ peerDependencies:
+ acorn: ^8.14.0
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
- acorn-walk@8.3.4:
- resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
- engines: {node: '>=0.4.0'}
-
acorn@2.7.0:
resolution: {integrity: sha512-pXK8ez/pVjqFdAgBkF1YPVRacuLQ9EXBKaKWaeh58WNfMkCmZhOZzu+NtKSPD5PHmCCHheQ5cD29qM1K4QTxIg==}
engines: {node: '>=0.4.0'}
hasBin: true
- acorn@7.4.1:
- resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
@@ -2036,6 +2197,10 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
agentkeepalive@4.6.0:
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
engines: {node: '>= 8.0.0'}
@@ -2062,15 +2227,11 @@ packages:
peerDependencies:
ajv: ^8.8.2
- ajv@6.12.6:
- resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ajv@6.14.0:
+ resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
- ajv@8.17.1:
- resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
-
- ansi-colors@4.1.3:
- resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
- engines: {node: '>=6'}
+ ajv@8.18.0:
+ resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
ansi-escapes@4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
@@ -2089,8 +2250,8 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
- ansi-regex@6.1.0:
- resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
ansi-styles@2.2.1:
@@ -2109,8 +2270,8 @@ packages:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
- ansi-styles@6.2.1:
- resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
any-base@1.1.0:
@@ -2236,6 +2397,13 @@ packages:
peerDependencies:
postcss: ^8.1.0
+ autoprefixer@10.4.23:
+ resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
autosize@3.0.21:
resolution: {integrity: sha512-xGFj5jTV4up6+fxRwtnAWiCIx/5N0tEnFn5rdhAkK1Lq2mliLMuGJgP5Bf4phck3sHGYrVKpYwugfJ61MSz9nA==}
@@ -2249,8 +2417,8 @@ packages:
aws4@1.13.2:
resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==}
- axios@1.11.0:
- resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
+ axios@1.15.0:
+ resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
babel-code-frame@6.26.0:
resolution: {integrity: sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==}
@@ -2266,6 +2434,12 @@ packages:
peerDependencies:
'@babel/core': ^7.8.0
+ babel-jest@30.3.0:
+ resolution: {integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ '@babel/core': ^7.11.0 || ^8.0.0-0
+
babel-loader@10.0.0:
resolution: {integrity: sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==}
engines: {node: ^18.20.0 || ^20.10.0 || >=22.0.0}
@@ -2273,12 +2447,18 @@ packages:
'@babel/core': ^7.12.0
webpack: '>=5.61.0'
- babel-loader@9.2.1:
- resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==}
- engines: {node: '>= 14.15.0'}
+ babel-loader@10.1.1:
+ resolution: {integrity: sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==}
+ engines: {node: ^18.20.0 || ^20.10.0 || >=22.0.0}
peerDependencies:
- '@babel/core': ^7.12.0
- webpack: '>=5'
+ '@babel/core': ^7.12.0 || ^8.0.0-beta.1
+ '@rspack/core': ^1.0.0 || ^2.0.0-0
+ webpack: '>=5.61.0'
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+ webpack:
+ optional: true
babel-messages@6.23.0:
resolution: {integrity: sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==}
@@ -2287,12 +2467,25 @@ packages:
resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
engines: {node: '>=8'}
+ babel-plugin-istanbul@7.0.1:
+ resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==}
+ engines: {node: '>=12'}
+
babel-plugin-jest-hoist@29.6.3:
resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- babel-plugin-polyfill-corejs2@0.4.14:
- resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==}
+ babel-plugin-jest-hoist@30.3.0:
+ resolution: {integrity: sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ babel-plugin-polyfill-corejs2@0.4.15:
+ resolution: {integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-corejs2@0.4.17:
+ resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==}
peerDependencies:
'@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
@@ -2301,8 +2494,18 @@ packages:
peerDependencies:
'@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
- babel-plugin-polyfill-regenerator@0.6.5:
- resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==}
+ babel-plugin-polyfill-corejs3@0.14.2:
+ resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-regenerator@0.6.6:
+ resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-regenerator@0.6.8:
+ resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==}
peerDependencies:
'@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
@@ -2317,12 +2520,23 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
+ babel-preset-current-node-syntax@1.2.0:
+ resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0 || ^8.0.0-0
+
babel-preset-jest@29.6.3:
resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@babel/core': ^7.0.0
+ babel-preset-jest@30.3.0:
+ resolution: {integrity: sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ '@babel/core': ^7.11.0 || ^8.0.0-beta.1
+
babel-runtime@6.26.0:
resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==}
@@ -2345,6 +2559,10 @@ packages:
balanced-match@2.0.0:
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
+ balanced-match@4.0.2:
+ resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
+ engines: {node: 20 || >=22}
+
base64-arraybuffer@1.0.2:
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
engines: {node: '>= 0.6.0'}
@@ -2352,6 +2570,15 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+ baseline-browser-mapping@2.10.16:
+ resolution: {integrity: sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ baseline-browser-mapping@2.9.19:
+ resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
+ hasBin: true
+
batch@0.6.1:
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
@@ -2378,10 +2605,6 @@ packages:
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
- body-parser@2.2.0:
- resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
- engines: {node: '>=18'}
-
bonjour-service@1.3.0:
resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==}
@@ -2397,12 +2620,19 @@ packages:
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+ brace-expansion@2.0.3:
+ resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
+
+ brace-expansion@5.0.2:
+ resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
+ engines: {node: 20 || >=22}
+
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- broadcast-channel@5.5.1:
- resolution: {integrity: sha512-C7LtMmJCIIU07xtJngYE2OxaGTGBsG+wOa0mBSPRpbTF36kqtsXQhpxtCVDTkpe8gpZMn9C6PhH+mZ/js4IabA==}
+ broadcast-channel@7.1.0:
+ resolution: {integrity: sha512-InJljddsYWbEL8LBnopnCg+qMQp9KcowvYWOt4YWrjD5HmxzDYKdVbDS1w/ji5rFZdRD58V5UxJPtBdpEbEJYw==}
browserslist-config-kolibri@0.18.0:
resolution: {integrity: sha512-d8JCoLUG8XlgfaE/wB7P58ok6ddiGGjChm6dVsL3W2702ibgFjOx+NtFHrLKCpkSuiaR2/gevvaQsIFwkVNNiA==}
@@ -2412,6 +2642,16 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ browserslist@4.28.2:
+ resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
@@ -2420,6 +2660,9 @@ packages:
engines: {node: '>= 0.4.0'}
hasBin: true
+ buffer-crc32@0.2.13:
+ resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
+
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -2483,6 +2726,12 @@ packages:
caniuse-lite@1.0.30001726:
resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==}
+ caniuse-lite@1.0.30001769:
+ resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
+
+ caniuse-lite@1.0.30001787:
+ resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
+
canvas-exif-orientation@0.4.0:
resolution: {integrity: sha512-1NjYRG+44oKnY5Ou6NtaRoHchLHYlIzxfzTNBAToTiWOO7BkCW4ays709sYIdD+Wg6DReDAAAcHzfrMgZjyiRg==}
@@ -2512,11 +2761,6 @@ packages:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
- check-node-version@4.2.1:
- resolution: {integrity: sha512-YYmFYHV/X7kSJhuN/QYHUu998n/TRuDe8UenM3+m5NrkiH670lb9ILqHIvBencvJc4SDh+XcbXMR4b+TtubJiw==}
- engines: {node: '>=8.3.0'}
- hasBin: true
-
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@@ -2525,6 +2769,10 @@ packages:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
chrome-trace-event@1.0.4:
resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==}
engines: {node: '>=6.0'}
@@ -2533,8 +2781,8 @@ packages:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
- ci-info@4.3.0:
- resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==}
+ ci-info@4.4.0:
+ resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
engines: {node: '>=8'}
circular-dependency-plugin@5.2.2:
@@ -2543,8 +2791,8 @@ packages:
peerDependencies:
webpack: '>=4.0.1'
- cjs-module-lexer@1.4.3:
- resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+ cjs-module-lexer@2.2.0:
+ resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==}
clean-stack@2.2.0:
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
@@ -2570,11 +2818,8 @@ packages:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
- codemirror@5.58.2:
- resolution: {integrity: sha512-K/hOh24cCwRutd1Mk3uLtjWzNISOkm4fvXiMO7LucCrqbh6aJDdtqUziim3MZUI6wOY0rvY1SlL1Ork01uMy6w==}
-
- collect-v8-coverage@1.0.2:
- resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==}
+ collect-v8-coverage@1.0.3:
+ resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==}
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@@ -2614,6 +2859,10 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ command-exists-promise@2.0.2:
+ resolution: {integrity: sha512-T6PB6vdFrwnHXg/I0kivM3DqaCGZLjjYSOe0a5WgFKcz1sOnmOeIjnhQPXVXX3QjVbLyTJ85lJkX6lUpukTzaA==}
+ engines: {node: '>=6'}
+
commander@10.0.1:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
@@ -2626,6 +2875,10 @@ packages:
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
engines: {node: '>=18'}
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -2633,20 +2886,10 @@ packages:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
- commander@9.5.0:
- resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
- engines: {node: ^12.20.0 || >=14}
-
- common-path-prefix@3.0.0:
- resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
-
common-tags@1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
engines: {node: '>=4.0.0'}
- commondir@1.0.1:
- resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
-
complex-esm@2.1.1-esm1:
resolution: {integrity: sha512-IShBEWHILB9s7MnfyevqNGxV0A1cfcSnewL/4uPFiSxkcQL4Mm3FxJ0pXMtCXuWLjYz3lRRyk6OfkeDZcjD6nw==}
engines: {node: '>=16.14.2', npm: '>=8.5.0'}
@@ -2846,10 +3089,6 @@ packages:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
- content-disposition@1.0.0:
- resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
- engines: {node: '>= 0.6'}
-
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
@@ -2860,27 +3099,19 @@ packages:
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
- cookie-signature@1.2.2:
- resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
- engines: {node: '>=6.6.0'}
-
cookie@0.7.1:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
- cookie@0.7.2:
- resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
- engines: {node: '>= 0.6'}
-
- core-js-compat@3.43.0:
- resolution: {integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==}
+ core-js-compat@3.49.0:
+ resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==}
core-js@2.6.12:
resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
- core-js@3.44.0:
- resolution: {integrity: sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==}
+ core-js@3.47.0:
+ resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==}
core-util-is@1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
@@ -2911,11 +3142,6 @@ packages:
engines: {node: '>=0.8'}
hasBin: true
- create-jest@29.7.0:
- resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- hasBin: true
-
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
@@ -2962,8 +3188,8 @@ packages:
webpack:
optional: true
- css-minimizer-webpack-plugin@7.0.2:
- resolution: {integrity: sha512-nBRWZtI77PBZQgcXMNqiIXVshiQOVLGSf2qX/WZfG8IQfMbeHUMXaBWQmiiSTmPJUflQxHjZjzAmuyO7tpL2Jg==}
+ css-minimizer-webpack-plugin@7.0.4:
+ resolution: {integrity: sha512-2iACis+P8qdLj1tHcShtztkGhCNIRUajJj7iX0IM9a5FA0wXGwjV8Nf6+HsBjBfb4LO8TTAVoetBbM54V6f3+Q==}
engines: {node: '>= 18.12.0'}
peerDependencies:
'@parcel/css': '*'
@@ -3042,21 +3268,18 @@ packages:
cssom@0.3.8:
resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
- cssom@0.5.0:
- resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==}
-
cssstyle@0.2.37:
resolution: {integrity: sha512-FUpKc+1FNBsHUr9IsfSGCovr8VuGOiiuzlgCyppKBjJi2jYTOFLN3oiiNRMIvYqbFzF38mqKj4BgcevzU5/kIA==}
- cssstyle@2.3.0:
- resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==}
- engines: {node: '>=8'}
+ cssstyle@4.6.0:
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
- csv-parse@5.6.0:
- resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==}
+ csv-parse@6.2.1:
+ resolution: {integrity: sha512-LRLMV+UCyfMokp8Wb411duBf1gaBKJfOfBWU9eHMJ+b+cJYZsNu3AFmjJf3+yPGd59Exz1TsMjaSFyxnYB9+IQ==}
csv-writer@1.6.0:
resolution: {integrity: sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==}
@@ -3069,9 +3292,9 @@ packages:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
engines: {node: '>=0.10'}
- data-urls@3.0.2:
- resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
- engines: {node: '>=12'}
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
@@ -3120,6 +3343,15 @@ packages:
supports-color:
optional: true
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
decamelize-keys@1.1.1:
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
engines: {node: '>=0.10.0'}
@@ -3132,15 +3364,15 @@ packages:
resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==}
engines: {node: '>=10'}
- decimal.js@10.5.0:
- resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==}
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'}
- dedent@1.6.0:
- resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==}
+ dedent@1.7.2:
+ resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==}
peerDependencies:
babel-plugin-macros: ^3.1.0
peerDependenciesMeta:
@@ -3227,10 +3459,6 @@ packages:
diacritics@1.3.0:
resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==}
- diff-sequences@29.6.3:
- resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -3262,11 +3490,6 @@ packages:
domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
- domexception@4.0.0:
- resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==}
- engines: {node: '>=12'}
- deprecated: Use your platform's native DOMException instead
-
domhandler@5.0.3:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
@@ -3304,6 +3527,12 @@ packages:
electron-to-chromium@1.5.178:
resolution: {integrity: sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==}
+ electron-to-chromium@1.5.286:
+ resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
+
+ electron-to-chromium@1.5.334:
+ resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==}
+
emittery@0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
@@ -3333,13 +3562,13 @@ packages:
resolution: {integrity: sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw==}
engines: {node: '>=0.6'}
- enhanced-resolve@5.18.2:
- resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
+ enhanced-resolve@5.19.0:
+ resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==}
engines: {node: '>=10.13.0'}
- enquirer@2.4.1:
- resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
- engines: {node: '>=8.6'}
+ enhanced-resolve@5.20.1:
+ resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
+ engines: {node: '>=10.13.0'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
@@ -3364,13 +3593,17 @@ packages:
err-code@2.0.3:
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
- error-ex@1.3.2:
- resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'}
+ es-abstract@1.24.1:
+ resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ engines: {node: '>= 0.4'}
+
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -3382,8 +3615,8 @@ packages:
es-get-iterator@1.1.3:
resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
- es-module-lexer@1.7.0:
- resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+ es-module-lexer@2.0.0:
+ resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
@@ -3439,11 +3672,6 @@ packages:
engines: {node: '>=4.0'}
hasBin: true
- escodegen@2.1.0:
- resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
- engines: {node: '>=6.0'}
- hasBin: true
-
eslint-config-prettier@10.1.8:
resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
hasBin: true
@@ -3514,6 +3742,19 @@ packages:
jest:
optional: true
+ eslint-plugin-jest@29.1.0:
+ resolution: {integrity: sha512-LabxXbASXVjguqL+kBHTPMf3gUeSqwH4fsrEyHTY/MCs42I/p9+ctg09SJpYiD8eGaIsP6GwYr5xW6xWS9XgZg==}
+ engines: {node: ^20.12.0 || ^22.0.0 || >=24.0.0}
+ peerDependencies:
+ '@typescript-eslint/eslint-plugin': ^8.0.0
+ eslint: ^8.57.0 || ^9.0.0
+ jest: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/eslint-plugin':
+ optional: true
+ jest:
+ optional: true
+
eslint-plugin-kolibri@0.18.0:
resolution: {integrity: sha512-gAWycVvKBxc+uSCckt7luKxoPLFZ0yK9623hAeZhyn5CE47ACBLCUMGiYg34LtxO46AMJGP6ZS6Y02X6zCW9FQ==}
engines: {node: '>=0.10.0'}
@@ -3532,18 +3773,6 @@ packages:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- eslint-utils@2.1.0:
- resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
- engines: {node: '>=6'}
-
- eslint-visitor-keys@1.3.0:
- resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}
- engines: {node: '>=4'}
-
- eslint-visitor-keys@2.1.0:
- resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
- engines: {node: '>=10'}
-
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3552,12 +3781,6 @@ packages:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- eslint@7.32.0:
- resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==}
- engines: {node: ^10.12.0 || >=12.0.0}
- deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
- hasBin: true
-
eslint@8.57.1:
resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3568,10 +3791,6 @@ packages:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
- espree@7.3.1:
- resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==}
- engines: {node: ^10.12.0 || >=12.0.0}
-
espree@9.6.1:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3632,22 +3851,18 @@ packages:
exif-parser@0.1.12:
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
- exit@0.1.2:
- resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
+ exit-x@0.2.2:
+ resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==}
engines: {node: '>= 0.8.0'}
- expect@29.7.0:
- resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ expect@30.3.0:
+ resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
express@4.21.2:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'}
- express@5.1.0:
- resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
- engines: {node: '>= 18'}
-
ext@1.7.0:
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
@@ -3666,8 +3881,8 @@ packages:
resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
engines: {'0': node >=0.6.0}
- fake-indexeddb@5.0.2:
- resolution: {integrity: sha512-cB507r5T3D55DfclY01GLkninZLfU7HXV/mhVRTnTRm5k2u+fY7Fof2dBkr80p5t7G7dlA/G5dI87QiMdPpMCQ==}
+ fake-indexeddb@6.2.5:
+ resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==}
engines: {node: '>=18'}
fast-deep-equal@3.1.3:
@@ -3683,8 +3898,8 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
- fast-uri@3.0.6:
- resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
fastest-levenshtein@1.0.16:
resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
@@ -3738,29 +3953,13 @@ packages:
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
engines: {node: '>= 0.8'}
- finalhandler@2.1.0:
- resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==}
- engines: {node: '>= 0.8'}
-
find-babel-config@1.2.2:
resolution: {integrity: sha512-oK59njMyw2y3yxto1BCfVK7MQp/OYf4FleHu0RgosH3riFJ1aOuo/7naLDLAObfrgn3ueFhw5sAT/cp0QuJI3Q==}
engines: {node: '>=4.0.0'}
- find-cache-dir@2.1.0:
- resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==}
- engines: {node: '>=6'}
-
- find-cache-dir@4.0.0:
- resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==}
- engines: {node: '>=14.16'}
-
find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
- find-up@3.0.0:
- resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
- engines: {node: '>=6'}
-
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -3769,10 +3968,6 @@ packages:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
- find-up@6.3.0:
- resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
flat-cache@3.2.0:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -3784,15 +3979,11 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
- flow-parser@0.274.2:
- resolution: {integrity: sha512-kCjoA1h5j+Ttu/9fekY9XzeKPG8SvNtxigiCkezmDIOlcKr+d9LysczrPylEeSYINE3sLlX45W5vT2CroD6sWA==}
- engines: {node: '>=0.4.0'}
-
flush-promises@1.0.2:
resolution: {integrity: sha512-G0sYfLQERwKz4+4iOZYQEZVpOt9zQrlItIxQAAYAWpfby3gbHrx0osCHz5RLl/XoXevXk0xoN4hDFky/VV9TrA==}
- follow-redirects@1.15.9:
- resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ follow-redirects@1.15.11:
+ resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
@@ -3800,9 +3991,6 @@ packages:
debug:
optional: true
- fontfaceobserver@2.3.0:
- resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==}
-
for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
@@ -3818,8 +4006,8 @@ packages:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
- form-data@4.0.4:
- resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
+ form-data@4.0.5:
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
forwarded@0.2.0:
@@ -3829,6 +4017,9 @@ packages:
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+ fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
frame-throttle@3.0.0:
resolution: {integrity: sha512-ENBi8VP95xoHf8JpxPwEaCdZneOrtxl0U2H5p2oS896u3NzriR9kRcylvJ+FcCW3qZfZFJ5TDeOT5LbENndBnQ==}
@@ -3836,10 +4027,6 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
- fresh@2.0.0:
- resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
- engines: {node: '>= 0.8'}
-
fs-extra@9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@@ -3863,9 +4050,6 @@ packages:
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
engines: {node: '>= 0.4'}
- functional-red-black-tree@1.0.1:
- resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==}
-
functions-have-names@1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
@@ -3938,20 +4122,32 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
+ glob@10.5.0:
+ resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
+ glob@11.1.0:
+ resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
+ engines: {node: 20 || >=22}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.1.7:
resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-modules@2.0.0:
resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
@@ -4077,9 +4273,9 @@ packages:
hpack.js@2.1.6:
resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==}
- html-encoding-sniffer@3.0.0:
- resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
- engines: {node: '>=12'}
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
html-entities@2.6.0:
resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==}
@@ -4123,6 +4319,10 @@ packages:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
http-proxy-middleware@2.0.9:
resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==}
engines: {node: '>=12.0.0'}
@@ -4144,6 +4344,10 @@ packages:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -4186,10 +4390,6 @@ packages:
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
- ignore@4.0.6:
- resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
- engines: {node: '>= 4'}
-
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -4245,9 +4445,9 @@ packages:
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
- ini@5.0.0:
- resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==}
- engines: {node: ^18.17.0 || >=20.5.0}
+ ini@6.0.0:
+ resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==}
+ engines: {node: ^20.17.0 || >=22.9.0}
inline-style-prefixer@4.0.2:
resolution: {integrity: sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg==}
@@ -4440,9 +4640,6 @@ packages:
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
- is-promise@4.0.0:
- resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
-
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@@ -4498,9 +4695,6 @@ packages:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
engines: {node: '>=16'}
- isarray@0.0.1:
- resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
-
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -4536,33 +4730,37 @@ packages:
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
engines: {node: '>=10'}
- istanbul-lib-source-maps@4.0.1:
- resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+ istanbul-lib-source-maps@5.0.6:
+ resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
engines: {node: '>=10'}
- istanbul-reports@3.1.7:
- resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
+ istanbul-reports@3.2.0:
+ resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
engines: {node: '>=8'}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
- jake@10.9.2:
- resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
+ jackspeak@4.2.3:
+ resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
+ engines: {node: 20 || >=22}
+
+ jake@10.9.4:
+ resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
engines: {node: '>=10'}
hasBin: true
- jest-changed-files@29.7.0:
- resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-changed-files@30.3.0:
+ resolution: {integrity: sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-circus@29.7.0:
- resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-circus@30.3.0:
+ resolution: {integrity: sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-cli@29.7.0:
- resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-cli@30.3.0:
+ resolution: {integrity: sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
@@ -4570,70 +4768,69 @@ packages:
node-notifier:
optional: true
- jest-config@29.7.0:
- resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-config@30.3.0:
+ resolution: {integrity: sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
peerDependencies:
'@types/node': '*'
+ esbuild-register: '>=3.4.0'
ts-node: '>=9.0.0'
peerDependenciesMeta:
'@types/node':
optional: true
+ esbuild-register:
+ optional: true
ts-node:
optional: true
- jest-diff@29.7.0:
- resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-docblock@29.7.0:
- resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-diff@30.3.0:
+ resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-each@29.7.0:
- resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-docblock@30.2.0:
+ resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-each@30.0.5:
- resolution: {integrity: sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==}
+ jest-each@30.3.0:
+ resolution: {integrity: sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-environment-jsdom@29.7.0:
- resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-environment-jsdom@30.3.0:
+ resolution: {integrity: sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
peerDependencies:
- canvas: ^2.5.0
+ canvas: ^3.0.0
peerDependenciesMeta:
canvas:
optional: true
- jest-environment-node@29.7.0:
- resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- jest-get-type@29.6.3:
- resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-environment-node@30.3.0:
+ resolution: {integrity: sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jest-haste-map@29.7.0:
resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- jest-leak-detector@29.7.0:
- resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-haste-map@30.3.0:
+ resolution: {integrity: sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-matcher-utils@29.7.0:
- resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-leak-detector@30.3.0:
+ resolution: {integrity: sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-message-util@29.7.0:
- resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-matcher-utils@30.3.0:
+ resolution: {integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-mock@29.7.0:
- resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-message-util@30.3.0:
+ resolution: {integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest-mock@30.3.0:
+ resolution: {integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jest-pnp-resolver@1.2.3:
resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==}
@@ -4652,44 +4849,44 @@ packages:
resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-resolve-dependencies@29.7.0:
- resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-resolve-dependencies@30.3.0:
+ resolution: {integrity: sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-resolve@29.7.0:
- resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-resolve@30.3.0:
+ resolution: {integrity: sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-runner@29.7.0:
- resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-runner@30.3.0:
+ resolution: {integrity: sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-runtime@29.7.0:
- resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-runtime@30.3.0:
+ resolution: {integrity: sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jest-serializer-vue@3.1.0:
resolution: {integrity: sha512-vXz9/3IgBbLhsaVANYLG4ROCQd+Wg3qbB6ICofzFL+fbhSFPlqb0/MMGXcueVsjaovdWlYiRaLQLpdi1PTcoRQ==}
- jest-snapshot@29.7.0:
- resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-snapshot@30.3.0:
+ resolution: {integrity: sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jest-util@29.7.0:
resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- jest-util@30.0.5:
- resolution: {integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==}
+ jest-util@30.3.0:
+ resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-validate@29.7.0:
- resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-validate@30.3.0:
+ resolution: {integrity: sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
- jest-watcher@29.7.0:
- resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-watcher@30.3.0:
+ resolution: {integrity: sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
jest-worker@27.5.1:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
@@ -4699,9 +4896,13 @@ packages:
resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- jest@29.7.0:
- resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ jest-worker@30.3.0:
+ resolution: {integrity: sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
+
+ jest@30.3.0:
+ resolution: {integrity: sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==}
+ engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
@@ -4713,13 +4914,13 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
jpeg-js@0.4.4:
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
- jquery@2.2.4:
- resolution: {integrity: sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==}
- deprecated: This version is deprecated. Please upgrade to the latest version or find support at https://www.herodevs.com/support/jquery-nes.
-
js-base64@2.6.4:
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
@@ -4755,21 +4956,11 @@ packages:
jsbn@1.1.0:
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
- jscodeshift@17.3.0:
- resolution: {integrity: sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==}
- engines: {node: '>=16'}
- hasBin: true
- peerDependencies:
- '@babel/preset-env': ^7.1.6
- peerDependenciesMeta:
- '@babel/preset-env':
- optional: true
-
- jsdom@20.0.3:
- resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==}
- engines: {node: '>=14'}
+ jsdom@26.1.0:
+ resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+ engines: {node: '>=18'}
peerDependencies:
- canvas: ^2.5.0
+ canvas: ^3.0.0
peerDependenciesMeta:
canvas:
optional: true
@@ -4777,11 +4968,6 @@ packages:
jsdom@8.5.0:
resolution: {integrity: sha512-rvWfcn2O8SrXPaX5fTYIfPVwvnbU8DnZkjAXK305wfP67csyaJBhgg0F2aU6imqJ+lZmj9EmrBAXy6rWHf2/9Q==}
- jsesc@3.0.2:
- resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
- engines: {node: '>=6'}
- hasBin: true
-
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -4820,8 +5006,8 @@ packages:
engines: {node: '>=6'}
hasBin: true
- jsonfile@6.1.0:
- resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
jsonpointer@5.0.1:
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
@@ -4850,50 +5036,55 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
- kleur@3.0.3:
- resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
- engines: {node: '>=6'}
-
known-css-properties@0.29.0:
resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==}
known-css-properties@0.36.0:
resolution: {integrity: sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==}
+ kolibri-build@1.0.0:
+ resolution: {integrity: sha512-CjMFxPwpMKWzc70hqal11HBP0s92qtTkVAh6zXO4N6O2vnV9t4grOYPuXuGGQyxjAUglnGFo5zDOXq20lXBaYw==}
+ engines: {node: '>= 20.19.0', npm: '>= 8'}
+ hasBin: true
+ peerDependencies:
+ vue-template-compiler: 2.7.16
+ webpack: ^5.102.0
+
kolibri-constants@0.2.12:
resolution: {integrity: sha512-ApVc/KLwEaDJohqKhQTdao4UdWmoyq2pQ5lk8ra+1rDpJvsFWsAOGVC4RTv4YEDlAYJzj2/QZlJQ91u5yUURSQ==}
- kolibri-constants@0.2.9:
- resolution: {integrity: sha512-frLKQPA5bSQczVEh/lnfHeQcK9i4MvGzgl+KMqvP+ixh+VJxXjlpSG+k119ajQqgq9k+nJb/IY4HbiLTjBM6oQ==}
-
- kolibri-design-system@5.0.1:
- resolution: {integrity: sha512-oz5gEFUj7NYZbrqr89gPjSfRFVuuilSqILA4bbWqLWwkI9ay/uVMdsh8cY2Xajhtzccjwqa1c/NxUETr5kMfHQ==}
-
- kolibri-design-system@5.3.0:
- resolution: {integrity: sha512-vqlqNaI200yWZOq6gwADZf79e+7Hq3wKz7l2idwNLt6UsihT/dRlodBN68aU4w5Wq3zqK/9bctnDLCcXVJx/rw==}
+ kolibri-design-system@5.6.0:
+ resolution: {integrity: sha512-lKCQVnnjguUQUIzQ5rKkUyNRWOIhY4cOOtPmuW0H3gz+hv73wPvBjbI2u9kQqA+oFs/C2MsL60roiMMbQDmt4A==}
kolibri-format@1.0.1:
resolution: {integrity: sha512-yGQpsJkBAzmRueAq6MG1UOuDl9pbhEtMWNxq9ObG5pPVkG8uhWJAS1L71GCuNAeaV1XG2IWo2565Ov4yXnudeA==}
engines: {node: 18.x, npm: '>= 8'}
hasBin: true
- kolibri-logging@1.0.0:
- resolution: {integrity: sha512-XsP2JzrzZWwnN5bbCZ12hYTrOuuREyFqOU3EZZP8tlVI2p4jO7/bvBo7xRs1gdAmU/8DSFY6tU1NRNRsBBu4aA==}
+ kolibri-glob@1.0.0:
+ resolution: {integrity: sha512-R87vDJ9Ve+poixrw/aUgz97XZ+NJ0Qkh3rq9nHkvaiA8PEMrcBqtAomtWGtRivVi6ph2/26Rn9D89OlPqMdE6Q==}
+ engines: {node: '>= 20.19.0', npm: '>= 8'}
- kolibri-tools@0.18.2:
- resolution: {integrity: sha512-7za5wY+LIzzOIZu9f2bMUzQaXWRf1Hrmlw9uCqvaJjKPQ0dZZ9cmwuU/RO8aXAHVzcfKUOcUDBr/akV5s5Wljg==}
- engines: {node: '>= 18.20.0', npm: '>= 8'}
+ kolibri-i18n@1.0.0:
+ resolution: {integrity: sha512-UwQ7GyoAe5JX73Rau2Y4JlL5DF9b0GbxLZUlwCVjvexf6kgVqq59V6889ScJnMr/Kb83U8+Y+Dh+6GPd57w9qw==}
+ engines: {node: '>= 20.19.0', npm: '>= 8'}
hasBin: true
+ peerDependencies:
+ vue-template-compiler: 2.7.16
+ webpack: ^5.102.0
- kolibri@0.18.0:
- resolution: {integrity: sha512-XlE5zXAemq2HvH7lOFkOYsLKX7Wci2RUEgYxY1C8Hmo8bqlNYFkiKWGPV2aaiSpQMJ2xFgREQhJvTLh0i4mZ+Q==}
+ kolibri-logging@1.0.0:
+ resolution: {integrity: sha512-XsP2JzrzZWwnN5bbCZ12hYTrOuuREyFqOU3EZZP8tlVI2p4jO7/bvBo7xRs1gdAmU/8DSFY6tU1NRNRsBBu4aA==}
- launch-editor-middleware@2.10.0:
- resolution: {integrity: sha512-RzZu7MeVlE3p1H6Sadc2BhuDGAj7bkeDCBpNq/zSENP4ohJGhso00k5+iYaRwKshIpiOAhMmimce+5D389xmSg==}
+ launch-editor-middleware@2.13.2:
+ resolution: {integrity: sha512-kI7VqA9g6mhoeQ6YdNgd+gKLaeuWHAUR8oDM8vFtt924wG8HbI2XO0n/hSX2ML4hcJbTgUihuPHfpnPjIKMdJg==}
launch-editor@2.10.0:
resolution: {integrity: sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==}
+ launch-editor@2.13.2:
+ resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==}
+
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'}
@@ -4922,15 +5113,15 @@ packages:
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
- linkifyjs@4.3.1:
- resolution: {integrity: sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg==}
+ linkifyjs@4.3.2:
+ resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
load-json-file@4.0.0:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
engines: {node: '>=4'}
- loader-runner@4.3.0:
- resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
+ loader-runner@4.3.1:
+ resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
engines: {node: '>=6.11.5'}
loader-utils@1.4.2:
@@ -4944,10 +5135,6 @@ packages:
localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
- locate-path@3.0.0:
- resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
- engines: {node: '>=6'}
-
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -4956,14 +5143,6 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
- locate-path@7.2.0:
- resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
- lockr@0.8.5:
- resolution: {integrity: sha512-PyWX+NYcJtk+12cARV5qaR0I2cfpHDplpOOI4KKoJtJufdnXo4sJPmfWhbZUAT5rCMgszzU0DovlSjEKp2u12A==}
- engines: {node: '>= 0.8.0'}
-
lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
@@ -4994,8 +5173,11 @@ packages:
lodash.without@4.4.0:
resolution: {integrity: sha512-M3MefBwfDhgKgINVuBJCO1YR3+gf6s9HNJsIiZ/Ru77Ws6uTb9eBuvrkpzO+9iLoAaRodGuq7tyrPCx+74QYGQ==}
- lodash@4.17.21:
- resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ lodash@4.17.23:
+ resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
+
+ lodash@4.18.1:
+ resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
loglevel@1.9.2:
resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==}
@@ -5011,6 +5193,10 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ lru-cache@11.3.3:
+ resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==}
+ engines: {node: 20 || >=22}
+
lru-cache@4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
@@ -5032,10 +5218,6 @@ packages:
magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
- make-dir@2.1.0:
- resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
- engines: {node: '>=6'}
-
make-dir@4.0.0:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
@@ -5059,13 +5241,15 @@ packages:
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
engines: {node: '>=8'}
- map-values@1.0.1:
- resolution: {integrity: sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==}
-
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
+ marked@16.1.1:
+ resolution: {integrity: sha512-ij/2lXfCRT71L6u0M29tJPhP0bM5shLL3u5BePhFwPELj2blMJ6GDtD7PfJhRLhJ/c2UwrK17ySVcDzy2YHjHQ==}
+ engines: {node: '>= 20'}
+ hasBin: true
+
marks-pane@1.0.9:
resolution: {integrity: sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg==}
@@ -5076,8 +5260,8 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
- mathlive@0.105.3:
- resolution: {integrity: sha512-eEnJIlRm1ga18ymY79di5Iuc161CzHs3PTXIg8WUHeEt/jpxFHs2CPVYRNxAzOo+5t4S7lA+HDretW4i5dsTmg==}
+ mathlive@0.108.2:
+ resolution: {integrity: sha512-GIZkfprGTxrbHckOvwo92ZmOOxdD018BHDzlrEwYUU+pzR5KabhqI1s43lxe/vqXdF5RLiQKgDcuk5jxEjhkYg==}
mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
@@ -5101,10 +5285,6 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
- media-typer@1.1.0:
- resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
- engines: {node: '>= 0.8'}
-
memfs@4.17.2:
resolution: {integrity: sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==}
engines: {node: '>= 4.0.0'}
@@ -5127,10 +5307,6 @@ packages:
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
- merge-descriptors@2.0.0:
- resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
- engines: {node: '>=18'}
-
merge-source-map@1.1.0:
resolution: {integrity: sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==}
@@ -5161,10 +5337,6 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
- mime-types@3.0.1:
- resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
- engines: {node: '>= 0.6'}
-
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@@ -5178,8 +5350,8 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
- mini-css-extract-plugin@2.9.2:
- resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==}
+ mini-css-extract-plugin@2.9.4:
+ resolution: {integrity: sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==}
engines: {node: '>= 12.13.0'}
peerDependencies:
webpack: ^5.0.0
@@ -5187,14 +5359,18 @@ packages:
minimalistic-assert@1.0.1:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
+ minimatch@10.2.0:
+ resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==}
+ engines: {node: 20 || >=22}
+
minimatch@3.0.8:
resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
- minimatch@5.1.6:
- resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ minimatch@5.1.9:
+ resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
engines: {node: '>=10'}
minimatch@9.0.1:
@@ -5205,6 +5381,10 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
+ minimatch@9.0.9:
+ resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
minimist-options@4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
@@ -5244,14 +5424,18 @@ packages:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
- minipass@7.1.2:
- resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
+ minizlib@3.1.0:
+ resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
+ engines: {node: '>= 18'}
+
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
@@ -5278,6 +5462,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ napi-postinstall@0.3.4:
+ resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -5289,10 +5478,6 @@ packages:
resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
engines: {node: '>= 0.6'}
- negotiator@1.0.0:
- resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
- engines: {node: '>= 0.6'}
-
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -5336,6 +5521,12 @@ packages:
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ node-releases@2.0.37:
+ resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
+
node-sass@9.0.0:
resolution: {integrity: sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==}
engines: {node: '>=16'}
@@ -5390,8 +5581,8 @@ packages:
nwmatcher@1.4.4:
resolution: {integrity: sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==}
- nwsapi@2.2.20:
- resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==}
+ nwsapi@2.2.23:
+ resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
oauth-sign@0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
@@ -5400,9 +5591,6 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
- object-filter@1.0.2:
- resolution: {integrity: sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==}
-
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
@@ -5431,8 +5619,9 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
- oblivious-set@1.1.1:
- resolution: {integrity: sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==}
+ oblivious-set@1.4.0:
+ resolution: {integrity: sha512-szyd0ou0T8nsAqHtprRcP3WidfsN1TnAR5yWXf2mFCEr5ek3LEOkT6EZ/92Xfs74HIdyhG5WkGxIssMU0jBaeg==}
+ engines: {node: '>=16'}
obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
@@ -5489,14 +5678,6 @@ packages:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
- p-limit@4.0.0:
- resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
- p-locate@3.0.0:
- resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
- engines: {node: '>=6'}
-
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
@@ -5505,10 +5686,6 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
- p-locate@6.0.0:
- resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
p-map@4.0.0:
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
engines: {node: '>=10'}
@@ -5571,10 +5748,6 @@ packages:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
- path-exists@5.0.0:
- resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
@@ -5594,16 +5767,13 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
+ path-scurry@2.0.1:
+ resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
+ engines: {node: 20 || >=22}
+
path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
- path-to-regexp@1.9.0:
- resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==}
-
- path-to-regexp@8.2.0:
- resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
- engines: {node: '>=16'}
-
path-type@3.0.0:
resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
engines: {node: '>=4'}
@@ -5627,6 +5797,9 @@ packages:
resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==}
engines: {node: '>=8'}
+ pend@1.2.0:
+ resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
+
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
@@ -5640,8 +5813,12 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
- picomatch@4.0.3:
- resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
pidtree@0.3.1:
@@ -5653,10 +5830,6 @@ packages:
resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
engines: {node: '>=4'}
- pify@4.0.1:
- resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
- engines: {node: '>=6'}
-
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
@@ -5665,18 +5838,10 @@ packages:
resolution: {integrity: sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==}
hasBin: true
- pkg-dir@3.0.0:
- resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
- engines: {node: '>=6'}
-
pkg-dir@4.2.0:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
- pkg-dir@7.0.0:
- resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==}
- engines: {node: '>=14.16'}
-
pngjs@3.4.0:
resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==}
engines: {node: '>=4.0.0'}
@@ -5758,6 +5923,19 @@ packages:
webpack:
optional: true
+ postcss-loader@8.2.1:
+ resolution: {integrity: sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0
+ postcss: ^7.0.0 || ^8.0.1
+ webpack: ^5.0.0
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+ webpack:
+ optional: true
+
postcss-media-query-parser@0.2.3:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
@@ -5974,12 +6152,8 @@ packages:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
- pretty-format@29.7.0:
- resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
- pretty-format@30.0.5:
- resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==}
+ pretty-format@30.3.0:
+ resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
pretty@2.0.0:
@@ -5993,10 +6167,6 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
- progress@2.0.3:
- resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
- engines: {node: '>=0.4.0'}
-
promise-inflight@1.0.1:
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
peerDependencies:
@@ -6009,10 +6179,6 @@ packages:
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
engines: {node: '>=10'}
- prompts@2.4.2:
- resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
- engines: {node: '>= 6'}
-
prosemirror-changeset@2.3.1:
resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
@@ -6025,14 +6191,14 @@ packages:
prosemirror-dropcursor@1.8.2:
resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
- prosemirror-gapcursor@1.3.2:
- resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==}
+ prosemirror-gapcursor@1.4.0:
+ resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==}
- prosemirror-history@1.4.1:
- resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==}
+ prosemirror-history@1.5.0:
+ resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==}
- prosemirror-inputrules@1.5.0:
- resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==}
+ prosemirror-inputrules@1.5.1:
+ resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
prosemirror-keymap@1.2.3:
resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
@@ -6043,8 +6209,8 @@ packages:
prosemirror-menu@1.2.5:
resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
- prosemirror-model@1.25.1:
- resolution: {integrity: sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==}
+ prosemirror-model@1.25.4:
+ resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
prosemirror-schema-basic@1.2.4:
resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
@@ -6052,11 +6218,11 @@ packages:
prosemirror-schema-list@1.5.1:
resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
- prosemirror-state@1.4.3:
- resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==}
+ prosemirror-state@1.4.4:
+ resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
- prosemirror-tables@1.7.1:
- resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==}
+ prosemirror-tables@1.8.3:
+ resolution: {integrity: sha512-wbqCR/RlRPRe41a4LFtmhKElzBEfBTdtAYWNIGHM6X2e24NN/MTNUKyXjjphfAfdQce37Kh/5yf765mLPYDe7Q==}
prosemirror-trailing-node@3.0.0:
resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
@@ -6065,11 +6231,11 @@ packages:
prosemirror-state: ^1.4.2
prosemirror-view: ^1.33.8
- prosemirror-transform@1.10.4:
- resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==}
+ prosemirror-transform@1.10.5:
+ resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==}
- prosemirror-view@1.40.0:
- resolution: {integrity: sha512-2G3svX0Cr1sJjkD/DYWSe3cfV5VPVTBOxI9XQEGWJDFEpsZb/gh4MV29ctv+OJx2RFX4BLt09i+6zaGM/ldkCw==}
+ prosemirror-view@1.41.4:
+ resolution: {integrity: sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==}
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -6078,8 +6244,9 @@ packages:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
- proxy-from-env@1.1.0:
- resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ proxy-from-env@2.1.0:
+ resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
+ engines: {node: '>=10'}
pseudomap@1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
@@ -6095,8 +6262,8 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
- pure-rand@6.1.0:
- resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
+ pure-rand@7.0.1:
+ resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==}
purecss@2.2.0:
resolution: {integrity: sha512-jEPrAALLgE+InDARWdPDt0AkZ1Bi0yXxHj4BOwWImq06sGIDe5CagPyS6Z9WGyEgMuZonrrhinInJ80nAHTIUA==}
@@ -6105,20 +6272,14 @@ packages:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
- qs@6.14.0:
- resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
+ qs@6.14.2:
+ resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
engines: {node: '>=0.6'}
- qs@6.5.3:
- resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
+ qs@6.5.5:
+ resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==}
engines: {node: '>=0.6'}
- query-ast@1.0.5:
- resolution: {integrity: sha512-JK+1ma4YDuLjvKKcz9JZ70G+CM9qEOs/l1cZzstMMfwKUabTJ9sud5jvDGrUNuv03yKUgs82bLkHXJkDyhRmBw==}
-
- querystringify@2.2.0:
- resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
-
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -6153,10 +6314,6 @@ packages:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
- raw-body@3.0.0:
- resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==}
- engines: {node: '>= 0.8'}
-
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
@@ -6202,10 +6359,6 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
- readline-sync@1.4.10:
- resolution: {integrity: sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==}
- engines: {node: '>= 0.8.0'}
-
recast@0.23.11:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
@@ -6226,8 +6379,8 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
- regenerate-unicode-properties@10.2.0:
- resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==}
+ regenerate-unicode-properties@10.2.2:
+ resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==}
engines: {node: '>=4'}
regenerate@1.4.2:
@@ -6246,19 +6399,15 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
- regexpp@3.2.0:
- resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
- engines: {node: '>=8'}
-
- regexpu-core@6.2.0:
- resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==}
+ regexpu-core@6.4.0:
+ resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==}
engines: {node: '>=4'}
regjsgen@0.8.0:
resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==}
- regjsparser@0.12.0:
- resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
+ regjsparser@0.13.1:
+ resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==}
hasBin: true
request@2.88.2:
@@ -6297,15 +6446,16 @@ packages:
resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
deprecated: https://github.com/lydell/resolve-url#deprecated
- resolve.exports@2.0.3:
- resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
- engines: {node: '>=10'}
-
resolve@1.22.10:
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
engines: {node: '>= 0.4'}
hasBin: true
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
resolve@2.0.0-next.5:
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
hasBin: true
@@ -6322,9 +6472,6 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- rewire@6.0.0:
- resolution: {integrity: sha512-7sZdz5dptqBCapJYocw9EcppLU62KMEqDLIILJnNET2iqzXHaQfaVP5SOJ06XvjX+dNIDJbzjw0ZWzrgDhtjYg==}
-
rgbcolor@1.0.1:
resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
engines: {node: '>= 0.8.15'}
@@ -6347,9 +6494,8 @@ packages:
rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
- router@2.2.0:
- resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
- engines: {node: '>= 18'}
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
rtlcss@4.3.0:
resolution: {integrity: sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==}
@@ -6410,8 +6556,26 @@ packages:
webpack:
optional: true
- sax@1.3.0:
- resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
+ sass-loader@16.0.7:
+ resolution: {integrity: sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@rspack/core': 0.x || ^1.0.0 || ^2.0.0-0
+ node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+ sass: ^1.3.0
+ sass-embedded: '*'
+ webpack: ^5.0.0
+ peerDependenciesMeta:
+ '@rspack/core':
+ optional: true
+ node-sass:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ webpack:
+ optional: true
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
@@ -6428,6 +6592,10 @@ packages:
resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==}
engines: {node: '>= 10.13.0'}
+ schema-utils@4.3.3:
+ resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==}
+ engines: {node: '>= 10.13.0'}
+
scss-tokenizer@0.4.3:
resolution: {integrity: sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==}
@@ -6451,14 +6619,15 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
send@0.19.0:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
- send@1.2.0:
- resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
- engines: {node: '>= 18'}
-
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
@@ -6470,10 +6639,6 @@ packages:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
- serve-static@2.2.0:
- resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==}
- engines: {node: '>= 18'}
-
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
@@ -6522,9 +6687,9 @@ packages:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
- showdown@2.1.0:
- resolution: {integrity: sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==}
- hasBin: true
+ shelljs@0.10.0:
+ resolution: {integrity: sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==}
+ engines: {node: '>=18'}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
@@ -6552,9 +6717,6 @@ packages:
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
- sisteransi@1.0.5:
- resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
-
slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@@ -6567,8 +6729,9 @@ packages:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
- smob@1.5.0:
- resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
+ smob@1.6.1:
+ resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==}
+ engines: {node: '>=20.0.0'}
sockjs@0.3.24:
resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==}
@@ -6606,6 +6769,10 @@ packages:
resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}
deprecated: See https://github.com/lydell/source-map-url#deprecated
+ source-map@0.5.6:
+ resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==}
+ engines: {node: '>=0.10.0'}
+
source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
@@ -6614,9 +6781,9 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
- source-map@0.7.4:
- resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
- engines: {node: '>= 8'}
+ source-map@0.7.6:
+ resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+ engines: {node: '>= 12'}
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
@@ -6686,10 +6853,6 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
- statuses@2.0.2:
- resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
- engines: {node: '>= 0.8'}
-
stdout-stream@1.4.1:
resolution: {integrity: sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==}
@@ -6765,8 +6928,8 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
- strip-ansi@7.1.0:
- resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
strip-bom@3.0.0:
@@ -6901,8 +7064,8 @@ packages:
engines: {node: ^14.13.1 || >=16.0.0}
hasBin: true
- stylus-loader@8.1.1:
- resolution: {integrity: sha512-Ohe29p3gwJiu1kxq16P80g1qq0FxGtwQevKctLE4su8KUq+Ea06Q6lp7SpcJjaKNrWIuEZQGvESUPt8JpukKVw==}
+ stylus-loader@8.1.2:
+ resolution: {integrity: sha512-zr4ae9vIDs4zslsMtsCAoN3CLkhtUEikwNwL+AyGGtwlo+oRaX2su/6MVy/TbaSND/WIGx5hSTK23G58T8zVyQ==}
engines: {node: '>= 18.12.0'}
peerDependencies:
'@rspack/core': 0.x || 1.x
@@ -6914,8 +7077,9 @@ packages:
webpack:
optional: true
- stylus@0.63.0:
- resolution: {integrity: sha512-OMlgrTCPzE/ibtRMoeLVhOY0RcNuNWh0rhAVqeKnk/QwcuUKQbnqhZ1kg2vzD8VU/6h3FoPTq4RJPHgLBvX6Bw==}
+ stylus@0.64.0:
+ resolution: {integrity: sha512-ZIdT8eUv8tegmqy1tTIdJv9We2DumkNZFdCF5mz/Kpq3OcTaxSuCAYZge6HKK2CmNC02G1eJig2RV7XTw5hQrA==}
+ engines: {node: '>=16'}
hasBin: true
supports-color@2.0.0:
@@ -6953,6 +7117,10 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ synckit@0.11.12:
+ resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
table@6.9.0:
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
engines: {node: '>=10.0.0'}
@@ -6961,13 +7129,22 @@ packages:
resolution: {integrity: sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ==}
engines: {node: '>=0.6'}
- tapable@2.2.2:
- resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==}
+ tapable@2.2.3:
+ resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==}
+ engines: {node: '>=6'}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
+ deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+
+ tar@7.5.13:
+ resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
+ engines: {node: '>=18'}
temp-dir@2.0.0:
resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
@@ -6981,8 +7158,8 @@ packages:
resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==}
engines: {node: '>=10'}
- terser-webpack-plugin@5.3.14:
- resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
+ terser-webpack-plugin@5.3.16:
+ resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==}
engines: {node: '>= 10.13.0'}
peerDependencies:
'@swc/core': '*'
@@ -6997,8 +7174,8 @@ packages:
uglify-js:
optional: true
- terser@5.43.1:
- resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==}
+ terser@5.46.0:
+ resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==}
engines: {node: '>=10'}
hasBin: true
@@ -7033,12 +7210,12 @@ packages:
tippy.js@4.3.5:
resolution: {integrity: sha512-NDq3efte8nGK6BOJ1dDN1/WelAwfmh3UtIYXXck6+SxLzbIQNZE/cmRSnwScZ/FyiKdIcvFHvYUgqmoGx8CcyA==}
- tippy.js@6.3.7:
- resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
+ tldts-core@6.1.86:
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
- tmp@0.2.3:
- resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
- engines: {node: '>=14.14'}
+ tldts@6.1.86:
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@@ -7066,9 +7243,9 @@ packages:
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
engines: {node: '>=0.8'}
- tough-cookie@4.1.4:
- resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
- engines: {node: '>=6'}
+ tough-cookie@5.1.2:
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -7076,9 +7253,9 @@ packages:
tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
- tr46@3.0.0:
- resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
- engines: {node: '>=12'}
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
tree-dump@1.0.3:
resolution: {integrity: sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==}
@@ -7162,10 +7339,6 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
- type-is@2.0.1:
- resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
- engines: {node: '>= 0.6'}
-
type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
@@ -7190,10 +7363,6 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- ua-parser-js@1.0.40:
- resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==}
- hasBin: true
-
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
@@ -7204,6 +7373,9 @@ packages:
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+ undici-types@7.18.2:
+ resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
+
undici-types@7.8.0:
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
@@ -7215,12 +7387,12 @@ packages:
resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
engines: {node: '>=4'}
- unicode-match-property-value-ecmascript@2.2.0:
- resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==}
+ unicode-match-property-value-ecmascript@2.2.1:
+ resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==}
engines: {node: '>=4'}
- unicode-property-aliases-ecmascript@2.1.0:
- resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}
+ unicode-property-aliases-ecmascript@2.2.0:
+ resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==}
engines: {node: '>=4'}
unique-filename@1.1.1:
@@ -7241,10 +7413,6 @@ packages:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'}
- universalify@0.2.0:
- resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
- engines: {node: '>= 4.0.0'}
-
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@@ -7256,6 +7424,9 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
+ unrs-resolver@1.11.1:
+ resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
+
upath@1.2.0:
resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
engines: {node: '>=4'}
@@ -7266,6 +7437,12 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -7283,9 +7460,6 @@ packages:
file-loader:
optional: true
- url-parse@1.5.10:
- resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
-
utif2@4.1.0:
resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==}
@@ -7312,9 +7486,6 @@ packages:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
- v8-compile-cache@2.4.0:
- resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==}
-
v8-to-istanbul@9.3.0:
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
engines: {node: '>=10.12.0'}
@@ -7338,17 +7509,6 @@ packages:
version: 3.2.14
engines: {node: '>= 4.0.0', npm: '>= 3.0.0'}
- vue-demi@0.14.10:
- resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
- engines: {node: '>=12'}
- hasBin: true
- peerDependencies:
- '@vue/composition-api': ^1.0.0-rc.1
- vue: ^3.0.0-0 || ^2.6.0
- peerDependenciesMeta:
- '@vue/composition-api':
- optional: true
-
vue-eslint-parser@9.4.3:
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
engines: {node: ^14.17.0 || >=16.0.0}
@@ -7390,9 +7550,6 @@ packages:
vue-template-compiler:
optional: true
- vue-meta@2.4.0:
- resolution: {integrity: sha512-XEeZUmlVeODclAjCNpWDnjgw+t3WA6gdzs6ENoIAgwO1J1d5p1tezDhtteLUFwcaQaTtayRrsx7GL6oXp/m2Jw==}
-
vue-router@3.6.5:
resolution: {integrity: sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ==}
peerDependencies:
@@ -7436,15 +7593,15 @@ packages:
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
- w3c-xmlserializer@4.0.0:
- resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
- engines: {node: '>=14'}
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
- watchpack@2.4.4:
- resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==}
+ watchpack@2.5.1:
+ resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==}
engines: {node: '>=10.13.0'}
wbuf@1.7.3:
@@ -7482,6 +7639,20 @@ packages:
webpack-dev-server:
optional: true
+ webpack-cli@7.0.2:
+ resolution: {integrity: sha512-dB0R4T+C/8YuvM+fabdvil6QE44/ChDXikV5lOOkrUeCkW5hTJv2pGLE3keh+D5hjYw8icBaJkZzpFoaHV4T+g==}
+ engines: {node: '>=20.9.0'}
+ hasBin: true
+ peerDependencies:
+ webpack: ^5.101.0
+ webpack-bundle-analyzer: ^4.0.0 || ^5.0.0
+ webpack-dev-server: ^5.0.0
+ peerDependenciesMeta:
+ webpack-bundle-analyzer:
+ optional: true
+ webpack-dev-server:
+ optional: true
+
webpack-dev-middleware@7.4.2:
resolution: {integrity: sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==}
engines: {node: '>= 18.12.0'}
@@ -7515,8 +7686,8 @@ packages:
resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
engines: {node: '>=10.13.0'}
- webpack@5.99.9:
- resolution: {integrity: sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==}
+ webpack@5.104.1:
+ resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==}
engines: {node: '>=10.13.0'}
hasBin: true
peerDependencies:
@@ -7533,20 +7704,21 @@ packages:
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
engines: {node: '>=0.8.0'}
- whatwg-encoding@2.0.0:
- resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
- engines: {node: '>=12'}
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
- whatwg-mimetype@3.0.0:
- resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
- engines: {node: '>=12'}
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
- whatwg-url@11.0.0:
- resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
- engines: {node: '>=12'}
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
whatwg-url@2.0.1:
resolution: {integrity: sha512-sX+FT4N6iR0ZiqGqyDEKklyfMGR99zvxZD+LQ8IGae5uVGswQ7DOeLPB5KgJY8FzkwSzwqOXLQeVQvtOTSQU9Q==}
@@ -7573,6 +7745,10 @@ packages:
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
engines: {node: '>= 0.4'}
+ which-typed-array@1.1.20:
+ resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
+ engines: {node: '>= 0.4'}
+
which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
@@ -7592,61 +7768,73 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- workbox-background-sync@7.3.0:
- resolution: {integrity: sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg==}
+ workbox-background-sync@7.4.0:
+ resolution: {integrity: sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==}
- workbox-broadcast-update@7.3.0:
- resolution: {integrity: sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA==}
+ workbox-broadcast-update@7.4.0:
+ resolution: {integrity: sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==}
- workbox-build@7.3.0:
- resolution: {integrity: sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ==}
- engines: {node: '>=16.0.0'}
+ workbox-build@7.4.0:
+ resolution: {integrity: sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==}
+ engines: {node: '>=20.0.0'}
- workbox-cacheable-response@7.3.0:
- resolution: {integrity: sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA==}
+ workbox-cacheable-response@7.4.0:
+ resolution: {integrity: sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==}
workbox-core@7.3.0:
resolution: {integrity: sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==}
- workbox-expiration@7.3.0:
- resolution: {integrity: sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ==}
+ workbox-core@7.4.0:
+ resolution: {integrity: sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==}
+
+ workbox-expiration@7.4.0:
+ resolution: {integrity: sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==}
- workbox-google-analytics@7.3.0:
- resolution: {integrity: sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg==}
+ workbox-google-analytics@7.4.0:
+ resolution: {integrity: sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==}
- workbox-navigation-preload@7.3.0:
- resolution: {integrity: sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg==}
+ workbox-navigation-preload@7.4.0:
+ resolution: {integrity: sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==}
- workbox-precaching@7.3.0:
- resolution: {integrity: sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==}
+ workbox-precaching@7.4.0:
+ resolution: {integrity: sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==}
- workbox-range-requests@7.3.0:
- resolution: {integrity: sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ==}
+ workbox-range-requests@7.4.0:
+ resolution: {integrity: sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==}
- workbox-recipes@7.3.0:
- resolution: {integrity: sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg==}
+ workbox-recipes@7.4.0:
+ resolution: {integrity: sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==}
workbox-routing@7.3.0:
resolution: {integrity: sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A==}
+ workbox-routing@7.4.0:
+ resolution: {integrity: sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==}
+
workbox-strategies@7.3.0:
resolution: {integrity: sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg==}
- workbox-streams@7.3.0:
- resolution: {integrity: sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw==}
+ workbox-strategies@7.4.0:
+ resolution: {integrity: sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==}
+
+ workbox-streams@7.4.0:
+ resolution: {integrity: sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==}
- workbox-sw@7.3.0:
- resolution: {integrity: sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA==}
+ workbox-sw@7.4.0:
+ resolution: {integrity: sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==}
- workbox-webpack-plugin@7.3.0:
- resolution: {integrity: sha512-EC8lmSAuNmPli04+a5r5lTgv8ab+f5l+XjdYuYpbGnxDT15kH6DBeBazVslpffqTDHt+wkdBMnBCu8GdkKrTSA==}
- engines: {node: '>=16.0.0'}
+ workbox-webpack-plugin@7.4.0:
+ resolution: {integrity: sha512-NRgx4lYe4JP5I8qqiROmngbc38WyyN3BZh48lUir2XYJ63EuHWN0KpDxgcYQ/fJtQQIBoswwUPmpqwQmaupnxQ==}
+ engines: {node: '>=20.0.0'}
peerDependencies:
webpack: ^4.4.0 || ^5.91.0
workbox-window@7.3.0:
resolution: {integrity: sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==}
+ workbox-window@7.4.0:
+ resolution: {integrity: sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==}
+
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -7678,6 +7866,18 @@ packages:
utf-8-validate:
optional: true
+ ws@8.20.0:
+ resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
xml-name-validator@2.0.1:
resolution: {integrity: sha512-jRKe/iQYMyVJpzPH+3HL97Lgu5HrCfii+qSo+TfjKHtOnvbnvdVfMYrn9Q34YV81M2e5sviJlI6Ko9y+nByzvA==}
@@ -7685,6 +7885,10 @@ packages:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
@@ -7709,6 +7913,10 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
@@ -7721,855 +7929,847 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
+ yauzl@3.3.0:
+ resolution: {integrity: sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ==}
+ engines: {node: '>=12'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- yocto-queue@1.2.1:
- resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
- engines: {node: '>=12.20'}
-
snapshots:
'@adobe/css-tools@4.3.3': {}
'@adobe/css-tools@4.4.3': {}
- '@ampproject/remapping@2.3.0':
- dependencies:
- '@jridgewell/gen-mapping': 0.3.12
- '@jridgewell/trace-mapping': 0.3.29
-
- '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)':
+ '@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)':
dependencies:
- ajv: 8.17.1
+ ajv: 8.18.0
json-schema: 0.4.0
jsonpointer: 5.0.1
leven: 3.1.0
- '@babel/code-frame@7.12.11':
+ '@asamuzakjp/css-color@3.2.0':
dependencies:
- '@babel/highlight': 7.25.9
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
- '@babel/code-frame@7.27.1':
+ '@babel/code-frame@7.29.0':
dependencies:
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
- '@babel/compat-data@7.28.0': {}
+ '@babel/compat-data@7.29.0': {}
- '@babel/core@7.28.0':
+ '@babel/core@7.29.0':
dependencies:
- '@ampproject/remapping': 2.3.0
- '@babel/code-frame': 7.27.1
- '@babel/generator': 7.28.0
- '@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
- '@babel/helpers': 7.27.6
- '@babel/parser': 7.28.0
- '@babel/template': 7.27.2
- '@babel/traverse': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
- debug: 4.4.1
+ debug: 4.4.3
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/generator@7.28.0':
+ '@babel/generator@7.29.1':
dependencies:
- '@babel/parser': 7.28.0
- '@babel/types': 7.28.0
- '@jridgewell/gen-mapping': 0.3.12
- '@jridgewell/trace-mapping': 0.3.29
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/helper-annotate-as-pure@7.27.3':
dependencies:
- '@babel/types': 7.28.0
+ '@babel/types': 7.29.0
- '@babel/helper-compilation-targets@7.27.2':
+ '@babel/helper-compilation-targets@7.28.6':
dependencies:
- '@babel/compat-data': 7.28.0
+ '@babel/compat-data': 7.29.0
'@babel/helper-validator-option': 7.27.1
- browserslist: 4.25.1
+ browserslist: 4.28.2
lru-cache: 5.1.1
semver: 6.3.1
- '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)':
+ '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/helper-member-expression-to-functions': 7.28.5
'@babel/helper-optimise-call-expression': 7.27.1
- '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0)
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
'@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/traverse': 7.29.0
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.0)':
+ '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@babel/helper-annotate-as-pure': 7.27.3
- regexpu-core: 6.2.0
+ regexpu-core: 6.4.0
semver: 6.3.1
- '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.0)':
+ '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-plugin-utils': 7.27.1
- debug: 4.4.1
+ '@babel/core': 7.29.0
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ debug: 4.4.3
lodash.debounce: 4.0.8
- resolve: 1.22.10
+ resolve: 1.22.11
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ debug: 4.4.3
+ lodash.debounce: 4.0.8
+ resolve: 1.22.11
transitivePeerDependencies:
- supports-color
'@babel/helper-globals@7.28.0': {}
- '@babel/helper-member-expression-to-functions@7.27.1':
+ '@babel/helper-member-expression-to-functions@7.28.5':
dependencies:
- '@babel/traverse': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-imports@7.27.1':
+ '@babel/helper-module-imports@7.28.6':
dependencies:
- '@babel/traverse': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)':
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-imports': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
'@babel/helper-optimise-call-expression@7.27.1':
dependencies:
- '@babel/types': 7.28.0
+ '@babel/types': 7.29.0
- '@babel/helper-plugin-utils@7.27.1': {}
+ '@babel/helper-plugin-utils@7.28.6': {}
- '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.0)':
+ '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-wrap-function': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/helper-wrap-function': 7.28.6
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.0)':
+ '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-member-expression-to-functions': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-member-expression-to-functions': 7.28.5
'@babel/helper-optimise-call-expression': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
'@babel/helper-skip-transparent-expression-wrappers@7.27.1':
dependencies:
- '@babel/traverse': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
'@babel/helper-string-parser@7.27.1': {}
- '@babel/helper-validator-identifier@7.27.1': {}
+ '@babel/helper-validator-identifier@7.28.5': {}
'@babel/helper-validator-option@7.27.1': {}
- '@babel/helper-wrap-function@7.27.1':
+ '@babel/helper-wrap-function@7.28.6':
dependencies:
- '@babel/template': 7.27.2
- '@babel/traverse': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/helpers@7.27.6':
+ '@babel/helpers@7.28.6':
dependencies:
- '@babel/template': 7.27.2
- '@babel/types': 7.28.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
- '@babel/highlight@7.25.9':
+ '@babel/parser@7.29.0':
dependencies:
- '@babel/helper-validator-identifier': 7.27.1
- chalk: 2.4.2
- js-tokens: 4.0.0
- picocolors: 1.1.1
+ '@babel/types': 7.29.0
- '@babel/parser@7.28.0':
+ '@babel/parser@7.29.2':
dependencies:
- '@babel/types': 7.28.0
+ '@babel/types': 7.29.0
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.0)
+ '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.0)':
- dependencies:
- '@babel/core': 7.28.0
-
- '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)':
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
- '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.0)':
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.0)
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0)
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-imports': 7.27.1
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-block-scoping@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-classes@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-compilation-targets': 7.28.6
'@babel/helper-globals': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0)
- '@babel/traverse': 7.28.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/template': 7.27.2
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/template': 7.28.6
- '@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.0)':
- dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
-
- '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.27.1
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-object-rest-spread@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.0)
- '@babel/traverse': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0)
+ '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0)
+ '@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.27.1
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.0)':
+ '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-regenerator@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-runtime@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-runtime@7.29.0(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-imports': 7.27.1
- '@babel/helper-plugin-utils': 7.27.1
- babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.0)
- babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.0)
- babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0)
+ babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.0)
+ babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.27.1
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.0)':
- dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
-
- '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.0)':
+ '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-annotate-as-pure': 7.27.3
- '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
- '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0)
- transitivePeerDependencies:
- - supports-color
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.0)':
+ '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.0)
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/preset-env@7.28.0(@babel/core@7.28.0)':
+ '@babel/preset-env@7.29.2(@babel/core@7.29.0)':
dependencies:
- '@babel/compat-data': 7.28.0
- '@babel/core': 7.28.0
- '@babel/helper-compilation-targets': 7.27.2
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/compat-data': 7.29.0
+ '@babel/core': 7.29.0
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-validator-option': 7.27.1
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.0)
- '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.0)
- '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-block-scoping': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-class-static-block': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-classes': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.0)
- '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-regenerator': 7.28.0(@babel/core@7.28.0)
- '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.0)
- '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.0)
- babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.0)
- babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.0)
- babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.0)
- core-js-compat: 3.43.0
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0)
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)
+ '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0)
+ '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0)
+ '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0)
+ '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0)
+ '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0)
+ '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0)
+ '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0)
+ '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0)
+ '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0)
+ babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0)
+ babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0)
+ babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0)
+ core-js-compat: 3.49.0
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/preset-flow@7.27.1(@babel/core@7.28.0)':
- dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-validator-option': 7.27.1
- '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.0)
-
- '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.0)':
+ '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/types': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/types': 7.29.0
esutils: 2.0.3
- '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)':
- dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-plugin-utils': 7.27.1
- '@babel/helper-validator-option': 7.27.1
- '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0)
- transitivePeerDependencies:
- - supports-color
-
- '@babel/register@7.27.1(@babel/core@7.28.0)':
- dependencies:
- '@babel/core': 7.28.0
- clone-deep: 4.0.1
- find-cache-dir: 2.1.0
- make-dir: 2.1.0
- pirates: 4.0.7
- source-map-support: 0.5.21
-
- '@babel/runtime@7.23.2':
+ '@babel/runtime@7.27.0':
dependencies:
regenerator-runtime: 0.14.1
'@babel/runtime@7.27.6': {}
- '@babel/template@7.27.2':
+ '@babel/runtime@7.28.4': {}
+
+ '@babel/runtime@7.28.6': {}
+
+ '@babel/template@7.28.6':
dependencies:
- '@babel/code-frame': 7.27.1
- '@babel/parser': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
- '@babel/traverse@7.28.0':
+ '@babel/traverse@7.29.0':
dependencies:
- '@babel/code-frame': 7.27.1
- '@babel/generator': 7.28.0
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
'@babel/helper-globals': 7.28.0
- '@babel/parser': 7.28.0
- '@babel/template': 7.27.2
- '@babel/types': 7.28.0
- debug: 4.4.1
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
- '@babel/types@7.28.0':
+ '@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
'@bcoe/v8-coverage@0.2.3': {}
- '@cortex-js/compute-engine@0.28.0':
+ '@cortex-js/compute-engine@0.30.2':
dependencies:
complex-esm: 2.1.1-esm1
- decimal.js: 10.5.0
+ decimal.js: 10.6.0
+
+ '@crowdin/cli@4.14.1(encoding@0.1.13)':
+ dependencies:
+ command-exists-promise: 2.0.2
+ node-fetch: 2.7.0(encoding@0.1.13)
+ shelljs: 0.10.0
+ tar: 7.5.13
+ yauzl: 3.3.0
+ transitivePeerDependencies:
+ - encoding
+
+ '@csstools/color-helpers@5.1.0': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
'@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1)':
dependencies:
'@csstools/css-tokenizer': 2.4.1
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
'@csstools/css-tokenizer@2.4.1': {}
+ '@csstools/css-tokenizer@3.0.4': {}
+
'@csstools/media-query-list-parser@2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)':
dependencies:
'@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1)
@@ -8581,30 +8781,39 @@ snapshots:
'@discoveryjs/json-ext@0.6.3': {}
+ '@discoveryjs/json-ext@1.0.0': {}
+
+ '@emnapi/core@1.9.2':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.1
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.9.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.2.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
eslint-visitor-keys: 3.4.3
- '@eslint-community/regexpp@4.12.1': {}
-
- '@eslint/eslintrc@0.4.3':
+ '@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)':
dependencies:
- ajv: 6.12.6
- debug: 4.4.1
- espree: 7.3.1
- globals: 13.24.0
- ignore: 4.0.6
- import-fresh: 3.3.1
- js-yaml: 3.14.1
- minimatch: 3.1.2
- strip-json-comments: 3.1.1
- transitivePeerDependencies:
- - supports-color
+ eslint: 8.57.1
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.1': {}
'@eslint/eslintrc@2.1.4':
dependencies:
- ajv: 6.12.6
+ ajv: 6.14.0
debug: 4.4.1
espree: 9.6.1
globals: 13.24.0
@@ -8618,6 +8827,20 @@ snapshots:
'@eslint/js@8.57.1': {}
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+ optional: true
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+ optional: true
+
+ '@floating-ui/utils@0.2.10':
+ optional: true
+
'@gar/promisify@1.1.3': {}
'@humanwhocodes/config-array@0.13.0':
@@ -8628,29 +8851,25 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@humanwhocodes/config-array@0.5.0':
- dependencies:
- '@humanwhocodes/object-schema': 1.2.1
- debug: 4.4.1
- minimatch: 3.1.2
- transitivePeerDependencies:
- - supports-color
-
'@humanwhocodes/module-importer@1.0.1': {}
- '@humanwhocodes/object-schema@1.2.1': {}
-
'@humanwhocodes/object-schema@2.0.3': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
- strip-ansi: 7.1.0
+ strip-ansi: 7.2.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
+ '@isaacs/cliui@9.0.0': {}
+
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.3
+
'@istanbuljs/load-nyc-config@1.1.0':
dependencies:
camelcase: 5.3.1
@@ -8661,118 +8880,130 @@ snapshots:
'@istanbuljs/schema@0.1.3': {}
- '@jest/console@29.7.0':
+ '@jest/console@30.3.0':
dependencies:
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
chalk: 4.1.2
- jest-message-util: 29.7.0
- jest-util: 29.7.0
+ jest-message-util: 30.3.0
+ jest-util: 30.3.0
slash: 3.0.0
- '@jest/core@29.7.0':
+ '@jest/core@30.3.0':
dependencies:
- '@jest/console': 29.7.0
- '@jest/reporters': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@jest/console': 30.3.0
+ '@jest/pattern': 30.0.1
+ '@jest/reporters': 30.3.0
+ '@jest/test-result': 30.3.0
+ '@jest/transform': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
ansi-escapes: 4.3.2
chalk: 4.1.2
- ci-info: 3.9.0
- exit: 0.1.2
+ ci-info: 4.4.0
+ exit-x: 0.2.2
graceful-fs: 4.2.11
- jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@24.1.0)
- jest-haste-map: 29.7.0
- jest-message-util: 29.7.0
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-resolve-dependencies: 29.7.0
- jest-runner: 29.7.0
- jest-runtime: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- jest-watcher: 29.7.0
- micromatch: 4.0.8
- pretty-format: 29.7.0
+ jest-changed-files: 30.3.0
+ jest-config: 30.3.0(@types/node@25.5.2)
+ jest-haste-map: 30.3.0
+ jest-message-util: 30.3.0
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.3.0
+ jest-resolve-dependencies: 30.3.0
+ jest-runner: 30.3.0
+ jest-runtime: 30.3.0
+ jest-snapshot: 30.3.0
+ jest-util: 30.3.0
+ jest-validate: 30.3.0
+ jest-watcher: 30.3.0
+ pretty-format: 30.3.0
slash: 3.0.0
- strip-ansi: 6.0.1
transitivePeerDependencies:
- babel-plugin-macros
+ - esbuild-register
- supports-color
- ts-node
- '@jest/environment@29.7.0':
+ '@jest/diff-sequences@30.3.0': {}
+
+ '@jest/environment-jsdom-abstract@30.3.0(jsdom@26.1.0)':
dependencies:
- '@jest/fake-timers': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.0.10
- jest-mock: 29.7.0
+ '@jest/environment': 30.3.0
+ '@jest/fake-timers': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/jsdom': 21.1.7
+ '@types/node': 25.5.2
+ jest-mock: 30.3.0
+ jest-util: 30.3.0
+ jsdom: 26.1.0
+
+ '@jest/environment@30.3.0':
+ dependencies:
+ '@jest/fake-timers': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
+ jest-mock: 30.3.0
- '@jest/expect-utils@29.7.0':
+ '@jest/expect-utils@30.3.0':
dependencies:
- jest-get-type: 29.6.3
+ '@jest/get-type': 30.1.0
- '@jest/expect@29.7.0':
+ '@jest/expect@30.3.0':
dependencies:
- expect: 29.7.0
- jest-snapshot: 29.7.0
+ expect: 30.3.0
+ jest-snapshot: 30.3.0
transitivePeerDependencies:
- supports-color
- '@jest/fake-timers@29.7.0':
+ '@jest/fake-timers@30.3.0':
dependencies:
- '@jest/types': 29.6.3
- '@sinonjs/fake-timers': 10.3.0
- '@types/node': 24.0.10
- jest-message-util: 29.7.0
- jest-mock: 29.7.0
- jest-util: 29.7.0
+ '@jest/types': 30.3.0
+ '@sinonjs/fake-timers': 15.3.0
+ '@types/node': 25.5.2
+ jest-message-util: 30.3.0
+ jest-mock: 30.3.0
+ jest-util: 30.3.0
- '@jest/get-type@30.0.1': {}
+ '@jest/get-type@30.1.0': {}
- '@jest/globals@29.7.0':
+ '@jest/globals@30.3.0':
dependencies:
- '@jest/environment': 29.7.0
- '@jest/expect': 29.7.0
- '@jest/types': 29.6.3
- jest-mock: 29.7.0
+ '@jest/environment': 30.3.0
+ '@jest/expect': 30.3.0
+ '@jest/types': 30.3.0
+ jest-mock: 30.3.0
transitivePeerDependencies:
- supports-color
'@jest/pattern@30.0.1':
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
jest-regex-util: 30.0.1
- '@jest/reporters@29.7.0':
+ '@jest/reporters@30.3.0':
dependencies:
'@bcoe/v8-coverage': 0.2.3
- '@jest/console': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@jridgewell/trace-mapping': 0.3.29
- '@types/node': 24.1.0
+ '@jest/console': 30.3.0
+ '@jest/test-result': 30.3.0
+ '@jest/transform': 30.3.0
+ '@jest/types': 30.3.0
+ '@jridgewell/trace-mapping': 0.3.31
+ '@types/node': 25.5.2
chalk: 4.1.2
- collect-v8-coverage: 1.0.2
- exit: 0.1.2
- glob: 7.2.3
+ collect-v8-coverage: 1.0.3
+ exit-x: 0.2.2
+ glob: 10.5.0
graceful-fs: 4.2.11
istanbul-lib-coverage: 3.2.2
istanbul-lib-instrument: 6.0.3
istanbul-lib-report: 3.0.1
- istanbul-lib-source-maps: 4.0.1
- istanbul-reports: 3.1.7
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- jest-worker: 29.7.0
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.2.0
+ jest-message-util: 30.3.0
+ jest-util: 30.3.0
+ jest-worker: 30.3.0
slash: 3.0.0
string-length: 4.0.2
- strip-ansi: 6.0.1
v8-to-istanbul: 9.3.0
transitivePeerDependencies:
- supports-color
@@ -8783,31 +9014,38 @@ snapshots:
'@jest/schemas@30.0.5':
dependencies:
- '@sinclair/typebox': 0.34.38
+ '@sinclair/typebox': 0.34.49
- '@jest/source-map@29.6.3':
+ '@jest/snapshot-utils@30.3.0':
dependencies:
- '@jridgewell/trace-mapping': 0.3.29
+ '@jest/types': 30.3.0
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ natural-compare: 1.4.0
+
+ '@jest/source-map@30.0.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
callsites: 3.1.0
graceful-fs: 4.2.11
- '@jest/test-result@29.7.0':
+ '@jest/test-result@30.3.0':
dependencies:
- '@jest/console': 29.7.0
- '@jest/types': 29.6.3
+ '@jest/console': 30.3.0
+ '@jest/types': 30.3.0
'@types/istanbul-lib-coverage': 2.0.6
- collect-v8-coverage: 1.0.2
+ collect-v8-coverage: 1.0.3
- '@jest/test-sequencer@29.7.0':
+ '@jest/test-sequencer@30.3.0':
dependencies:
- '@jest/test-result': 29.7.0
+ '@jest/test-result': 30.3.0
graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
+ jest-haste-map: 30.3.0
slash: 3.0.0
'@jest/transform@29.7.0':
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.29
babel-plugin-istanbul: 6.1.1
@@ -8825,23 +9063,42 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@jest/transform@30.3.0':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@jest/types': 30.3.0
+ '@jridgewell/trace-mapping': 0.3.31
+ babel-plugin-istanbul: 7.0.1
+ chalk: 4.1.2
+ convert-source-map: 2.0.0
+ fast-json-stable-stringify: 2.1.0
+ graceful-fs: 4.2.11
+ jest-haste-map: 30.3.0
+ jest-regex-util: 30.0.1
+ jest-util: 30.3.0
+ pirates: 4.0.7
+ slash: 3.0.0
+ write-file-atomic: 5.0.1
+ transitivePeerDependencies:
+ - supports-color
+
'@jest/types@29.6.3':
dependencies:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/yargs': 17.0.33
chalk: 4.1.2
- '@jest/types@30.0.5':
+ '@jest/types@30.3.0':
dependencies:
'@jest/pattern': 30.0.1
'@jest/schemas': 30.0.5
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
- '@types/node': 24.1.0
- '@types/yargs': 17.0.33
+ '@types/node': 25.5.2
+ '@types/yargs': 17.0.35
chalk: 4.1.2
'@jimp/bmp@0.22.12(@jimp/custom@0.22.12(encoding@0.1.13))':
@@ -8912,25 +9169,37 @@ snapshots:
dependencies:
regenerator-runtime: 0.13.11
- '@jridgewell/gen-mapping@0.3.12':
+ '@jridgewell/gen-mapping@0.3.13':
dependencies:
- '@jridgewell/sourcemap-codec': 1.5.4
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
- '@jridgewell/source-map@0.3.10':
+ '@jridgewell/source-map@0.3.11':
dependencies:
- '@jridgewell/gen-mapping': 0.3.12
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
'@jridgewell/sourcemap-codec@1.5.4': {}
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
'@jridgewell/trace-mapping@0.3.29':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.4
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
'@jsonjoy.com/base64@1.1.2(tslib@2.8.1)':
dependencies:
tslib: 2.8.1
@@ -8949,6 +9218,13 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {}
+ '@napi-rs/wasm-runtime@0.2.12':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -8964,12 +9240,12 @@ snapshots:
'@npmcli/fs@1.1.1':
dependencies:
'@gar/promisify': 1.1.3
- semver: 7.7.2
+ semver: 7.7.4
'@npmcli/fs@2.1.2':
dependencies:
'@gar/promisify': 1.1.3
- semver: 7.7.2
+ semver: 7.7.4
'@npmcli/move-file@1.1.2':
dependencies:
@@ -8986,14 +9262,14 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
- '@popperjs/core@2.11.8': {}
+ '@pkgr/core@0.2.9': {}
'@remirror/core-constants@3.0.0': {}
- '@rollup/plugin-babel@5.3.1(@babel/core@7.28.0)(@types/babel__core@7.20.5)(rollup@2.79.2)':
+ '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@2.79.2)':
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-module-imports': 7.27.1
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
'@rollup/pluginutils': 3.1.0(rollup@2.79.2)
rollup: 2.79.2
optionalDependencies:
@@ -9003,11 +9279,11 @@ snapshots:
'@rollup/plugin-node-resolve@15.3.1(rollup@2.79.2)':
dependencies:
- '@rollup/pluginutils': 5.2.0(rollup@2.79.2)
+ '@rollup/pluginutils': 5.3.0(rollup@2.79.2)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-module: 1.0.0
- resolve: 1.22.10
+ resolve: 1.22.11
optionalDependencies:
rollup: 2.79.2
@@ -9020,8 +9296,8 @@ snapshots:
'@rollup/plugin-terser@0.4.4(rollup@2.79.2)':
dependencies:
serialize-javascript: 6.0.2
- smob: 1.5.0
- terser: 5.43.1
+ smob: 1.6.1
+ terser: 5.46.0
optionalDependencies:
rollup: 2.79.2
@@ -9029,64 +9305,62 @@ snapshots:
dependencies:
'@types/estree': 0.0.39
estree-walker: 1.0.1
- picomatch: 2.3.1
+ picomatch: 2.3.2
rollup: 2.79.2
- '@rollup/pluginutils@5.2.0(rollup@2.79.2)':
+ '@rollup/pluginutils@5.3.0(rollup@2.79.2)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
- picomatch: 4.0.3
+ picomatch: 4.0.4
optionalDependencies:
rollup: 2.79.2
'@rtsao/scc@1.1.0': {}
- '@rushstack/eslint-patch@1.12.0': {}
-
- '@sentry-internal/browser-utils@9.40.0':
+ '@sentry-internal/browser-utils@10.6.0':
dependencies:
- '@sentry/core': 9.40.0
+ '@sentry/core': 10.6.0
- '@sentry-internal/feedback@9.40.0':
+ '@sentry-internal/feedback@10.6.0':
dependencies:
- '@sentry/core': 9.40.0
+ '@sentry/core': 10.6.0
- '@sentry-internal/replay-canvas@9.40.0':
+ '@sentry-internal/replay-canvas@10.6.0':
dependencies:
- '@sentry-internal/replay': 9.40.0
- '@sentry/core': 9.40.0
+ '@sentry-internal/replay': 10.6.0
+ '@sentry/core': 10.6.0
- '@sentry-internal/replay@9.40.0':
+ '@sentry-internal/replay@10.6.0':
dependencies:
- '@sentry-internal/browser-utils': 9.40.0
- '@sentry/core': 9.40.0
+ '@sentry-internal/browser-utils': 10.6.0
+ '@sentry/core': 10.6.0
- '@sentry/browser@9.40.0':
+ '@sentry/browser@10.6.0':
dependencies:
- '@sentry-internal/browser-utils': 9.40.0
- '@sentry-internal/feedback': 9.40.0
- '@sentry-internal/replay': 9.40.0
- '@sentry-internal/replay-canvas': 9.40.0
- '@sentry/core': 9.40.0
+ '@sentry-internal/browser-utils': 10.6.0
+ '@sentry-internal/feedback': 10.6.0
+ '@sentry-internal/replay': 10.6.0
+ '@sentry-internal/replay-canvas': 10.6.0
+ '@sentry/core': 10.6.0
- '@sentry/core@9.40.0': {}
+ '@sentry/core@10.6.0': {}
- '@sentry/vue@9.40.0(vue@2.7.16)':
+ '@sentry/vue@10.6.0(vue@2.7.16)':
dependencies:
- '@sentry/browser': 9.40.0
- '@sentry/core': 9.40.0
+ '@sentry/browser': 10.6.0
+ '@sentry/core': 10.6.0
vue: 2.7.16
'@sinclair/typebox@0.27.8': {}
- '@sinclair/typebox@0.34.38': {}
+ '@sinclair/typebox@0.34.49': {}
'@sinonjs/commons@3.0.1':
dependencies:
type-detect: 4.0.8
- '@sinonjs/fake-timers@10.3.0':
+ '@sinonjs/fake-timers@15.3.0':
dependencies:
'@sinonjs/commons': 3.0.1
@@ -9099,7 +9373,7 @@ snapshots:
'@testing-library/dom@9.3.4':
dependencies:
- '@babel/code-frame': 7.27.1
+ '@babel/code-frame': 7.29.0
'@babel/runtime': 7.27.6
'@types/aria-query': 5.0.4
aria-query: 5.1.3
@@ -9115,7 +9389,7 @@ snapshots:
chalk: 3.0.0
css.escape: 1.5.1
dom-accessibility-api: 0.6.3
- lodash: 4.17.21
+ lodash: 4.18.1
redent: 3.0.0
'@testing-library/user-event@14.6.1(@testing-library/dom@9.3.4)':
@@ -9130,187 +9404,199 @@ snapshots:
vue: 2.7.16
vue-template-compiler: 2.7.16
- '@tiptap/core@2.23.1(@tiptap/pm@2.23.1)':
+ '@tiptap/core@3.13.0(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/pm': 2.23.1
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-blockquote@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-blockquote@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-bold@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-bold@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-bubble-menu@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-bubble-menu@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
- tippy.js: 6.3.7
+ '@floating-ui/dom': 1.7.4
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
+ optional: true
- '@tiptap/extension-bullet-list@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-bullet-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-code-block-lowlight@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/extension-code-block@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(highlight.js@11.11.1)(lowlight@3.3.0)':
+ '@tiptap/extension-code-block-lowlight@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/extension-code-block@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(highlight.js@11.11.1)(lowlight@3.3.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/extension-code-block': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/extension-code-block': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
highlight.js: 11.11.1
lowlight: 3.3.0
- '@tiptap/extension-code-block@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-code-block@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
+ dependencies:
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
+
+ '@tiptap/extension-code@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-code@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-document@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-document@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-dropcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-dropcursor@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-floating-menu@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ '@floating-ui/dom': 1.7.4
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
+ optional: true
+
+ '@tiptap/extension-gapcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
+ dependencies:
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-floating-menu@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-hard-break@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
- tippy.js: 6.3.7
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-gapcursor@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-heading@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-hard-break@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-horizontal-rule@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-heading@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-italic@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-history@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-link@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
+ linkifyjs: 4.3.2
- '@tiptap/extension-horizontal-rule@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-list-item@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-italic@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-list-keymap@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-link@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)':
+ '@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
- linkifyjs: 4.3.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-list-item@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-ordered-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-ordered-list@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-paragraph@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-paragraph@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-strike@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-strike@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-subscript@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-subscript@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-superscript@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-superscript@2.26.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-text-align@3.18.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-text-style@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-text@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-text@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extension-underline@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-underline@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))':
+ '@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/pm@2.23.1':
+ '@tiptap/pm@3.13.0':
dependencies:
prosemirror-changeset: 2.3.1
prosemirror-collab: 1.3.1
prosemirror-commands: 1.7.1
prosemirror-dropcursor: 1.8.2
- prosemirror-gapcursor: 1.3.2
- prosemirror-history: 1.4.1
- prosemirror-inputrules: 1.5.0
+ prosemirror-gapcursor: 1.4.0
+ prosemirror-history: 1.5.0
+ prosemirror-inputrules: 1.5.1
prosemirror-keymap: 1.2.3
prosemirror-markdown: 1.13.2
prosemirror-menu: 1.2.5
- prosemirror-model: 1.25.1
+ prosemirror-model: 1.25.4
prosemirror-schema-basic: 1.2.4
prosemirror-schema-list: 1.5.1
- prosemirror-state: 1.4.3
- prosemirror-tables: 1.7.1
- prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)
- prosemirror-transform: 1.10.4
- prosemirror-view: 1.40.0
-
- '@tiptap/starter-kit@2.23.1':
- dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/extension-blockquote': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-bold': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-bullet-list': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-code': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-code-block': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/extension-document': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-dropcursor': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/extension-gapcursor': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/extension-hard-break': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-heading': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-history': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/extension-horizontal-rule': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/extension-italic': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-list-item': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-ordered-list': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-paragraph': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-strike': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-text': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/extension-text-style': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))
- '@tiptap/pm': 2.23.1
-
- '@tiptap/vue-2@2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)(vue@2.7.16)':
- dependencies:
- '@tiptap/core': 2.23.1(@tiptap/pm@2.23.1)
- '@tiptap/extension-bubble-menu': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/extension-floating-menu': 2.23.1(@tiptap/core@2.23.1(@tiptap/pm@2.23.1))(@tiptap/pm@2.23.1)
- '@tiptap/pm': 2.23.1
+ prosemirror-state: 1.4.4
+ prosemirror-tables: 1.8.3
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4)
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
+
+ '@tiptap/starter-kit@3.13.0':
+ dependencies:
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/extension-blockquote': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-bold': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-bullet-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-code': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-code-block': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-document': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-dropcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-gapcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-hard-break': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-heading': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-horizontal-rule': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-italic': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-link': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-list-item': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-list-keymap': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-ordered-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-paragraph': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-strike': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-text': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-underline': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
+
+ '@tiptap/vue-2@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(vue@2.7.16)':
+ dependencies:
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
vue: 2.7.16
vue-ts-types: 1.6.2(vue@2.7.16)
-
- '@toast-ui/editor@2.5.4':
- dependencies:
- '@types/codemirror': 0.0.71
- codemirror: 5.58.2
+ optionalDependencies:
+ '@tiptap/extension-bubble-menu': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-floating-menu': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ transitivePeerDependencies:
+ - '@floating-ui/dom'
'@tokenizer/token@0.3.0': {}
@@ -9320,42 +9606,43 @@ snapshots:
'@trysound/sax@0.2.0': {}
- '@types/aria-query@5.0.4': {}
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/aria-query@5.0.4': {}
'@types/babel__core@7.20.5':
dependencies:
- '@babel/parser': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
'@types/babel__generator': 7.27.0
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.20.7
'@types/babel__generator@7.27.0':
dependencies:
- '@babel/types': 7.28.0
+ '@babel/types': 7.29.0
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.28.0
- '@babel/types': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
'@types/babel__traverse@7.20.7':
dependencies:
- '@babel/types': 7.28.0
+ '@babel/types': 7.29.0
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/bonjour@3.5.13':
dependencies:
'@types/node': 24.1.0
- '@types/codemirror@0.0.71':
- dependencies:
- '@types/tern': 0.23.9
-
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 4.19.6
@@ -9363,7 +9650,7 @@ snapshots:
'@types/connect@3.4.38':
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/eslint-scope@3.7.7':
dependencies:
@@ -9395,7 +9682,7 @@ snapshots:
'@types/graceful-fs@4.1.9':
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/hast@3.0.4':
dependencies:
@@ -9405,7 +9692,7 @@ snapshots:
'@types/http-proxy@1.17.16':
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/istanbul-lib-coverage@2.0.6': {}
@@ -9417,9 +9704,9 @@ snapshots:
dependencies:
'@types/istanbul-lib-report': 3.0.3
- '@types/jsdom@20.0.1':
+ '@types/jsdom@21.1.7':
dependencies:
- '@types/node': 24.0.10
+ '@types/node': 25.5.2
'@types/tough-cookie': 4.0.5
parse5: 7.3.0
@@ -9446,7 +9733,7 @@ snapshots:
'@types/node-forge@1.3.11':
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/node@16.9.1': {}
@@ -9454,13 +9741,13 @@ snapshots:
dependencies:
undici-types: 5.26.5
- '@types/node@24.0.10':
+ '@types/node@24.1.0':
dependencies:
undici-types: 7.8.0
- '@types/node@24.1.0':
+ '@types/node@25.5.2':
dependencies:
- undici-types: 7.8.0
+ undici-types: 7.18.2
'@types/normalize-package-data@2.4.4': {}
@@ -9475,7 +9762,7 @@ snapshots:
'@types/send@0.17.5':
dependencies:
'@types/mime': 1.3.5
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
'@types/serve-index@1.9.4':
dependencies:
@@ -9497,18 +9784,12 @@ snapshots:
'@types/strip-json-comments@0.0.30': {}
- '@types/tern@0.23.9':
- dependencies:
- '@types/estree': 1.0.8
-
'@types/tough-cookie@4.0.5': {}
'@types/trusted-types@2.0.7': {}
'@types/unist@3.0.3': {}
- '@types/web-bluetooth@0.0.20': {}
-
'@types/ws@8.18.1':
dependencies:
'@types/node': 24.1.0
@@ -9519,60 +9800,123 @@ snapshots:
dependencies:
'@types/yargs-parser': 21.0.3
- '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)':
+ '@types/yargs@17.0.35':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3)
- '@typescript-eslint/types': 8.35.1
- debug: 4.4.1
+ '@types/yargs-parser': 21.0.3
+
+ '@typescript-eslint/project-service@8.47.0(typescript@5.8.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.8.3)
+ '@typescript-eslint/types': 8.47.0
+ debug: 4.4.3
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.35.1':
+ '@typescript-eslint/scope-manager@8.47.0':
dependencies:
- '@typescript-eslint/types': 8.35.1
- '@typescript-eslint/visitor-keys': 8.35.1
+ '@typescript-eslint/types': 8.47.0
+ '@typescript-eslint/visitor-keys': 8.47.0
- '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)':
+ '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.8.3)':
dependencies:
typescript: 5.8.3
- '@typescript-eslint/types@8.35.1': {}
+ '@typescript-eslint/types@8.47.0': {}
- '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)':
+ '@typescript-eslint/typescript-estree@8.47.0(typescript@5.8.3)':
dependencies:
- '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3)
- '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3)
- '@typescript-eslint/types': 8.35.1
- '@typescript-eslint/visitor-keys': 8.35.1
- debug: 4.4.1
+ '@typescript-eslint/project-service': 8.47.0(typescript@5.8.3)
+ '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.8.3)
+ '@typescript-eslint/types': 8.47.0
+ '@typescript-eslint/visitor-keys': 8.47.0
+ debug: 4.4.3
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
- semver: 7.7.2
+ semver: 7.7.4
ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.35.1(eslint@8.57.1)(typescript@5.8.3)':
+ '@typescript-eslint/utils@8.47.0(eslint@8.57.1)(typescript@5.8.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1)
- '@typescript-eslint/scope-manager': 8.35.1
- '@typescript-eslint/types': 8.35.1
- '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3)
+ '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1)
+ '@typescript-eslint/scope-manager': 8.47.0
+ '@typescript-eslint/types': 8.47.0
+ '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.8.3)
eslint: 8.57.1
typescript: 5.8.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.35.1':
+ '@typescript-eslint/visitor-keys@8.47.0':
dependencies:
- '@typescript-eslint/types': 8.35.1
+ '@typescript-eslint/types': 8.47.0
eslint-visitor-keys: 4.2.1
'@ungap/structured-clone@1.3.0': {}
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ dependencies:
+ '@napi-rs/wasm-runtime': 0.2.12
+ optional: true
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ optional: true
+
'@vibrant/color@4.0.0': {}
'@vibrant/core@4.0.0':
@@ -9630,15 +9974,15 @@ snapshots:
'@vue/compiler-sfc@2.7.16':
dependencies:
- '@babel/parser': 7.28.0
+ '@babel/parser': 7.29.0
postcss: 8.5.6
source-map: 0.6.1
optionalDependencies:
prettier: 2.8.8
- '@vue/component-compiler-utils@3.3.0(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(ejs@3.1.10)(lodash@4.17.21)':
+ '@vue/component-compiler-utils@3.3.0(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(lodash@4.18.1)':
dependencies:
- consolidate: 0.15.1(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(ejs@3.1.10)(lodash@4.17.21)
+ consolidate: 0.15.1(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(lodash@4.18.1)
hash-sum: 1.0.2
lru-cache: 4.1.5
merge-source-map: 1.1.0
@@ -9706,29 +10050,81 @@ snapshots:
'@vue/test-utils@1.3.6(vue-template-compiler@2.7.16)(vue@2.7.16)':
dependencies:
dom-event-types: 1.1.0
- lodash: 4.17.21
+ lodash: 4.18.1
pretty: 2.0.0
vue: 2.7.16
vue-template-compiler: 2.7.16
- '@vueuse/core@11.3.0(vue@2.7.16)':
- dependencies:
- '@types/web-bluetooth': 0.0.20
- '@vueuse/metadata': 11.3.0
- '@vueuse/shared': 11.3.0(vue@2.7.16)
- vue-demi: 0.14.10(vue@2.7.16)
- transitivePeerDependencies:
- - '@vue/composition-api'
- - vue
-
- '@vueuse/metadata@11.3.0': {}
-
- '@vueuse/shared@11.3.0(vue@2.7.16)':
+ '@vue/vue2-jest@29.2.6(@babel/core@7.29.0)(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(babel-jest@29.7.0(@babel/core@7.29.0))(ejs@3.1.10)(jest@30.3.0(@types/node@25.5.2))(lodash@4.18.1)(typescript@5.8.3)(vue-template-compiler@2.7.16)(vue@2.7.16)':
dependencies:
- vue-demi: 0.14.10(vue@2.7.16)
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0)
+ '@vue/component-compiler-utils': 3.3.0(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(lodash@4.18.1)
+ babel-jest: 29.7.0(@babel/core@7.29.0)
+ chalk: 2.4.2
+ css-tree: 2.3.1
+ jest: 30.3.0(@types/node@25.5.2)
+ source-map: 0.5.6
+ tsconfig: 7.0.0
+ vue: 2.7.16
+ vue-template-compiler: 2.7.16
+ optionalDependencies:
+ typescript: 5.8.3
transitivePeerDependencies:
- - '@vue/composition-api'
- - vue
+ - arc-templates
+ - atpl
+ - babel-core
+ - bracket-template
+ - coffee-script
+ - dot
+ - dust
+ - dustjs-helpers
+ - dustjs-linkedin
+ - eco
+ - ect
+ - ejs
+ - haml-coffee
+ - hamlet
+ - hamljs
+ - handlebars
+ - hogan.js
+ - htmling
+ - jade
+ - jazz
+ - jqtpl
+ - just
+ - liquid-node
+ - liquor
+ - lodash
+ - marko
+ - mote
+ - mustache
+ - nunjucks
+ - plates
+ - pug
+ - qejs
+ - ractive
+ - razor-tmpl
+ - react
+ - react-dom
+ - slm
+ - squirrelly
+ - supports-color
+ - swig
+ - swig-templates
+ - teacup
+ - templayed
+ - then-jade
+ - then-pug
+ - tinyliquid
+ - toffee
+ - twig
+ - twing
+ - underscore
+ - vash
+ - velocityjs
+ - walrus
+ - whiskers
'@webassemblyjs/ast@1.14.1':
dependencies:
@@ -9806,22 +10202,22 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
- '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.99.9)':
+ '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.104.1)':
dependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1)
- '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.99.9)':
+ '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.104.1)':
dependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1)
- '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.99.9)':
+ '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.104.1)':
dependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1)
optionalDependencies:
- webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.99.9)
+ webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.104.1)
'@xmldom/xmldom@0.7.13': {}
@@ -9831,8 +10227,6 @@ snapshots:
abab@1.0.4: {}
- abab@2.0.6: {}
-
abbrev@1.1.1: {}
abbrev@2.0.0: {}
@@ -9846,44 +10240,30 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
- accepts@2.0.0:
- dependencies:
- mime-types: 3.0.1
- negotiator: 1.0.0
-
acorn-globals@1.0.9:
dependencies:
acorn: 2.7.0
- acorn-globals@7.0.1:
+ acorn-import-phases@1.0.4(acorn@8.15.0):
dependencies:
acorn: 8.15.0
- acorn-walk: 8.3.4
-
- acorn-jsx@5.3.2(acorn@7.4.1):
- dependencies:
- acorn: 7.4.1
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
- acorn-walk@8.3.4:
- dependencies:
- acorn: 8.15.0
-
acorn@2.7.0: {}
- acorn@7.4.1: {}
-
acorn@8.15.0: {}
agent-base@6.0.2:
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
+ agent-base@7.1.4: {}
+
agentkeepalive@4.6.0:
dependencies:
humanize-ms: 1.2.1
@@ -9893,35 +10273,33 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
- ajv-formats@2.1.1(ajv@8.17.1):
+ ajv-formats@2.1.1(ajv@8.18.0):
optionalDependencies:
- ajv: 8.17.1
+ ajv: 8.18.0
- ajv-keywords@3.5.2(ajv@6.12.6):
+ ajv-keywords@3.5.2(ajv@6.14.0):
dependencies:
- ajv: 6.12.6
+ ajv: 6.14.0
- ajv-keywords@5.1.0(ajv@8.17.1):
+ ajv-keywords@5.1.0(ajv@8.18.0):
dependencies:
- ajv: 8.17.1
+ ajv: 8.18.0
fast-deep-equal: 3.1.3
- ajv@6.12.6:
+ ajv@6.14.0:
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
- ajv@8.17.1:
+ ajv@8.18.0:
dependencies:
fast-deep-equal: 3.1.3
- fast-uri: 3.0.6
+ fast-uri: 3.1.0
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
- ansi-colors@4.1.3: {}
-
ansi-escapes@4.3.2:
dependencies:
type-fest: 0.21.3
@@ -9932,7 +10310,7 @@ snapshots:
ansi-regex@5.0.1: {}
- ansi-regex@6.1.0: {}
+ ansi-regex@6.2.2: {}
ansi-styles@2.2.1: {}
@@ -9946,14 +10324,14 @@ snapshots:
ansi-styles@5.2.0: {}
- ansi-styles@6.2.1: {}
+ ansi-styles@6.2.3: {}
any-base@1.1.0: {}
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
- picomatch: 2.3.1
+ picomatch: 2.3.2
aphrodite@https://codeload.github.com/learningequality/aphrodite/tar.gz/fdc8d7be8912a5cf17f74ff10f124013c52c3e32:
dependencies:
@@ -10078,6 +10456,15 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
+ autoprefixer@10.4.23(postcss@8.5.6):
+ dependencies:
+ browserslist: 4.28.1
+ caniuse-lite: 1.0.30001769
+ fraction.js: 5.3.4
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+
autosize@3.0.21: {}
available-typed-arrays@1.0.7:
@@ -10088,11 +10475,11 @@ snapshots:
aws4@1.13.2: {}
- axios@1.11.0:
+ axios@1.15.0:
dependencies:
- follow-redirects: 1.15.9
- form-data: 4.0.4
- proxy-from-env: 1.1.0
+ follow-redirects: 1.15.11
+ form-data: 4.0.5
+ proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
@@ -10102,35 +10489,48 @@ snapshots:
esutils: 2.0.3
js-tokens: 3.0.2
- babel-core@7.0.0-bridge.0(@babel/core@7.28.0):
+ babel-core@7.0.0-bridge.0(@babel/core@7.29.0):
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
- babel-jest@29.7.0(@babel/core@7.28.0):
+ babel-jest@29.7.0(@babel/core@7.29.0):
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
'@jest/transform': 29.7.0
'@types/babel__core': 7.20.5
babel-plugin-istanbul: 6.1.1
- babel-preset-jest: 29.6.3(@babel/core@7.28.0)
+ babel-preset-jest: 29.6.3(@babel/core@7.29.0)
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-jest@30.3.0(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@jest/transform': 30.3.0
+ '@types/babel__core': 7.20.5
+ babel-plugin-istanbul: 7.0.1
+ babel-preset-jest: 30.3.0(@babel/core@7.29.0)
chalk: 4.1.2
graceful-fs: 4.2.11
slash: 3.0.0
transitivePeerDependencies:
- supports-color
- babel-loader@10.0.0(@babel/core@7.28.0)(webpack@5.99.9):
+ babel-loader@10.0.0(@babel/core@7.29.0)(webpack@5.104.1):
dependencies:
- '@babel/core': 7.28.0
+ '@babel/core': 7.29.0
find-up: 5.0.0
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
- babel-loader@9.2.1(@babel/core@7.28.0)(webpack@5.99.9):
+ babel-loader@10.1.1(@babel/core@7.29.0)(webpack@5.104.1):
dependencies:
- '@babel/core': 7.28.0
- find-cache-dir: 4.0.0
- schema-utils: 4.3.2
- webpack: 5.99.9(webpack-cli@6.0.1)
+ '@babel/core': 7.29.0
+ find-up: 5.0.0
+ optionalDependencies:
+ webpack: 5.104.1(webpack-cli@6.0.1)
babel-messages@6.23.0:
dependencies:
@@ -10138,7 +10538,7 @@ snapshots:
babel-plugin-istanbul@6.1.1:
dependencies:
- '@babel/helper-plugin-utils': 7.27.1
+ '@babel/helper-plugin-utils': 7.28.6
'@istanbuljs/load-nyc-config': 1.1.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-instrument: 5.2.1
@@ -10146,34 +10546,72 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ babel-plugin-istanbul@7.0.1:
+ dependencies:
+ '@babel/helper-plugin-utils': 7.28.6
+ '@istanbuljs/load-nyc-config': 1.1.0
+ '@istanbuljs/schema': 0.1.3
+ istanbul-lib-instrument: 6.0.3
+ test-exclude: 6.0.0
+ transitivePeerDependencies:
+ - supports-color
+
babel-plugin-jest-hoist@29.6.3:
dependencies:
- '@babel/template': 7.27.2
- '@babel/types': 7.28.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
'@types/babel__core': 7.20.5
'@types/babel__traverse': 7.20.7
- babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.0):
+ babel-plugin-jest-hoist@30.3.0:
+ dependencies:
+ '@types/babel__core': 7.20.5
+
+ babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.29.0):
+ dependencies:
+ '@babel/compat-data': 7.29.0
+ '@babel/core': 7.29.0
+ '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0)
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0):
dependencies:
- '@babel/compat-data': 7.28.0
- '@babel/core': 7.28.0
- '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.0)
+ '@babel/compat-data': 7.29.0
+ '@babel/core': 7.29.0
+ '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.0):
+ babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0):
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.0)
- core-js-compat: 3.43.0
+ '@babel/core': 7.29.0
+ '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0)
+ core-js-compat: 3.49.0
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.0):
+ babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0):
dependencies:
- '@babel/core': 7.28.0
- '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0)
+ core-js-compat: 3.49.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0)
transitivePeerDependencies:
- supports-color
@@ -10191,30 +10629,55 @@ snapshots:
babel-runtime: 6.26.0
babel-types: 6.26.0
- babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.0):
- dependencies:
- '@babel/core': 7.28.0
- '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.0)
- '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0)
- '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.0)
- '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.0)
- '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.0)
- '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.0)
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.0)
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.0)
- '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.0)
- '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.0)
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.0)
- '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.0)
- '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.0)
- '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.0)
-
- babel-preset-jest@29.6.3(@babel/core@7.28.0):
- dependencies:
- '@babel/core': 7.28.0
+ babel-preset-current-node-syntax@1.1.0(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0)
+ '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0)
+ '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0)
+ '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0)
+
+ babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0)
+ '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0)
+ '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0)
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0)
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0)
+ '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0)
+
+ babel-preset-jest@29.6.3(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
babel-plugin-jest-hoist: 29.6.3
- babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0)
+ babel-preset-current-node-syntax: 1.1.0(@babel/core@7.29.0)
+
+ babel-preset-jest@30.3.0(@babel/core@7.29.0):
+ dependencies:
+ '@babel/core': 7.29.0
+ babel-plugin-jest-hoist: 30.3.0
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
babel-runtime@6.26.0:
dependencies:
@@ -10227,7 +10690,7 @@ snapshots:
babel-traverse: 6.26.0
babel-types: 6.26.0
babylon: 6.18.0
- lodash: 4.17.21
+ lodash: 4.18.1
transitivePeerDependencies:
- supports-color
@@ -10241,7 +10704,7 @@ snapshots:
debug: 2.6.9
globals: 9.18.0
invariant: 2.2.4
- lodash: 4.17.21
+ lodash: 4.18.1
transitivePeerDependencies:
- supports-color
@@ -10249,7 +10712,7 @@ snapshots:
dependencies:
babel-runtime: 6.26.0
esutils: 2.0.3
- lodash: 4.17.21
+ lodash: 4.18.1
to-fast-properties: 1.0.3
babylon@6.18.0: {}
@@ -10258,10 +10721,18 @@ snapshots:
balanced-match@2.0.0: {}
+ balanced-match@4.0.2:
+ dependencies:
+ jackspeak: 4.2.3
+
base64-arraybuffer@1.0.2: {}
base64-js@1.5.1: {}
+ baseline-browser-mapping@2.10.16: {}
+
+ baseline-browser-mapping@2.9.19: {}
+
batch@0.6.1: {}
bcrypt-pbkdf@1.0.2:
@@ -10297,20 +10768,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- body-parser@2.2.0:
- dependencies:
- bytes: 3.1.2
- content-type: 1.0.5
- debug: 4.4.1
- http-errors: 2.0.0
- iconv-lite: 0.6.3
- on-finished: 2.4.1
- qs: 6.14.0
- raw-body: 3.0.0
- type-is: 2.0.1
- transitivePeerDependencies:
- - supports-color
-
bonjour-service@1.3.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -10329,14 +10786,22 @@ snapshots:
dependencies:
balanced-match: 1.0.2
+ brace-expansion@2.0.3:
+ dependencies:
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.2:
+ dependencies:
+ balanced-match: 4.0.2
+
braces@3.0.3:
dependencies:
fill-range: 7.1.1
- broadcast-channel@5.5.1:
+ broadcast-channel@7.1.0:
dependencies:
- '@babel/runtime': 7.23.2
- oblivious-set: 1.1.1
+ '@babel/runtime': 7.27.0
+ oblivious-set: 1.4.0
p-queue: 6.6.2
unload: 2.4.1
@@ -10349,12 +10814,30 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.1)
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.19
+ caniuse-lite: 1.0.30001769
+ electron-to-chromium: 1.5.286
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ browserslist@4.28.2:
+ dependencies:
+ baseline-browser-mapping: 2.10.16
+ caniuse-lite: 1.0.30001787
+ electron-to-chromium: 1.5.334
+ node-releases: 2.0.37
+ update-browserslist-db: 1.2.3(browserslist@4.28.2)
+
bser@2.1.1:
dependencies:
node-int64: 0.4.0
btoa@1.2.1: {}
+ buffer-crc32@0.2.13: {}
+
buffer-from@1.1.2: {}
buffer@5.7.1:
@@ -10457,13 +10940,17 @@ snapshots:
caniuse-api@3.0.0:
dependencies:
- browserslist: 4.25.1
- caniuse-lite: 1.0.30001726
+ browserslist: 4.28.2
+ caniuse-lite: 1.0.30001787
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
caniuse-lite@1.0.30001726: {}
+ caniuse-lite@1.0.30001769: {}
+
+ caniuse-lite@1.0.30001787: {}
+
canvas-exif-orientation@0.4.0: {}
canvg@1.5.3:
@@ -10501,15 +10988,6 @@ snapshots:
char-regex@1.0.2: {}
- check-node-version@4.2.1:
- dependencies:
- chalk: 3.0.0
- map-values: 1.0.1
- minimist: 1.2.8
- object-filter: 1.0.2
- run-parallel: 1.2.0
- semver: 6.3.1
-
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@@ -10524,17 +11002,19 @@ snapshots:
chownr@2.0.0: {}
+ chownr@3.0.0: {}
+
chrome-trace-event@1.0.4: {}
ci-info@3.9.0: {}
- ci-info@4.3.0: {}
+ ci-info@4.4.0: {}
- circular-dependency-plugin@5.2.2(webpack@5.99.9):
+ circular-dependency-plugin@5.2.2(webpack@5.104.1):
dependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
- cjs-module-lexer@1.4.3: {}
+ cjs-module-lexer@2.2.0: {}
clean-stack@2.2.0: {}
@@ -10558,9 +11038,7 @@ snapshots:
co@4.6.0: {}
- codemirror@5.58.2: {}
-
- collect-v8-coverage@1.0.2: {}
+ collect-v8-coverage@1.0.3: {}
color-convert@1.9.3:
dependencies:
@@ -10596,24 +11074,22 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ command-exists-promise@2.0.2: {}
+
commander@10.0.1: {}
commander@12.1.0: {}
commander@13.1.0: {}
+ commander@14.0.3: {}
+
commander@2.20.3: {}
commander@7.2.0: {}
- commander@9.5.0: {}
-
- common-path-prefix@3.0.0: {}
-
common-tags@1.8.2: {}
- commondir@1.0.1: {}
-
complex-esm@2.1.1-esm1: {}
compressible@2.0.18:
@@ -10649,41 +11125,33 @@ snapshots:
console-control-strings@1.1.0: {}
- consolidate@0.15.1(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(ejs@3.1.10)(lodash@4.17.21):
+ consolidate@0.15.1(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(lodash@4.18.1):
dependencies:
bluebird: 3.7.2
optionalDependencies:
- babel-core: 7.0.0-bridge.0(@babel/core@7.28.0)
+ babel-core: 7.0.0-bridge.0(@babel/core@7.29.0)
ejs: 3.1.10
- lodash: 4.17.21
+ lodash: 4.18.1
content-disposition@0.5.4:
dependencies:
safe-buffer: 5.2.1
- content-disposition@1.0.0:
- dependencies:
- safe-buffer: 5.2.1
-
content-type@1.0.5: {}
convert-source-map@2.0.0: {}
cookie-signature@1.0.6: {}
- cookie-signature@1.2.2: {}
-
cookie@0.7.1: {}
- cookie@0.7.2: {}
-
- core-js-compat@3.43.0:
+ core-js-compat@3.49.0:
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
core-js@2.6.12: {}
- core-js@3.44.0: {}
+ core-js@3.47.0: {}
core-util-is@1.0.2: {}
@@ -10709,21 +11177,6 @@ snapshots:
crc-32@1.2.2: {}
- create-jest@29.7.0(@types/node@24.1.0):
- dependencies:
- '@jest/types': 29.6.3
- chalk: 4.1.2
- exit: 0.1.2
- graceful-fs: 4.2.11
- jest-config: 29.7.0(@types/node@24.1.0)
- jest-util: 29.7.0
- prompts: 2.4.2
- transitivePeerDependencies:
- - '@types/node'
- - babel-plugin-macros
- - supports-color
- - ts-node
-
crelt@1.0.6: {}
cross-spawn@6.0.6:
@@ -10759,7 +11212,7 @@ snapshots:
dependencies:
utrie: 1.0.2
- css-loader@7.1.2(webpack@5.99.9):
+ css-loader@7.1.2(webpack@5.104.1):
dependencies:
icss-utils: 5.1.0(postcss@8.5.6)
postcss: 8.5.6
@@ -10770,17 +11223,17 @@ snapshots:
postcss-value-parser: 4.2.0
semver: 7.7.2
optionalDependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
- css-minimizer-webpack-plugin@7.0.2(webpack@5.99.9):
+ css-minimizer-webpack-plugin@7.0.4(webpack@5.104.1):
dependencies:
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/trace-mapping': 0.3.31
cssnano: 7.0.7(postcss@8.5.6)
- jest-worker: 29.7.0
+ jest-worker: 30.3.0
postcss: 8.5.6
- schema-utils: 4.3.2
+ schema-utils: 4.3.3
serialize-javascript: 6.0.2
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
css-select@5.2.2:
dependencies:
@@ -10820,7 +11273,7 @@ snapshots:
cssnano-preset-default@7.0.7(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
css-declaration-sorter: 7.2.0(postcss@8.5.6)
cssnano-utils: 5.0.1(postcss@8.5.6)
postcss: 8.5.6
@@ -10868,19 +11321,18 @@ snapshots:
cssom@0.3.8: {}
- cssom@0.5.0: {}
-
cssstyle@0.2.37:
dependencies:
cssom: 0.3.8
- cssstyle@2.3.0:
+ cssstyle@4.6.0:
dependencies:
- cssom: 0.3.8
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
csstype@3.1.3: {}
- csv-parse@5.6.0: {}
+ csv-parse@6.2.1: {}
csv-writer@1.6.0: {}
@@ -10893,11 +11345,10 @@ snapshots:
dependencies:
assert-plus: 1.0.0
- data-urls@3.0.2:
+ data-urls@5.0.0:
dependencies:
- abab: 2.0.6
- whatwg-mimetype: 3.0.0
- whatwg-url: 11.0.0
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
data-view-buffer@1.0.2:
dependencies:
@@ -10938,6 +11389,10 @@ snapshots:
dependencies:
ms: 2.1.3
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
decamelize-keys@1.1.1:
dependencies:
decamelize: 1.2.0
@@ -10947,11 +11402,11 @@ snapshots:
decamelize@5.0.1: {}
- decimal.js@10.5.0: {}
+ decimal.js@10.6.0: {}
decode-uri-component@0.2.2: {}
- dedent@1.6.0: {}
+ dedent@1.7.2: {}
deep-equal@2.2.3:
dependencies:
@@ -11038,8 +11493,6 @@ snapshots:
diacritics@1.3.0: {}
- diff-sequences@29.6.3: {}
-
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
@@ -11070,11 +11523,7 @@ snapshots:
domelementtype@2.3.0: {}
- domexception@4.0.0:
- dependencies:
- webidl-conversions: 7.0.0
-
- domhandler@5.0.3:
+ domhandler@5.0.3:
dependencies:
domelementtype: 2.3.0
@@ -11104,16 +11553,20 @@ snapshots:
'@one-ini/wasm': 0.1.1
commander: 10.0.1
minimatch: 9.0.1
- semver: 7.7.2
+ semver: 7.7.4
ee-first@1.1.1: {}
ejs@3.1.10:
dependencies:
- jake: 10.9.2
+ jake: 10.9.4
electron-to-chromium@1.5.178: {}
+ electron-to-chromium@1.5.286: {}
+
+ electron-to-chromium@1.5.334: {}
+
emittery@0.13.1: {}
emoji-regex@8.0.0: {}
@@ -11137,15 +11590,15 @@ snapshots:
memory-fs: 0.2.0
tapable: 0.1.10
- enhanced-resolve@5.18.2:
+ enhanced-resolve@5.19.0:
dependencies:
graceful-fs: 4.2.11
- tapable: 2.2.2
+ tapable: 2.3.0
- enquirer@2.4.1:
+ enhanced-resolve@5.20.1:
dependencies:
- ansi-colors: 4.1.3
- strip-ansi: 6.0.1
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
entities@4.5.0: {}
@@ -11159,17 +11612,17 @@ snapshots:
dependencies:
'@types/localforage': 0.0.34
'@xmldom/xmldom': 0.7.13
- core-js: 3.44.0
+ core-js: 3.47.0
event-emitter: 0.3.5
jszip: 3.10.1
localforage: 1.10.0
- lodash: 4.17.21
+ lodash: 4.18.1
marks-pane: 1.0.9
path-webpack: 0.0.3
err-code@2.0.3: {}
- error-ex@1.3.2:
+ error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -11230,6 +11683,63 @@ snapshots:
unbox-primitive: 1.1.0
which-typed-array: 1.1.19
+ es-abstract@1.24.1:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.3
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.20
+
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@@ -11246,7 +11756,7 @@ snapshots:
isarray: 2.0.5
stop-iteration-iterator: 1.1.0
- es-module-lexer@1.7.0: {}
+ es-module-lexer@2.0.0: {}
es-object-atoms@1.1.1:
dependencies:
@@ -11308,14 +11818,6 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
- escodegen@2.1.0:
- dependencies:
- esprima: 4.0.1
- estraverse: 5.3.0
- esutils: 2.0.3
- optionalDependencies:
- source-map: 0.6.1
-
eslint-config-prettier@10.1.8(eslint@8.57.1):
dependencies:
eslint: 8.57.1
@@ -11328,7 +11830,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-webpack@0.13.10(eslint-plugin-import@2.32.0)(webpack@5.99.9):
+ eslint-import-resolver-webpack@0.13.10(eslint-plugin-import@2.32.0)(webpack@5.104.1):
dependencies:
debug: 3.2.7
enhanced-resolve: 0.9.1
@@ -11338,10 +11840,10 @@ snapshots:
interpret: 1.4.0
is-core-module: 2.16.1
is-regex: 1.2.1
- lodash: 4.17.21
+ lodash: 4.18.1
resolve: 2.0.0-next.5
semver: 5.7.2
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
transitivePeerDependencies:
- supports-color
@@ -11351,7 +11853,7 @@ snapshots:
optionalDependencies:
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-webpack: 0.13.10(eslint-plugin-import@2.32.0)(webpack@5.99.9)
+ eslint-import-resolver-webpack: 0.13.10(eslint-plugin-import@2.32.0)(webpack@5.104.1)
transitivePeerDependencies:
- supports-color
@@ -11390,12 +11892,22 @@ snapshots:
optionalDependencies:
'@testing-library/dom': 9.3.4
- eslint-plugin-jest@28.14.0(eslint@8.57.1)(jest@29.7.0(@types/node@24.1.0))(typescript@5.8.3):
+ eslint-plugin-jest@28.14.0(eslint@8.57.1)(jest@30.3.0(@types/node@25.5.2))(typescript@5.8.3):
dependencies:
- '@typescript-eslint/utils': 8.35.1(eslint@8.57.1)(typescript@5.8.3)
+ '@typescript-eslint/utils': 8.47.0(eslint@8.57.1)(typescript@5.8.3)
eslint: 8.57.1
optionalDependencies:
- jest: 29.7.0(@types/node@24.1.0)
+ jest: 30.3.0(@types/node@25.5.2)
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ eslint-plugin-jest@29.1.0(eslint@8.57.1)(jest@30.3.0(@types/node@25.5.2))(typescript@5.8.3):
+ dependencies:
+ '@typescript-eslint/utils': 8.47.0(eslint@8.57.1)(typescript@5.8.3)
+ eslint: 8.57.1
+ optionalDependencies:
+ jest: 30.3.0(@types/node@25.5.2)
transitivePeerDependencies:
- supports-color
- typescript
@@ -11428,63 +11940,10 @@ snapshots:
esrecurse: 4.3.0
estraverse: 5.3.0
- eslint-utils@2.1.0:
- dependencies:
- eslint-visitor-keys: 1.3.0
-
- eslint-visitor-keys@1.3.0: {}
-
- eslint-visitor-keys@2.1.0: {}
-
eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.2.1: {}
- eslint@7.32.0:
- dependencies:
- '@babel/code-frame': 7.12.11
- '@eslint/eslintrc': 0.4.3
- '@humanwhocodes/config-array': 0.5.0
- ajv: 6.12.6
- chalk: 4.1.2
- cross-spawn: 7.0.6
- debug: 4.4.1
- doctrine: 3.0.0
- enquirer: 2.4.1
- escape-string-regexp: 4.0.0
- eslint-scope: 5.1.1
- eslint-utils: 2.1.0
- eslint-visitor-keys: 2.1.0
- espree: 7.3.1
- esquery: 1.6.0
- esutils: 2.0.3
- fast-deep-equal: 3.1.3
- file-entry-cache: 6.0.1
- functional-red-black-tree: 1.0.1
- glob-parent: 5.1.2
- globals: 13.24.0
- ignore: 4.0.6
- import-fresh: 3.3.1
- imurmurhash: 0.1.4
- is-glob: 4.0.3
- js-yaml: 3.14.1
- json-stable-stringify-without-jsonify: 1.0.1
- levn: 0.4.1
- lodash.merge: 4.6.2
- minimatch: 3.1.2
- natural-compare: 1.4.0
- optionator: 0.9.4
- progress: 2.0.3
- regexpp: 3.2.0
- semver: 7.7.2
- strip-ansi: 6.0.1
- strip-json-comments: 3.1.1
- table: 6.9.0
- text-table: 0.2.0
- v8-compile-cache: 2.4.0
- transitivePeerDependencies:
- - supports-color
-
eslint@8.57.1:
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1)
@@ -11495,7 +11954,7 @@ snapshots:
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
'@ungap/structured-clone': 1.3.0
- ajv: 6.12.6
+ ajv: 6.14.0
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.1
@@ -11535,12 +11994,6 @@ snapshots:
event-emitter: 0.3.5
type: 2.7.3
- espree@7.3.1:
- dependencies:
- acorn: 7.4.1
- acorn-jsx: 5.3.2(acorn@7.4.1)
- eslint-visitor-keys: 1.3.0
-
espree@9.6.1:
dependencies:
acorn: 8.15.0
@@ -11594,15 +12047,16 @@ snapshots:
exif-parser@0.1.12: {}
- exit@0.1.2: {}
+ exit-x@0.2.2: {}
- expect@29.7.0:
+ expect@30.3.0:
dependencies:
- '@jest/expect-utils': 29.7.0
- jest-get-type: 29.6.3
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-util: 29.7.0
+ '@jest/expect-utils': 30.3.0
+ '@jest/get-type': 30.1.0
+ jest-matcher-utils: 30.3.0
+ jest-message-util: 30.3.0
+ jest-mock: 30.3.0
+ jest-util: 30.3.0
express@4.21.2:
dependencies:
@@ -11640,38 +12094,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- express@5.1.0:
- dependencies:
- accepts: 2.0.0
- body-parser: 2.2.0
- content-disposition: 1.0.0
- content-type: 1.0.5
- cookie: 0.7.2
- cookie-signature: 1.2.2
- debug: 4.4.1
- encodeurl: 2.0.0
- escape-html: 1.0.3
- etag: 1.8.1
- finalhandler: 2.1.0
- fresh: 2.0.0
- http-errors: 2.0.0
- merge-descriptors: 2.0.0
- mime-types: 3.0.1
- on-finished: 2.4.1
- once: 1.4.0
- parseurl: 1.3.3
- proxy-addr: 2.0.7
- qs: 6.14.0
- range-parser: 1.2.1
- router: 2.2.0
- send: 1.2.0
- serve-static: 2.2.0
- statuses: 2.0.2
- type-is: 2.0.1
- vary: 1.1.2
- transitivePeerDependencies:
- - supports-color
-
ext@1.7.0:
dependencies:
type: 2.7.3
@@ -11688,7 +12110,7 @@ snapshots:
extsprintf@1.3.0: {}
- fake-indexeddb@5.0.2: {}
+ fake-indexeddb@6.2.5: {}
fast-deep-equal@3.1.3: {}
@@ -11704,7 +12126,7 @@ snapshots:
fast-levenshtein@2.0.6: {}
- fast-uri@3.0.6: {}
+ fast-uri@3.1.0: {}
fastest-levenshtein@1.0.16: {}
@@ -11728,11 +12150,11 @@ snapshots:
dependencies:
flat-cache: 3.2.0
- file-loader@6.2.0(webpack@5.99.9):
+ file-loader@6.2.0(webpack@5.104.1):
dependencies:
loader-utils: 2.0.4
schema-utils: 3.3.0
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
file-saver@2.0.1: {}
@@ -11748,7 +12170,7 @@ snapshots:
filelist@1.0.4:
dependencies:
- minimatch: 5.1.6
+ minimatch: 5.1.9
fill-range@7.1.1:
dependencies:
@@ -11766,39 +12188,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- finalhandler@2.1.0:
- dependencies:
- debug: 4.4.1
- encodeurl: 2.0.0
- escape-html: 1.0.3
- on-finished: 2.4.1
- parseurl: 1.3.3
- statuses: 2.0.2
- transitivePeerDependencies:
- - supports-color
-
find-babel-config@1.2.2:
dependencies:
json5: 1.0.2
path-exists: 3.0.0
- find-cache-dir@2.1.0:
- dependencies:
- commondir: 1.0.1
- make-dir: 2.1.0
- pkg-dir: 3.0.0
-
- find-cache-dir@4.0.0:
- dependencies:
- common-path-prefix: 3.0.0
- pkg-dir: 7.0.0
-
find-root@1.1.0: {}
- find-up@3.0.0:
- dependencies:
- locate-path: 3.0.0
-
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
@@ -11809,11 +12205,6 @@ snapshots:
locate-path: 6.0.0
path-exists: 4.0.0
- find-up@6.3.0:
- dependencies:
- locate-path: 7.2.0
- path-exists: 5.0.0
-
flat-cache@3.2.0:
dependencies:
flatted: 3.3.3
@@ -11824,13 +12215,9 @@ snapshots:
flatted@3.3.3: {}
- flow-parser@0.274.2: {}
-
flush-promises@1.0.2: {}
- follow-redirects@1.15.9: {}
-
- fontfaceobserver@2.3.0: {}
+ follow-redirects@1.15.11: {}
for-each@0.3.5:
dependencies:
@@ -11849,7 +12236,7 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.35
- form-data@4.0.4:
+ form-data@4.0.5:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
@@ -11861,17 +12248,17 @@ snapshots:
fraction.js@4.3.7: {}
+ fraction.js@5.3.4: {}
+
frame-throttle@3.0.0: {}
fresh@0.5.2: {}
- fresh@2.0.0: {}
-
fs-extra@9.1.0:
dependencies:
at-least-node: 1.0.0
graceful-fs: 4.2.11
- jsonfile: 6.1.0
+ jsonfile: 6.2.0
universalify: 2.0.1
fs-minipass@2.1.0:
@@ -11894,8 +12281,6 @@ snapshots:
hasown: 2.0.2
is-callable: 1.2.7
- functional-red-black-tree@1.0.1: {}
-
functions-have-names@1.2.3: {}
fuzzysearch@1.0.3: {}
@@ -11977,10 +12362,28 @@ snapshots:
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.5
- minipass: 7.1.2
+ minipass: 7.1.3
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@10.5.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.9
+ minipass: 7.1.3
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
+ glob@11.1.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 4.2.3
+ minimatch: 10.2.0
+ minipass: 7.1.3
+ package-json-from-dist: 1.0.1
+ path-scurry: 2.0.1
+
glob@7.1.7:
dependencies:
fs.realpath: 1.0.0
@@ -12004,7 +12407,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
- minimatch: 5.1.6
+ minimatch: 5.1.9
once: 1.4.0
global-modules@2.0.0:
@@ -12042,7 +12445,7 @@ snapshots:
globule@1.3.4:
dependencies:
glob: 7.1.7
- lodash: 4.17.21
+ lodash: 4.18.1
minimatch: 3.0.8
gonzales-pe@4.3.0:
@@ -12061,7 +12464,7 @@ snapshots:
har-validator@5.1.5:
dependencies:
- ajv: 6.12.6
+ ajv: 6.14.0
har-schema: 2.0.0
hard-rejection@2.1.0: {}
@@ -12117,9 +12520,9 @@ snapshots:
readable-stream: 2.3.8
wbuf: 1.7.3
- html-encoding-sniffer@3.0.0:
+ html-encoding-sniffer@4.0.0:
dependencies:
- whatwg-encoding: 2.0.0
+ whatwg-encoding: 3.1.1
html-entities@2.6.0: {}
@@ -12164,7 +12567,7 @@ snapshots:
dependencies:
'@tootallnate/once': 1.1.2
agent-base: 6.0.2
- debug: 4.4.1
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -12172,7 +12575,14 @@ snapshots:
dependencies:
'@tootallnate/once': 2.0.0
agent-base: 6.0.2
- debug: 4.4.1
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -12191,7 +12601,7 @@ snapshots:
http-proxy@1.18.1:
dependencies:
eventemitter3: 4.0.7
- follow-redirects: 1.15.9
+ follow-redirects: 1.15.11
requires-port: 1.0.0
transitivePeerDependencies:
- debug
@@ -12205,7 +12615,14 @@ snapshots:
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
- debug: 4.4.1
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -12243,8 +12660,6 @@ snapshots:
ieee754@1.2.1: {}
- ignore@4.0.6: {}
-
ignore@5.3.2: {}
image-q@4.0.0:
@@ -12286,7 +12701,7 @@ snapshots:
ini@1.3.8: {}
- ini@5.0.0: {}
+ ini@6.0.0: {}
inline-style-prefixer@4.0.2:
dependencies:
@@ -12449,8 +12864,6 @@ snapshots:
is-potential-custom-element-name@1.0.1: {}
- is-promise@4.0.0: {}
-
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@@ -12502,8 +12915,6 @@ snapshots:
dependencies:
is-inside-container: 1.0.0
- isarray@0.0.1: {}
-
isarray@1.0.0: {}
isarray@2.0.5: {}
@@ -12525,8 +12936,8 @@ snapshots:
istanbul-lib-instrument@5.2.1:
dependencies:
- '@babel/core': 7.28.0
- '@babel/parser': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 6.3.1
@@ -12535,11 +12946,11 @@ snapshots:
istanbul-lib-instrument@6.0.3:
dependencies:
- '@babel/core': 7.28.0
- '@babel/parser': 7.28.0
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.2
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
- semver: 7.7.2
+ semver: 7.7.4
transitivePeerDependencies:
- supports-color
@@ -12549,15 +12960,15 @@ snapshots:
make-dir: 4.0.0
supports-color: 7.2.0
- istanbul-lib-source-maps@4.0.1:
+ istanbul-lib-source-maps@5.0.6:
dependencies:
- debug: 4.4.1
+ '@jridgewell/trace-mapping': 0.3.31
+ debug: 4.4.3
istanbul-lib-coverage: 3.2.2
- source-map: 0.6.1
transitivePeerDependencies:
- supports-color
- istanbul-reports@3.1.7:
+ istanbul-reports@3.2.0:
dependencies:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
@@ -12568,152 +12979,142 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
- jake@10.9.2:
+ jackspeak@4.2.3:
+ dependencies:
+ '@isaacs/cliui': 9.0.0
+
+ jake@10.9.4:
dependencies:
async: 3.2.6
- chalk: 4.1.2
filelist: 1.0.4
- minimatch: 3.1.2
+ picocolors: 1.1.1
- jest-changed-files@29.7.0:
+ jest-changed-files@30.3.0:
dependencies:
execa: 5.1.1
- jest-util: 29.7.0
+ jest-util: 30.3.0
p-limit: 3.1.0
- jest-circus@29.7.0:
+ jest-circus@30.3.0:
dependencies:
- '@jest/environment': 29.7.0
- '@jest/expect': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@jest/environment': 30.3.0
+ '@jest/expect': 30.3.0
+ '@jest/test-result': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
chalk: 4.1.2
co: 4.6.0
- dedent: 1.6.0
+ dedent: 1.7.2
is-generator-fn: 2.1.0
- jest-each: 29.7.0
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-runtime: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
+ jest-each: 30.3.0
+ jest-matcher-utils: 30.3.0
+ jest-message-util: 30.3.0
+ jest-runtime: 30.3.0
+ jest-snapshot: 30.3.0
+ jest-util: 30.3.0
p-limit: 3.1.0
- pretty-format: 29.7.0
- pure-rand: 6.1.0
+ pretty-format: 30.3.0
+ pure-rand: 7.0.1
slash: 3.0.0
stack-utils: 2.0.6
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
- jest-cli@29.7.0(@types/node@24.1.0):
+ jest-cli@30.3.0(@types/node@25.5.2):
dependencies:
- '@jest/core': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
+ '@jest/core': 30.3.0
+ '@jest/test-result': 30.3.0
+ '@jest/types': 30.3.0
chalk: 4.1.2
- create-jest: 29.7.0(@types/node@24.1.0)
- exit: 0.1.2
+ exit-x: 0.2.2
import-local: 3.2.0
- jest-config: 29.7.0(@types/node@24.1.0)
- jest-util: 29.7.0
- jest-validate: 29.7.0
+ jest-config: 30.3.0(@types/node@25.5.2)
+ jest-util: 30.3.0
+ jest-validate: 30.3.0
yargs: 17.7.2
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
+ - esbuild-register
- supports-color
- ts-node
- jest-config@29.7.0(@types/node@24.1.0):
+ jest-config@30.3.0(@types/node@25.5.2):
dependencies:
- '@babel/core': 7.28.0
- '@jest/test-sequencer': 29.7.0
- '@jest/types': 29.6.3
- babel-jest: 29.7.0(@babel/core@7.28.0)
+ '@babel/core': 7.29.0
+ '@jest/get-type': 30.1.0
+ '@jest/pattern': 30.0.1
+ '@jest/test-sequencer': 30.3.0
+ '@jest/types': 30.3.0
+ babel-jest: 30.3.0(@babel/core@7.29.0)
chalk: 4.1.2
- ci-info: 3.9.0
+ ci-info: 4.4.0
deepmerge: 4.3.1
- glob: 7.2.3
+ glob: 10.5.0
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.8
+ jest-circus: 30.3.0
+ jest-docblock: 30.2.0
+ jest-environment-node: 30.3.0
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.3.0
+ jest-runner: 30.3.0
+ jest-util: 30.3.0
+ jest-validate: 30.3.0
parse-json: 5.2.0
- pretty-format: 29.7.0
+ pretty-format: 30.3.0
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
- jest-diff@29.7.0:
+ jest-diff@30.3.0:
dependencies:
+ '@jest/diff-sequences': 30.3.0
+ '@jest/get-type': 30.1.0
chalk: 4.1.2
- diff-sequences: 29.6.3
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
+ pretty-format: 30.3.0
- jest-docblock@29.7.0:
+ jest-docblock@30.2.0:
dependencies:
detect-newline: 3.1.0
- jest-each@29.7.0:
- dependencies:
- '@jest/types': 29.6.3
- chalk: 4.1.2
- jest-get-type: 29.6.3
- jest-util: 29.7.0
- pretty-format: 29.7.0
-
- jest-each@30.0.5:
+ jest-each@30.3.0:
dependencies:
- '@jest/get-type': 30.0.1
- '@jest/types': 30.0.5
+ '@jest/get-type': 30.1.0
+ '@jest/types': 30.3.0
chalk: 4.1.2
- jest-util: 30.0.5
- pretty-format: 30.0.5
+ jest-util: 30.3.0
+ pretty-format: 30.3.0
- jest-environment-jsdom@29.7.0:
+ jest-environment-jsdom@30.3.0:
dependencies:
- '@jest/environment': 29.7.0
- '@jest/fake-timers': 29.7.0
- '@jest/types': 29.6.3
- '@types/jsdom': 20.0.1
- '@types/node': 24.0.10
- jest-mock: 29.7.0
- jest-util: 29.7.0
- jsdom: 20.0.3
+ '@jest/environment': 30.3.0
+ '@jest/environment-jsdom-abstract': 30.3.0(jsdom@26.1.0)
+ jsdom: 26.1.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- jest-environment-node@29.7.0:
+ jest-environment-node@30.3.0:
dependencies:
- '@jest/environment': 29.7.0
- '@jest/fake-timers': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
- jest-mock: 29.7.0
- jest-util: 29.7.0
-
- jest-get-type@29.6.3: {}
+ '@jest/environment': 30.3.0
+ '@jest/fake-timers': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
+ jest-mock: 30.3.0
+ jest-util: 30.3.0
+ jest-validate: 30.3.0
jest-haste-map@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -12725,111 +13126,126 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
- jest-leak-detector@29.7.0:
+ jest-haste-map@30.3.0:
dependencies:
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
+ anymatch: 3.1.3
+ fb-watchman: 2.0.2
+ graceful-fs: 4.2.11
+ jest-regex-util: 30.0.1
+ jest-util: 30.3.0
+ jest-worker: 30.3.0
+ picomatch: 4.0.4
+ walker: 1.0.8
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ jest-leak-detector@30.3.0:
+ dependencies:
+ '@jest/get-type': 30.1.0
+ pretty-format: 30.3.0
- jest-matcher-utils@29.7.0:
+ jest-matcher-utils@30.3.0:
dependencies:
+ '@jest/get-type': 30.1.0
chalk: 4.1.2
- jest-diff: 29.7.0
- jest-get-type: 29.6.3
- pretty-format: 29.7.0
+ jest-diff: 30.3.0
+ pretty-format: 30.3.0
- jest-message-util@29.7.0:
+ jest-message-util@30.3.0:
dependencies:
- '@babel/code-frame': 7.27.1
- '@jest/types': 29.6.3
+ '@babel/code-frame': 7.29.0
+ '@jest/types': 30.3.0
'@types/stack-utils': 2.0.3
chalk: 4.1.2
graceful-fs: 4.2.11
- micromatch: 4.0.8
- pretty-format: 29.7.0
+ picomatch: 4.0.4
+ pretty-format: 30.3.0
slash: 3.0.0
stack-utils: 2.0.6
- jest-mock@29.7.0:
+ jest-mock@30.3.0:
dependencies:
- '@jest/types': 29.6.3
- '@types/node': 24.0.10
- jest-util: 29.7.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
+ jest-util: 30.3.0
- jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
+ jest-pnp-resolver@1.2.3(jest-resolve@30.3.0):
optionalDependencies:
- jest-resolve: 29.7.0
+ jest-resolve: 30.3.0
jest-regex-util@29.6.3: {}
jest-regex-util@30.0.1: {}
- jest-resolve-dependencies@29.7.0:
+ jest-resolve-dependencies@30.3.0:
dependencies:
- jest-regex-util: 29.6.3
- jest-snapshot: 29.7.0
+ jest-regex-util: 30.0.1
+ jest-snapshot: 30.3.0
transitivePeerDependencies:
- supports-color
- jest-resolve@29.7.0:
+ jest-resolve@30.3.0:
dependencies:
chalk: 4.1.2
graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0)
- jest-util: 29.7.0
- jest-validate: 29.7.0
- resolve: 1.22.10
- resolve.exports: 2.0.3
+ jest-haste-map: 30.3.0
+ jest-pnp-resolver: 1.2.3(jest-resolve@30.3.0)
+ jest-util: 30.3.0
+ jest-validate: 30.3.0
slash: 3.0.0
+ unrs-resolver: 1.11.1
- jest-runner@29.7.0:
+ jest-runner@30.3.0:
dependencies:
- '@jest/console': 29.7.0
- '@jest/environment': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@jest/console': 30.3.0
+ '@jest/environment': 30.3.0
+ '@jest/test-result': 30.3.0
+ '@jest/transform': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
chalk: 4.1.2
emittery: 0.13.1
+ exit-x: 0.2.2
graceful-fs: 4.2.11
- jest-docblock: 29.7.0
- jest-environment-node: 29.7.0
- jest-haste-map: 29.7.0
- jest-leak-detector: 29.7.0
- jest-message-util: 29.7.0
- jest-resolve: 29.7.0
- jest-runtime: 29.7.0
- jest-util: 29.7.0
- jest-watcher: 29.7.0
- jest-worker: 29.7.0
+ jest-docblock: 30.2.0
+ jest-environment-node: 30.3.0
+ jest-haste-map: 30.3.0
+ jest-leak-detector: 30.3.0
+ jest-message-util: 30.3.0
+ jest-resolve: 30.3.0
+ jest-runtime: 30.3.0
+ jest-util: 30.3.0
+ jest-watcher: 30.3.0
+ jest-worker: 30.3.0
p-limit: 3.1.0
source-map-support: 0.5.13
transitivePeerDependencies:
- supports-color
- jest-runtime@29.7.0:
+ jest-runtime@30.3.0:
dependencies:
- '@jest/environment': 29.7.0
- '@jest/fake-timers': 29.7.0
- '@jest/globals': 29.7.0
- '@jest/source-map': 29.6.3
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@jest/environment': 30.3.0
+ '@jest/fake-timers': 30.3.0
+ '@jest/globals': 30.3.0
+ '@jest/source-map': 30.0.1
+ '@jest/test-result': 30.3.0
+ '@jest/transform': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
chalk: 4.1.2
- cjs-module-lexer: 1.4.3
- collect-v8-coverage: 1.0.2
- glob: 7.2.3
+ cjs-module-lexer: 2.2.0
+ collect-v8-coverage: 1.0.3
+ glob: 10.5.0
graceful-fs: 4.2.11
- jest-haste-map: 29.7.0
- jest-message-util: 29.7.0
- jest-mock: 29.7.0
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
+ jest-haste-map: 30.3.0
+ jest-message-util: 30.3.0
+ jest-mock: 30.3.0
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.3.0
+ jest-snapshot: 30.3.0
+ jest-util: 30.3.0
slash: 3.0.0
strip-bom: 4.0.0
transitivePeerDependencies:
@@ -12839,99 +13255,109 @@ snapshots:
dependencies:
pretty: 2.0.0
- jest-snapshot@29.7.0:
- dependencies:
- '@babel/core': 7.28.0
- '@babel/generator': 7.28.0
- '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0)
- '@babel/types': 7.28.0
- '@jest/expect-utils': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0)
+ jest-snapshot@30.3.0:
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/types': 7.29.0
+ '@jest/expect-utils': 30.3.0
+ '@jest/get-type': 30.1.0
+ '@jest/snapshot-utils': 30.3.0
+ '@jest/transform': 30.3.0
+ '@jest/types': 30.3.0
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
chalk: 4.1.2
- expect: 29.7.0
+ expect: 30.3.0
graceful-fs: 4.2.11
- jest-diff: 29.7.0
- jest-get-type: 29.6.3
- jest-matcher-utils: 29.7.0
- jest-message-util: 29.7.0
- jest-util: 29.7.0
- natural-compare: 1.4.0
- pretty-format: 29.7.0
- semver: 7.7.2
+ jest-diff: 30.3.0
+ jest-matcher-utils: 30.3.0
+ jest-message-util: 30.3.0
+ jest-util: 30.3.0
+ pretty-format: 30.3.0
+ semver: 7.7.4
+ synckit: 0.11.12
transitivePeerDependencies:
- supports-color
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
picomatch: 2.3.1
- jest-util@30.0.5:
+ jest-util@30.3.0:
dependencies:
- '@jest/types': 30.0.5
- '@types/node': 24.1.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
chalk: 4.1.2
- ci-info: 4.3.0
+ ci-info: 4.4.0
graceful-fs: 4.2.11
- picomatch: 4.0.3
+ picomatch: 4.0.4
- jest-validate@29.7.0:
+ jest-validate@30.3.0:
dependencies:
- '@jest/types': 29.6.3
+ '@jest/get-type': 30.1.0
+ '@jest/types': 30.3.0
camelcase: 6.3.0
chalk: 4.1.2
- jest-get-type: 29.6.3
leven: 3.1.0
- pretty-format: 29.7.0
+ pretty-format: 30.3.0
- jest-watcher@29.7.0:
+ jest-watcher@30.3.0:
dependencies:
- '@jest/test-result': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 24.1.0
+ '@jest/test-result': 30.3.0
+ '@jest/types': 30.3.0
+ '@types/node': 25.5.2
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
- jest-util: 29.7.0
+ jest-util: 30.3.0
string-length: 4.0.2
jest-worker@27.5.1:
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
merge-stream: 2.0.0
supports-color: 8.1.1
jest-worker@29.7.0:
dependencies:
- '@types/node': 24.1.0
+ '@types/node': 25.5.2
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
- jest@29.7.0(@types/node@24.1.0):
+ jest-worker@30.3.0:
dependencies:
- '@jest/core': 29.7.0
- '@jest/types': 29.6.3
+ '@types/node': 25.5.2
+ '@ungap/structured-clone': 1.3.0
+ jest-util: 30.3.0
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
+ jest@30.3.0(@types/node@25.5.2):
+ dependencies:
+ '@jest/core': 30.3.0
+ '@jest/types': 30.3.0
import-local: 3.2.0
- jest-cli: 29.7.0(@types/node@24.1.0)
+ jest-cli: 30.3.0(@types/node@25.5.2)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
+ - esbuild-register
- supports-color
- ts-node
jiti@1.21.7: {}
- jpeg-js@0.4.4: {}
+ jiti@2.6.1: {}
- jquery@2.2.4: {}
+ jpeg-js@0.4.4: {}
js-base64@2.6.4: {}
@@ -12964,59 +13390,28 @@ snapshots:
jsbn@1.1.0: {}
- jscodeshift@17.3.0(@babel/preset-env@7.28.0(@babel/core@7.28.0)):
- dependencies:
- '@babel/core': 7.28.0
- '@babel/parser': 7.28.0
- '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.0)
- '@babel/preset-flow': 7.27.1(@babel/core@7.28.0)
- '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0)
- '@babel/register': 7.27.1(@babel/core@7.28.0)
- flow-parser: 0.274.2
- graceful-fs: 4.2.11
- micromatch: 4.0.8
- neo-async: 2.6.2
- picocolors: 1.1.1
- recast: 0.23.11
- tmp: 0.2.3
- write-file-atomic: 5.0.1
- optionalDependencies:
- '@babel/preset-env': 7.28.0(@babel/core@7.28.0)
- transitivePeerDependencies:
- - supports-color
-
- jsdom@20.0.3:
+ jsdom@26.1.0:
dependencies:
- abab: 2.0.6
- acorn: 8.15.0
- acorn-globals: 7.0.1
- cssom: 0.5.0
- cssstyle: 2.3.0
- data-urls: 3.0.2
- decimal.js: 10.5.0
- domexception: 4.0.0
- escodegen: 2.1.0
- form-data: 4.0.4
- html-encoding-sniffer: 3.0.0
- http-proxy-agent: 5.0.0
- https-proxy-agent: 5.0.1
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.20
+ nwsapi: 2.2.23
parse5: 7.3.0
+ rrweb-cssom: 0.8.0
saxes: 6.0.0
symbol-tree: 3.2.4
- tough-cookie: 4.1.4
- w3c-xmlserializer: 4.0.0
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
webidl-conversions: 7.0.0
- whatwg-encoding: 2.0.0
- whatwg-mimetype: 3.0.0
- whatwg-url: 11.0.0
- ws: 8.18.3
- xml-name-validator: 4.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.20.0
+ xml-name-validator: 5.0.0
transitivePeerDependencies:
- bufferutil
- supports-color
@@ -13042,8 +13437,6 @@ snapshots:
whatwg-url: 2.0.1
xml-name-validator: 2.0.1
- jsesc@3.0.2: {}
-
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
@@ -13068,7 +13461,7 @@ snapshots:
json5@2.2.3: {}
- jsonfile@6.1.0:
+ jsonfile@6.2.0:
dependencies:
universalify: 2.0.1
optionalDependencies:
@@ -13111,36 +13504,126 @@ snapshots:
kind-of@6.0.3: {}
- kleur@3.0.3: {}
-
known-css-properties@0.29.0: {}
known-css-properties@0.36.0: {}
- kolibri-constants@0.2.12: {}
-
- kolibri-constants@0.2.9: {}
-
- kolibri-design-system@5.0.1:
+ kolibri-build@1.0.0(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(file-loader@6.2.0(webpack@5.104.1))(postcss@8.5.6)(typescript@5.8.3)(vue-template-compiler@2.7.16)(webpack@5.104.1):
dependencies:
- aphrodite: https://codeload.github.com/learningequality/aphrodite/tar.gz/fdc8d7be8912a5cf17f74ff10f124013c52c3e32
- autosize: 3.0.21
- color: 4.2.3
- css-element-queries: 1.2.0
- date-fns: 1.30.1
- frame-throttle: 3.0.0
- fuzzysearch: 1.0.3
- lodash: 4.17.21
- node-glob: 1.2.0
- popper.js: 1.16.1
- purecss: 2.2.0
- tippy.js: 4.3.5
- vue: 2.7.16
- vue-intl: 3.1.0(vue@2.7.16)
- vue2-teleport: 1.1.4
- xstate: 4.38.3
+ '@babel/core': 7.29.0
+ '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0)
+ '@babel/preset-env': 7.29.2(@babel/core@7.29.0)
+ autoprefixer: 10.4.23(postcss@8.5.6)
+ babel-loader: 10.1.1(@babel/core@7.29.0)(webpack@5.104.1)
+ browserslist-config-kolibri: 0.18.0
+ commander: 14.0.3
+ core-js: 3.47.0
+ css-loader: 7.1.2(webpack@5.104.1)
+ css-minimizer-webpack-plugin: 7.0.4(webpack@5.104.1)
+ enhanced-resolve: 5.20.1
+ kolibri-glob: 1.0.0
+ kolibri-logging: 1.0.0
+ launch-editor-middleware: 2.13.2
+ lodash: 4.18.1
+ mini-css-extract-plugin: 2.9.4(webpack@5.104.1)
+ node-sass: 9.0.0
+ postcss-loader: 8.2.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.104.1)
+ process: 0.11.10
+ rtlcss: 4.3.0
+ sass-loader: 16.0.7(node-sass@9.0.0)(webpack@5.104.1)
+ strip-ansi: 6.0.1
+ style-loader: 4.0.0(webpack@5.104.1)
+ temp: 0.8.4
+ terser-webpack-plugin: 5.3.16(webpack@5.104.1)
+ url-loader: 4.1.1(file-loader@6.2.0(webpack@5.104.1))(webpack@5.104.1)
+ vue-loader: 15.11.1(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(css-loader@7.1.2(webpack@5.104.1))(ejs@3.1.10)(lodash@4.18.1)(vue-template-compiler@2.7.16)(webpack@5.104.1)
+ vue-style-loader: 4.1.3
+ vue-template-compiler: 2.7.16
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-cli: 7.0.2(webpack-dev-server@5.2.2)(webpack@5.104.1)
+ webpack-dev-server: 5.2.2(webpack-cli@7.0.2)(webpack@5.104.1)
+ webpack-merge: 6.0.1
+ transitivePeerDependencies:
+ - '@parcel/css'
+ - '@rspack/core'
+ - '@swc/core'
+ - '@swc/css'
+ - '@vue/compiler-sfc'
+ - arc-templates
+ - atpl
+ - babel-core
+ - bluebird
+ - bracket-template
+ - bufferutil
+ - cache-loader
+ - clean-css
+ - coffee-script
+ - csso
+ - debug
+ - dot
+ - dust
+ - dustjs-helpers
+ - dustjs-linkedin
+ - eco
+ - ect
+ - ejs
+ - esbuild
+ - file-loader
+ - haml-coffee
+ - hamlet
+ - hamljs
+ - handlebars
+ - hogan.js
+ - htmling
+ - jade
+ - jazz
+ - jqtpl
+ - just
+ - lightningcss
+ - liquid-node
+ - liquor
+ - marko
+ - mote
+ - mustache
+ - nunjucks
+ - plates
+ - postcss
+ - prettier
+ - pug
+ - qejs
+ - ractive
+ - razor-tmpl
+ - react
+ - react-dom
+ - sass
+ - sass-embedded
+ - slm
+ - squirrelly
+ - supports-color
+ - swig
+ - swig-templates
+ - teacup
+ - templayed
+ - then-jade
+ - then-pug
+ - tinyliquid
+ - toffee
+ - twig
+ - twing
+ - typescript
+ - uglify-js
+ - underscore
+ - utf-8-validate
+ - vash
+ - velocityjs
+ - walrus
+ - webpack-bundle-analyzer
+ - whiskers
- kolibri-design-system@5.3.0:
+ kolibri-constants@0.2.12: {}
+
+ kolibri-design-system@5.6.0:
dependencies:
aphrodite: https://codeload.github.com/learningequality/aphrodite/tar.gz/fdc8d7be8912a5cf17f74ff10f124013c52c3e32
autosize: 3.0.21
@@ -13149,7 +13632,7 @@ snapshots:
date-fns: 1.30.1
frame-throttle: 3.0.0
fuzzysearch: 1.0.3
- lodash: 4.17.21
+ lodash: 4.17.23
node-glob: 1.2.0
popper.js: 1.16.1
purecss: 2.2.0
@@ -13159,14 +13642,14 @@ snapshots:
vue2-teleport: 1.1.4
xstate: 4.38.3
- kolibri-format@1.0.1(@testing-library/dom@9.3.4)(eslint-import-resolver-webpack@0.13.10)(jest@29.7.0(@types/node@24.1.0))(postcss@8.5.6)(typescript@5.8.3):
+ kolibri-format@1.0.1(@testing-library/dom@9.3.4)(eslint-import-resolver-webpack@0.13.10)(jest@30.3.0(@types/node@25.5.2))(postcss@8.5.6)(typescript@5.8.3):
dependencies:
chalk: 4.1.2
commander: 13.1.0
eslint: 8.57.1
eslint-config-prettier: 10.1.8(eslint@8.57.1)
eslint-plugin-import: 2.32.0(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1)
- eslint-plugin-jest: 28.14.0(eslint@8.57.1)(jest@29.7.0(@types/node@24.1.0))(typescript@5.8.3)
+ eslint-plugin-jest: 28.14.0(eslint@8.57.1)(jest@30.3.0(@types/node@25.5.2))(typescript@5.8.3)
eslint-plugin-jest-dom: 5.5.0(@testing-library/dom@9.3.4)(eslint@8.57.1)
eslint-plugin-kolibri: 0.18.0
eslint-plugin-vue: 9.33.0(eslint@8.57.1)
@@ -13197,95 +13680,46 @@ snapshots:
- supports-color
- typescript
- kolibri-logging@1.0.0:
+ kolibri-glob@1.0.0:
dependencies:
- chalk: 4.1.2
- loglevel: 1.9.2
+ fast-glob: 3.3.3
- kolibri-tools@0.18.2(@testing-library/dom@9.3.4)(@types/node@24.1.0)(ejs@3.1.10)(eslint-import-resolver-webpack@0.13.10)(file-loader@6.2.0(webpack@5.99.9))(postcss@8.5.6)(typescript@5.8.3)(vue@2.7.16):
+ kolibri-i18n@1.0.0(@testing-library/dom@9.3.4)(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(eslint-import-resolver-webpack@0.13.10)(file-loader@6.2.0(webpack@5.104.1))(jest@30.3.0(@types/node@25.5.2))(postcss@8.5.6)(typescript@5.8.3)(vue-template-compiler@2.7.16)(webpack@5.104.1):
dependencies:
- '@babel/core': 7.28.0
- '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.0)
- '@babel/plugin-transform-runtime': 7.28.0(@babel/core@7.28.0)
- '@babel/preset-env': 7.28.0(@babel/core@7.28.0)
- '@rushstack/eslint-patch': 1.12.0
- '@testing-library/jest-dom': 6.6.3
- '@testing-library/user-event': 14.6.1(@testing-library/dom@9.3.4)
- '@testing-library/vue': 5.9.0(vue-template-compiler@2.7.16)(vue@2.7.16)
- '@vue/test-utils': 1.3.6(vue-template-compiler@2.7.16)(vue@2.7.16)
ast-traverse: 0.1.1
- autoprefixer: 10.4.21(postcss@8.5.6)
- babel-core: 7.0.0-bridge.0(@babel/core@7.28.0)
- babel-jest: 29.7.0(@babel/core@7.28.0)
- babel-loader: 10.0.0(@babel/core@7.28.0)(webpack@5.99.9)
- browserslist-config-kolibri: 0.18.0
- chalk: 4.1.2
- check-node-version: 4.2.1
cli-table: 0.3.11
- commander: 13.1.0
- core-js: 3.44.0
- css-loader: 7.1.2(webpack@5.99.9)
- css-minimizer-webpack-plugin: 7.0.2(webpack@5.99.9)
- csv-parse: 5.6.0
+ commander: 14.0.3
+ csv-parse: 6.2.1
csv-writer: 1.6.0
del: 6.1.1
- enhanced-resolve: 5.18.2
- express: 5.1.0
- fast-glob: 3.3.3
- ini: 5.0.0
- jest: 29.7.0(@types/node@24.1.0)
- jest-environment-jsdom: 29.7.0
- jest-serializer-vue: 3.1.0
- jscodeshift: 17.3.0(@babel/preset-env@7.28.0(@babel/core@7.28.0))
- kolibri: 0.18.0
- kolibri-format: 1.0.1(@testing-library/dom@9.3.4)(eslint-import-resolver-webpack@0.13.10)(jest@29.7.0(@types/node@24.1.0))(postcss@8.5.6)(typescript@5.8.3)
- launch-editor-middleware: 2.10.0
- lodash: 4.17.21
- mini-css-extract-plugin: 2.9.2(webpack@5.99.9)
- node-sass: 9.0.0
- postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.99.9)
- process: 0.11.10
- query-ast: 1.0.5
- readline-sync: 1.4.10
+ enhanced-resolve: 5.20.1
+ ini: 6.0.0
+ kolibri-build: 1.0.0(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(file-loader@6.2.0(webpack@5.104.1))(postcss@8.5.6)(typescript@5.8.3)(vue-template-compiler@2.7.16)(webpack@5.104.1)
+ kolibri-format: 1.0.1(@testing-library/dom@9.3.4)(eslint-import-resolver-webpack@0.13.10)(jest@30.3.0(@types/node@25.5.2))(postcss@8.5.6)(typescript@5.8.3)
+ kolibri-glob: 1.0.0
+ kolibri-logging: 1.0.0
+ lodash: 4.18.1
recast: 0.23.11
- rewire: 6.0.0
- rtlcss: 4.3.0
- sass-loader: 16.0.5(node-sass@9.0.0)(webpack@5.99.9)
- semver: 7.7.2
- strip-ansi: 6.0.1
- style-loader: 4.0.0(webpack@5.99.9)
- temp: 0.8.4
- terser-webpack-plugin: 5.3.14(webpack@5.99.9)
toml: 3.0.0
- url-loader: 4.1.1(file-loader@6.2.0(webpack@5.99.9))(webpack@5.99.9)
- vue-jest: 3.0.7(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(vue-template-compiler@2.7.16)(vue@2.7.16)
- vue-loader: 15.11.1(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(css-loader@7.1.2(webpack@5.99.9))(ejs@3.1.10)(lodash@4.17.21)(vue-template-compiler@2.7.16)(webpack@5.99.9)
vue-sfc-descriptor-to-string: 1.0.0
- vue-style-loader: 4.1.3
vue-template-compiler: 2.7.16
- webpack: 5.99.9(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
- webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.99.9)
- webpack-merge: 6.0.1
+ webpack: 5.104.1(webpack-cli@6.0.1)
transitivePeerDependencies:
- '@parcel/css'
- '@rspack/core'
- '@swc/core'
- '@swc/css'
- '@testing-library/dom'
- - '@types/node'
- '@typescript-eslint/eslint-plugin'
- '@typescript-eslint/parser'
- '@vue/compiler-sfc'
- - '@vue/composition-api'
- arc-templates
- atpl
- - babel-plugin-macros
+ - babel-core
- bluebird
- bracket-template
- bufferutil
- cache-loader
- - canvas
- clean-css
- coffee-script
- csso
@@ -13309,6 +13743,7 @@ snapshots:
- htmling
- jade
- jazz
+ - jest
- jqtpl
- just
- lightningcss
@@ -13317,7 +13752,6 @@ snapshots:
- marko
- mote
- mustache
- - node-notifier
- nunjucks
- plates
- postcss
@@ -13341,7 +13775,6 @@ snapshots:
- then-pug
- tinyliquid
- toffee
- - ts-node
- twig
- twing
- typescript
@@ -13350,42 +13783,29 @@ snapshots:
- utf-8-validate
- vash
- velocityjs
- - vue
- walrus
- webpack-bundle-analyzer
- whiskers
- kolibri@0.18.0:
+ kolibri-logging@1.0.0:
dependencies:
- '@vueuse/core': 11.3.0(vue@2.7.16)
- axios: 1.11.0
- fontfaceobserver: 2.3.0
- frame-throttle: 3.0.0
- intl: 1.2.5
- kolibri-constants: 0.2.9
- kolibri-design-system: 5.0.1
- lockr: 0.8.5
- lodash: 4.17.21
- path-to-regexp: 1.9.0
- ua-parser-js: 1.0.40
- vue: 2.7.16
- vue-intl: 3.1.0(vue@2.7.16)
- vue-meta: 2.4.0
- vue-router: 3.6.5(vue@2.7.16)
- vuex: 3.6.2(vue@2.7.16)
- transitivePeerDependencies:
- - '@vue/composition-api'
- - debug
+ chalk: 4.1.2
+ loglevel: 1.9.2
- launch-editor-middleware@2.10.0:
+ launch-editor-middleware@2.13.2:
dependencies:
- launch-editor: 2.10.0
+ launch-editor: 2.13.2
launch-editor@2.10.0:
dependencies:
picocolors: 1.1.1
shell-quote: 1.8.3
+ launch-editor@2.13.2:
+ dependencies:
+ picocolors: 1.1.1
+ shell-quote: 1.8.3
+
leven@3.1.0: {}
levn@0.3.0:
@@ -13414,7 +13834,7 @@ snapshots:
dependencies:
uc.micro: 2.1.0
- linkifyjs@4.3.1: {}
+ linkifyjs@4.3.2: {}
load-json-file@4.0.0:
dependencies:
@@ -13423,7 +13843,7 @@ snapshots:
pify: 3.0.0
strip-bom: 3.0.0
- loader-runner@4.3.0: {}
+ loader-runner@4.3.1: {}
loader-utils@1.4.2:
dependencies:
@@ -13441,11 +13861,6 @@ snapshots:
dependencies:
lie: 3.1.1
- locate-path@3.0.0:
- dependencies:
- p-locate: 3.0.0
- path-exists: 3.0.0
-
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
@@ -13454,12 +13869,6 @@ snapshots:
dependencies:
p-locate: 5.0.0
- locate-path@7.2.0:
- dependencies:
- p-locate: 6.0.0
-
- lockr@0.8.5: {}
-
lodash.clonedeep@4.5.0: {}
lodash.debounce@4.0.8: {}
@@ -13480,7 +13889,9 @@ snapshots:
lodash.without@4.4.0: {}
- lodash@4.17.21: {}
+ lodash@4.17.23: {}
+
+ lodash@4.18.1: {}
loglevel@1.9.2: {}
@@ -13496,6 +13907,8 @@ snapshots:
lru-cache@10.4.3: {}
+ lru-cache@11.3.3: {}
+
lru-cache@4.1.5:
dependencies:
pseudomap: 1.0.2
@@ -13517,14 +13930,9 @@ snapshots:
dependencies:
sourcemap-codec: 1.4.8
- make-dir@2.1.0:
- dependencies:
- pify: 4.0.1
- semver: 5.7.2
-
make-dir@4.0.0:
dependencies:
- semver: 7.7.2
+ semver: 7.7.4
make-fetch-happen@10.2.1:
dependencies:
@@ -13578,8 +13986,6 @@ snapshots:
map-obj@4.3.0: {}
- map-values@1.0.1: {}
-
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
@@ -13589,15 +13995,17 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
+ marked@16.1.1: {}
+
marks-pane@1.0.9: {}
material-icons@0.3.1: {}
math-intrinsics@1.1.0: {}
- mathlive@0.105.3:
+ mathlive@0.108.2:
dependencies:
- '@cortex-js/compute-engine': 0.28.0
+ '@cortex-js/compute-engine': 0.30.2
mathml-tag-names@2.1.3: {}
@@ -13613,8 +14021,6 @@ snapshots:
media-typer@0.3.0: {}
- media-typer@1.1.0: {}
-
memfs@4.17.2:
dependencies:
'@jsonjoy.com/json-pack': 1.2.0(tslib@2.8.1)
@@ -13658,8 +14064,6 @@ snapshots:
merge-descriptors@1.0.3: {}
- merge-descriptors@2.0.0: {}
-
merge-source-map@1.1.0:
dependencies:
source-map: 0.6.1
@@ -13683,24 +14087,24 @@ snapshots:
dependencies:
mime-db: 1.52.0
- mime-types@3.0.1:
- dependencies:
- mime-db: 1.54.0
-
mime@1.6.0: {}
mimic-fn@2.1.0: {}
min-indent@1.0.1: {}
- mini-css-extract-plugin@2.9.2(webpack@5.99.9):
+ mini-css-extract-plugin@2.9.4(webpack@5.104.1):
dependencies:
schema-utils: 4.3.2
- tapable: 2.2.2
- webpack: 5.99.9(webpack-cli@6.0.1)
+ tapable: 2.2.3
+ webpack: 5.104.1(webpack-cli@6.0.1)
minimalistic-assert@1.0.1: {}
+ minimatch@10.2.0:
+ dependencies:
+ brace-expansion: 5.0.2
+
minimatch@3.0.8:
dependencies:
brace-expansion: 1.1.12
@@ -13709,9 +14113,9 @@ snapshots:
dependencies:
brace-expansion: 1.1.12
- minimatch@5.1.6:
+ minimatch@5.1.9:
dependencies:
- brace-expansion: 2.0.2
+ brace-expansion: 2.0.3
minimatch@9.0.1:
dependencies:
@@ -13721,6 +14125,10 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
+ minimatch@9.0.9:
+ dependencies:
+ brace-expansion: 2.0.3
+
minimist-options@4.1.0:
dependencies:
arrify: 1.0.1
@@ -13767,13 +14175,17 @@ snapshots:
minipass@5.0.0: {}
- minipass@7.1.2: {}
+ minipass@7.1.3: {}
minizlib@2.1.2:
dependencies:
minipass: 3.3.6
yallist: 4.0.0
+ minizlib@3.1.0:
+ dependencies:
+ minipass: 7.1.3
+
mkdirp@1.0.4: {}
ms@2.0.0: {}
@@ -13791,14 +14203,14 @@ snapshots:
nanoid@3.3.11: {}
+ napi-postinstall@0.3.4: {}
+
natural-compare@1.4.0: {}
negotiator@0.6.3: {}
negotiator@0.6.4: {}
- negotiator@1.0.0: {}
-
neo-async@2.6.2: {}
next-tick@1.1.0: {}
@@ -13810,7 +14222,7 @@ snapshots:
node-cache@4.2.1:
dependencies:
clone: 2.1.2
- lodash: 4.17.21
+ lodash: 4.18.1
node-fetch@2.7.0(encoding@0.1.13):
dependencies:
@@ -13834,7 +14246,7 @@ snapshots:
nopt: 5.0.0
npmlog: 6.0.2
rimraf: 3.0.2
- semver: 7.7.2
+ semver: 7.7.4
tar: 6.2.1
which: 2.0.2
transitivePeerDependencies:
@@ -13845,6 +14257,10 @@ snapshots:
node-releases@2.0.19: {}
+ node-releases@2.0.27: {}
+
+ node-releases@2.0.37: {}
+
node-sass@9.0.0:
dependencies:
async-foreach: 0.1.3
@@ -13853,7 +14269,7 @@ snapshots:
gaze: 1.1.3
get-stdin: 4.0.1
glob: 7.2.3
- lodash: 4.17.21
+ lodash: 4.18.1
make-fetch-happen: 10.2.1
meow: 9.0.0
nan: 2.22.2
@@ -13887,7 +14303,7 @@ snapshots:
normalize-package-data@2.5.0:
dependencies:
hosted-git-info: 2.8.9
- resolve: 1.22.10
+ resolve: 1.22.11
semver: 5.7.2
validate-npm-package-license: 3.0.4
@@ -13895,7 +14311,7 @@ snapshots:
dependencies:
hosted-git-info: 4.1.0
is-core-module: 2.16.1
- semver: 7.7.2
+ semver: 7.7.4
validate-npm-package-license: 3.0.4
normalize-path@3.0.0: {}
@@ -13931,14 +14347,12 @@ snapshots:
nwmatcher@1.4.4: {}
- nwsapi@2.2.20: {}
+ nwsapi@2.2.23: {}
oauth-sign@0.9.0: {}
object-assign@4.1.1: {}
- object-filter@1.0.2: {}
-
object-inspect@1.13.4: {}
object-is@1.1.6:
@@ -13977,7 +14391,7 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
- oblivious-set@1.1.1: {}
+ oblivious-set@1.4.0: {}
obuf@1.1.2: {}
@@ -14042,14 +14456,6 @@ snapshots:
dependencies:
yocto-queue: 0.1.0
- p-limit@4.0.0:
- dependencies:
- yocto-queue: 1.2.1
-
- p-locate@3.0.0:
- dependencies:
- p-limit: 2.3.0
-
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
@@ -14058,10 +14464,6 @@ snapshots:
dependencies:
p-limit: 3.1.0
- p-locate@6.0.0:
- dependencies:
- p-limit: 4.0.0
-
p-map@4.0.0:
dependencies:
aggregate-error: 3.1.0
@@ -14097,13 +14499,13 @@ snapshots:
parse-json@4.0.0:
dependencies:
- error-ex: 1.3.2
+ error-ex: 1.3.4
json-parse-better-errors: 1.0.2
parse-json@5.2.0:
dependencies:
- '@babel/code-frame': 7.27.1
- error-ex: 1.3.2
+ '@babel/code-frame': 7.29.0
+ error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
@@ -14119,8 +14521,6 @@ snapshots:
path-exists@4.0.0: {}
- path-exists@5.0.0: {}
-
path-is-absolute@1.0.1: {}
path-key@2.0.1: {}
@@ -14132,15 +14532,14 @@ snapshots:
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
- minipass: 7.1.2
-
- path-to-regexp@0.1.12: {}
+ minipass: 7.1.3
- path-to-regexp@1.9.0:
+ path-scurry@2.0.1:
dependencies:
- isarray: 0.0.1
+ lru-cache: 11.3.3
+ minipass: 7.1.3
- path-to-regexp@8.2.0: {}
+ path-to-regexp@0.1.12: {}
path-type@3.0.0:
dependencies:
@@ -14157,6 +14556,8 @@ snapshots:
peek-readable@4.1.0: {}
+ pend@1.2.0: {}
+
performance-now@2.1.0: {}
picocolors@0.2.1: {}
@@ -14165,32 +14566,24 @@ snapshots:
picomatch@2.3.1: {}
- picomatch@4.0.3: {}
+ picomatch@2.3.2: {}
+
+ picomatch@4.0.4: {}
pidtree@0.3.1: {}
pify@3.0.0: {}
- pify@4.0.1: {}
-
pirates@4.0.7: {}
pixelmatch@4.0.2:
dependencies:
pngjs: 3.4.0
- pkg-dir@3.0.0:
- dependencies:
- find-up: 3.0.0
-
pkg-dir@4.2.0:
dependencies:
find-up: 4.1.0
- pkg-dir@7.0.0:
- dependencies:
- find-up: 6.3.0
-
pngjs@3.4.0: {}
pngjs@6.0.0: {}
@@ -14207,7 +14600,7 @@ snapshots:
postcss-colormin@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
caniuse-api: 3.0.0
colord: 2.9.3
postcss: 8.5.6
@@ -14215,7 +14608,7 @@ snapshots:
postcss-convert-values@7.0.5(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -14247,14 +14640,25 @@ snapshots:
dependencies:
postcss: 8.5.6
- postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.99.9):
+ postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.104.1):
dependencies:
cosmiconfig: 9.0.0(typescript@5.8.3)
jiti: 1.21.7
postcss: 8.5.6
semver: 7.7.2
optionalDependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ transitivePeerDependencies:
+ - typescript
+
+ postcss-loader@8.2.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.104.1):
+ dependencies:
+ cosmiconfig: 9.0.0(typescript@5.8.3)
+ jiti: 2.6.1
+ postcss: 8.5.6
+ semver: 7.7.4
+ optionalDependencies:
+ webpack: 5.104.1(webpack-cli@6.0.1)
transitivePeerDependencies:
- typescript
@@ -14268,7 +14672,7 @@ snapshots:
postcss-merge-rules@7.0.5(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
caniuse-api: 3.0.0
cssnano-utils: 5.0.1(postcss@8.5.6)
postcss: 8.5.6
@@ -14288,7 +14692,7 @@ snapshots:
postcss-minify-params@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
cssnano-utils: 5.0.1(postcss@8.5.6)
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -14351,7 +14755,7 @@ snapshots:
postcss-normalize-unicode@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -14373,7 +14777,7 @@ snapshots:
postcss-reduce-initial@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
caniuse-api: 3.0.0
postcss: 8.5.6
@@ -14452,13 +14856,7 @@ snapshots:
ansi-styles: 5.2.0
react-is: 17.0.2
- pretty-format@29.7.0:
- dependencies:
- '@jest/schemas': 29.6.3
- ansi-styles: 5.2.0
- react-is: 18.3.1
-
- pretty-format@30.0.5:
+ pretty-format@30.3.0:
dependencies:
'@jest/schemas': 30.0.5
ansi-styles: 5.2.0
@@ -14474,8 +14872,6 @@ snapshots:
process@0.11.10: {}
- progress@2.0.3: {}
-
promise-inflight@1.0.1: {}
promise-retry@2.0.1:
@@ -14483,113 +14879,108 @@ snapshots:
err-code: 2.0.3
retry: 0.12.0
- prompts@2.4.2:
- dependencies:
- kleur: 3.0.3
- sisteransi: 1.0.5
-
prosemirror-changeset@2.3.1:
dependencies:
- prosemirror-transform: 1.10.4
+ prosemirror-transform: 1.10.5
prosemirror-collab@1.3.1:
dependencies:
- prosemirror-state: 1.4.3
+ prosemirror-state: 1.4.4
prosemirror-commands@1.7.1:
dependencies:
- prosemirror-model: 1.25.1
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
prosemirror-dropcursor@1.8.2:
dependencies:
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
- prosemirror-view: 1.40.0
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
- prosemirror-gapcursor@1.3.2:
+ prosemirror-gapcursor@1.4.0:
dependencies:
prosemirror-keymap: 1.2.3
- prosemirror-model: 1.25.1
- prosemirror-state: 1.4.3
- prosemirror-view: 1.40.0
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.4
- prosemirror-history@1.4.1:
+ prosemirror-history@1.5.0:
dependencies:
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
- prosemirror-view: 1.40.0
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
rope-sequence: 1.3.4
- prosemirror-inputrules@1.5.0:
+ prosemirror-inputrules@1.5.1:
dependencies:
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
prosemirror-keymap@1.2.3:
dependencies:
- prosemirror-state: 1.4.3
+ prosemirror-state: 1.4.4
w3c-keyname: 2.2.8
prosemirror-markdown@1.13.2:
dependencies:
'@types/markdown-it': 14.1.2
markdown-it: 14.1.0
- prosemirror-model: 1.25.1
+ prosemirror-model: 1.25.4
prosemirror-menu@1.2.5:
dependencies:
crelt: 1.0.6
prosemirror-commands: 1.7.1
- prosemirror-history: 1.4.1
- prosemirror-state: 1.4.3
+ prosemirror-history: 1.5.0
+ prosemirror-state: 1.4.4
- prosemirror-model@1.25.1:
+ prosemirror-model@1.25.4:
dependencies:
orderedmap: 2.1.1
prosemirror-schema-basic@1.2.4:
dependencies:
- prosemirror-model: 1.25.1
+ prosemirror-model: 1.25.4
prosemirror-schema-list@1.5.1:
dependencies:
- prosemirror-model: 1.25.1
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
- prosemirror-state@1.4.3:
+ prosemirror-state@1.4.4:
dependencies:
- prosemirror-model: 1.25.1
- prosemirror-transform: 1.10.4
- prosemirror-view: 1.40.0
+ prosemirror-model: 1.25.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
- prosemirror-tables@1.7.1:
+ prosemirror-tables@1.8.3:
dependencies:
prosemirror-keymap: 1.2.3
- prosemirror-model: 1.25.1
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
- prosemirror-view: 1.40.0
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.4
- prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0):
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.4):
dependencies:
'@remirror/core-constants': 3.0.0
escape-string-regexp: 4.0.0
- prosemirror-model: 1.25.1
- prosemirror-state: 1.4.3
- prosemirror-view: 1.40.0
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.4
- prosemirror-transform@1.10.4:
+ prosemirror-transform@1.10.5:
dependencies:
- prosemirror-model: 1.25.1
+ prosemirror-model: 1.25.4
- prosemirror-view@1.40.0:
+ prosemirror-view@1.41.4:
dependencies:
- prosemirror-model: 1.25.1
- prosemirror-state: 1.4.3
- prosemirror-transform: 1.10.4
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
proto-list@1.2.4: {}
@@ -14598,7 +14989,7 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
- proxy-from-env@1.1.0: {}
+ proxy-from-env@2.1.0: {}
pseudomap@1.0.2: {}
@@ -14610,7 +15001,7 @@ snapshots:
punycode@2.3.1: {}
- pure-rand@6.1.0: {}
+ pure-rand@7.0.1: {}
purecss@2.2.0: {}
@@ -14618,18 +15009,11 @@ snapshots:
dependencies:
side-channel: 1.1.0
- qs@6.14.0:
+ qs@6.14.2:
dependencies:
side-channel: 1.1.0
- qs@6.5.3: {}
-
- query-ast@1.0.5:
- dependencies:
- invariant: 2.2.4
- lodash: 4.17.21
-
- querystringify@2.2.0: {}
+ qs@6.5.5: {}
queue-microtask@1.2.3: {}
@@ -14645,25 +15029,25 @@ snapshots:
ranges-apply@5.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.4
ranges-merge: 7.1.0
ranges-merge@7.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.6
ranges-push: 5.1.0
ranges-sort: 4.1.0
ranges-push@5.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.4
ranges-merge: 7.1.0
string-collapse-leading-whitespace: 5.1.0
string-trim-spaces-only: 3.1.0
ranges-sort@4.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.6
raw-body@2.5.2:
dependencies:
@@ -14672,13 +15056,6 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
- raw-body@3.0.0:
- dependencies:
- bytes: 3.1.2
- http-errors: 2.0.0
- iconv-lite: 0.6.3
- unpipe: 1.0.0
-
react-is@17.0.2: {}
react-is@18.3.1: {}
@@ -14745,9 +15122,7 @@ snapshots:
readdirp@3.6.0:
dependencies:
- picomatch: 2.3.1
-
- readline-sync@1.4.10: {}
+ picomatch: 2.3.2
recast@0.23.11:
dependencies:
@@ -14759,7 +15134,7 @@ snapshots:
rechoir@0.8.0:
dependencies:
- resolve: 1.22.10
+ resolve: 1.22.11
redent@3.0.0:
dependencies:
@@ -14782,7 +15157,7 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
- regenerate-unicode-properties@10.2.0:
+ regenerate-unicode-properties@10.2.2:
dependencies:
regenerate: 1.4.2
@@ -14803,22 +15178,20 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
- regexpp@3.2.0: {}
-
- regexpu-core@6.2.0:
+ regexpu-core@6.4.0:
dependencies:
regenerate: 1.4.2
- regenerate-unicode-properties: 10.2.0
+ regenerate-unicode-properties: 10.2.2
regjsgen: 0.8.0
- regjsparser: 0.12.0
+ regjsparser: 0.13.1
unicode-match-property-ecmascript: 2.0.0
- unicode-match-property-value-ecmascript: 2.2.0
+ unicode-match-property-value-ecmascript: 2.2.1
regjsgen@0.8.0: {}
- regjsparser@0.12.0:
+ regjsparser@0.13.1:
dependencies:
- jsesc: 3.0.2
+ jsesc: 3.1.0
request@2.88.2:
dependencies:
@@ -14837,7 +15210,7 @@ snapshots:
mime-types: 2.1.35
oauth-sign: 0.9.0
performance-now: 2.1.0
- qs: 6.5.3
+ qs: 6.5.5
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
@@ -14861,14 +15234,18 @@ snapshots:
resolve-url@0.2.1: {}
- resolve.exports@2.0.3: {}
-
resolve@1.22.10:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
+ resolve@1.22.11:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
resolve@2.0.0-next.5:
dependencies:
is-core-module: 2.16.1
@@ -14881,12 +15258,6 @@ snapshots:
reusify@1.1.0: {}
- rewire@6.0.0:
- dependencies:
- eslint: 7.32.0
- transitivePeerDependencies:
- - supports-color
-
rgbcolor@1.0.1: {}
rimraf@2.6.3:
@@ -14903,15 +15274,7 @@ snapshots:
rope-sequence@1.3.4: {}
- router@2.2.0:
- dependencies:
- debug: 4.4.1
- depd: 2.0.0
- is-promise: 4.0.0
- parseurl: 1.3.3
- path-to-regexp: 8.2.0
- transitivePeerDependencies:
- - supports-color
+ rrweb-cssom@0.8.0: {}
rtlcss@4.3.0:
dependencies:
@@ -14954,18 +15317,23 @@ snapshots:
sass-graph@4.0.1:
dependencies:
glob: 7.2.3
- lodash: 4.17.21
+ lodash: 4.18.1
scss-tokenizer: 0.4.3
yargs: 17.7.2
- sass-loader@16.0.5(node-sass@9.0.0)(webpack@5.99.9):
+ sass-loader@16.0.5(node-sass@9.0.0)(webpack@5.104.1):
dependencies:
neo-async: 2.6.2
optionalDependencies:
node-sass: 9.0.0
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
- sax@1.3.0: {}
+ sass-loader@16.0.7(node-sass@9.0.0)(webpack@5.104.1):
+ dependencies:
+ neo-async: 2.6.2
+ optionalDependencies:
+ node-sass: 9.0.0
+ webpack: 5.104.1(webpack-cli@6.0.1)
sax@1.4.1: {}
@@ -14976,20 +15344,27 @@ snapshots:
schema-utils@3.3.0:
dependencies:
'@types/json-schema': 7.0.15
- ajv: 6.12.6
- ajv-keywords: 3.5.2(ajv@6.12.6)
+ ajv: 6.14.0
+ ajv-keywords: 3.5.2(ajv@6.14.0)
schema-utils@4.3.2:
dependencies:
'@types/json-schema': 7.0.15
- ajv: 8.17.1
- ajv-formats: 2.1.1(ajv@8.17.1)
- ajv-keywords: 5.1.0(ajv@8.17.1)
+ ajv: 8.18.0
+ ajv-formats: 2.1.1(ajv@8.18.0)
+ ajv-keywords: 5.1.0(ajv@8.18.0)
+
+ schema-utils@4.3.3:
+ dependencies:
+ '@types/json-schema': 7.0.15
+ ajv: 8.18.0
+ ajv-formats: 2.1.1(ajv@8.18.0)
+ ajv-keywords: 5.1.0(ajv@8.18.0)
scss-tokenizer@0.4.3:
dependencies:
js-base64: 2.6.4
- source-map: 0.7.4
+ source-map: 0.7.6
select-hose@2.0.0: {}
@@ -15004,6 +15379,8 @@ snapshots:
semver@7.7.2: {}
+ semver@7.7.4: {}
+
send@0.19.0:
dependencies:
debug: 2.6.9
@@ -15022,22 +15399,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- send@1.2.0:
- dependencies:
- debug: 4.4.1
- encodeurl: 2.0.0
- escape-html: 1.0.3
- etag: 1.8.1
- fresh: 2.0.0
- http-errors: 2.0.0
- mime-types: 3.0.1
- ms: 2.1.3
- on-finished: 2.4.1
- range-parser: 1.2.1
- statuses: 2.0.2
- transitivePeerDependencies:
- - supports-color
-
serialize-javascript@6.0.2:
dependencies:
randombytes: 2.1.0
@@ -15063,15 +15424,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- serve-static@2.2.0:
- dependencies:
- encodeurl: 2.0.0
- escape-html: 1.0.3
- parseurl: 1.3.3
- send: 1.2.0
- transitivePeerDependencies:
- - supports-color
-
set-blocking@2.0.0: {}
set-function-length@1.2.2:
@@ -15120,9 +15472,10 @@ snapshots:
shell-quote@1.8.3: {}
- showdown@2.1.0:
+ shelljs@0.10.0:
dependencies:
- commander: 9.5.0
+ execa: 5.1.1
+ fast-glob: 3.3.3
side-channel-list@1.0.0:
dependencies:
@@ -15160,8 +15513,6 @@ snapshots:
dependencies:
is-arrayish: 0.3.2
- sisteransi@1.0.5: {}
-
slash@3.0.0: {}
slice-ansi@4.0.0:
@@ -15172,7 +15523,7 @@ snapshots:
smart-buffer@4.2.0: {}
- smob@1.5.0: {}
+ smob@1.6.1: {}
sockjs@0.3.24:
dependencies:
@@ -15183,7 +15534,7 @@ snapshots:
socks-proxy-agent@6.2.1:
dependencies:
agent-base: 6.0.2
- debug: 4.4.1
+ debug: 4.4.3
socks: 2.8.5
transitivePeerDependencies:
- supports-color
@@ -15191,7 +15542,7 @@ snapshots:
socks-proxy-agent@7.0.0:
dependencies:
agent-base: 6.0.2
- debug: 4.4.1
+ debug: 4.4.3
socks: 2.8.5
transitivePeerDependencies:
- supports-color
@@ -15225,11 +15576,13 @@ snapshots:
source-map-url@0.4.1: {}
+ source-map@0.5.6: {}
+
source-map@0.5.7: {}
source-map@0.6.1: {}
- source-map@0.7.4: {}
+ source-map@0.7.6: {}
source-map@0.8.0-beta.0:
dependencies:
@@ -15255,7 +15608,7 @@ snapshots:
spdy-transport@3.0.0:
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
detect-node: 2.1.0
hpack.js: 2.1.6
obuf: 1.1.2
@@ -15266,7 +15619,7 @@ snapshots:
spdy@4.0.2:
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
handle-thing: 2.0.1
http-deceiver: 1.2.7
select-hose: 2.0.0
@@ -15310,8 +15663,6 @@ snapshots:
statuses@2.0.1: {}
- statuses@2.0.2: {}
-
stdout-stream@1.4.1:
dependencies:
readable-stream: 2.3.8
@@ -15325,13 +15676,13 @@ snapshots:
string-collapse-leading-whitespace@5.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.6
string-hash@1.1.3: {}
string-left-right@4.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.4
lodash.clonedeep: 4.5.0
lodash.isplainobject: 4.0.6
@@ -15353,7 +15704,7 @@ snapshots:
string-trim-spaces-only@3.1.0:
dependencies:
- '@babel/runtime': 7.27.6
+ '@babel/runtime': 7.28.6
string-width@4.2.3:
dependencies:
@@ -15365,14 +15716,14 @@ snapshots:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
- strip-ansi: 7.1.0
+ strip-ansi: 7.2.0
string.prototype.matchall@4.0.12:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.4
define-properties: 1.2.1
- es-abstract: 1.24.0
+ es-abstract: 1.24.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
@@ -15435,9 +15786,9 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
- strip-ansi@7.1.0:
+ strip-ansi@7.2.0:
dependencies:
- ansi-regex: 6.1.0
+ ansi-regex: 6.2.2
strip-bom@3.0.0: {}
@@ -15464,15 +15815,15 @@ snapshots:
'@tokenizer/token': 0.3.0
peek-readable: 4.1.0
- style-loader@4.0.0(webpack@5.99.9):
+ style-loader@4.0.0(webpack@5.104.1):
dependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
style-search@0.1.0: {}
stylehacks@7.0.5(postcss@8.5.6):
dependencies:
- browserslist: 4.25.1
+ browserslist: 4.28.2
postcss: 8.5.6
postcss-selector-parser: 7.1.0
@@ -15597,21 +15948,21 @@ snapshots:
- supports-color
- typescript
- stylus-loader@8.1.1(stylus@0.63.0)(webpack@5.99.9):
+ stylus-loader@8.1.2(stylus@0.64.0)(webpack@5.104.1):
dependencies:
fast-glob: 3.3.3
normalize-path: 3.0.0
- stylus: 0.63.0
+ stylus: 0.64.0
optionalDependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
- stylus@0.63.0:
+ stylus@0.64.0:
dependencies:
'@adobe/css-tools': 4.3.3
- debug: 4.4.1
- glob: 7.2.3
- sax: 1.3.0
- source-map: 0.7.4
+ debug: 4.4.3
+ glob: 10.4.5
+ sax: 1.4.1
+ source-map: 0.7.6
transitivePeerDependencies:
- supports-color
@@ -15650,9 +16001,13 @@ snapshots:
symbol-tree@3.2.4: {}
+ synckit@0.11.12:
+ dependencies:
+ '@pkgr/core': 0.2.9
+
table@6.9.0:
dependencies:
- ajv: 8.17.1
+ ajv: 8.18.0
lodash.truncate: 4.4.2
slice-ansi: 4.0.0
string-width: 4.2.3
@@ -15660,7 +16015,9 @@ snapshots:
tapable@0.1.10: {}
- tapable@2.2.2: {}
+ tapable@2.2.3: {}
+
+ tapable@2.3.0: {}
tar@6.2.1:
dependencies:
@@ -15671,6 +16028,14 @@ snapshots:
mkdirp: 1.0.4
yallist: 4.0.0
+ tar@7.5.13:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.3
+ minizlib: 3.1.0
+ yallist: 5.0.0
+
temp-dir@2.0.0: {}
temp@0.8.4:
@@ -15684,18 +16049,18 @@ snapshots:
type-fest: 0.16.0
unique-string: 2.0.0
- terser-webpack-plugin@5.3.14(webpack@5.99.9):
+ terser-webpack-plugin@5.3.16(webpack@5.104.1):
dependencies:
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
- schema-utils: 4.3.2
+ schema-utils: 4.3.3
serialize-javascript: 6.0.2
- terser: 5.43.1
- webpack: 5.99.9(webpack-cli@6.0.1)
+ terser: 5.46.0
+ webpack: 5.104.1(webpack-cli@6.0.1)
- terser@5.43.1:
+ terser@5.46.0:
dependencies:
- '@jridgewell/source-map': 0.3.10
+ '@jridgewell/source-map': 0.3.11
acorn: 8.15.0
commander: 2.20.3
source-map-support: 0.5.21
@@ -15728,11 +16093,11 @@ snapshots:
dependencies:
popper.js: 1.16.1
- tippy.js@6.3.7:
- dependencies:
- '@popperjs/core': 2.11.8
+ tldts-core@6.1.86: {}
- tmp@0.2.3: {}
+ tldts@6.1.86:
+ dependencies:
+ tldts-core: 6.1.86
tmpl@1.0.5: {}
@@ -15756,12 +16121,9 @@ snapshots:
psl: 1.15.0
punycode: 2.3.1
- tough-cookie@4.1.4:
+ tough-cookie@5.1.2:
dependencies:
- psl: 1.15.0
- punycode: 2.3.1
- universalify: 0.2.0
- url-parse: 1.5.10
+ tldts: 6.1.86
tr46@0.0.3: {}
@@ -15769,7 +16131,7 @@ snapshots:
dependencies:
punycode: 2.3.1
- tr46@3.0.0:
+ tr46@5.1.1:
dependencies:
punycode: 2.3.1
@@ -15838,12 +16200,6 @@ snapshots:
media-typer: 0.3.0
mime-types: 2.1.35
- type-is@2.0.1:
- dependencies:
- content-type: 1.0.5
- media-typer: 1.1.0
- mime-types: 3.0.1
-
type@2.7.3: {}
typed-array-buffer@1.0.3:
@@ -15881,8 +16237,6 @@ snapshots:
typescript@5.8.3: {}
- ua-parser-js@1.0.40: {}
-
uc.micro@2.1.0: {}
unbox-primitive@1.1.0:
@@ -15894,6 +16248,8 @@ snapshots:
undici-types@5.26.5: {}
+ undici-types@7.18.2: {}
+
undici-types@7.8.0: {}
unicode-canonical-property-names-ecmascript@2.0.1: {}
@@ -15901,11 +16257,11 @@ snapshots:
unicode-match-property-ecmascript@2.0.0:
dependencies:
unicode-canonical-property-names-ecmascript: 2.0.1
- unicode-property-aliases-ecmascript: 2.1.0
+ unicode-property-aliases-ecmascript: 2.2.0
- unicode-match-property-value-ecmascript@2.2.0: {}
+ unicode-match-property-value-ecmascript@2.2.1: {}
- unicode-property-aliases-ecmascript@2.1.0: {}
+ unicode-property-aliases-ecmascript@2.2.0: {}
unique-filename@1.1.1:
dependencies:
@@ -15927,14 +16283,36 @@ snapshots:
dependencies:
crypto-random-string: 2.0.0
- universalify@0.2.0: {}
-
universalify@2.0.1: {}
unload@2.4.1: {}
unpipe@1.0.0: {}
+ unrs-resolver@1.11.1:
+ dependencies:
+ napi-postinstall: 0.3.4
+ optionalDependencies:
+ '@unrs/resolver-binding-android-arm-eabi': 1.11.1
+ '@unrs/resolver-binding-android-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-x64': 1.11.1
+ '@unrs/resolver-binding-freebsd-x64': 1.11.1
+ '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-musl': 1.11.1
+ '@unrs/resolver-binding-wasm32-wasi': 1.11.1
+ '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-x64-msvc': 1.11.1
+
upath@1.2.0: {}
update-browserslist-db@1.1.3(browserslist@4.25.1):
@@ -15943,25 +16321,32 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ update-browserslist-db@1.2.3(browserslist@4.28.2):
+ dependencies:
+ browserslist: 4.28.2
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
urix@0.1.0: {}
- url-loader@4.1.1(file-loader@6.2.0(webpack@5.99.9))(webpack@5.99.9):
+ url-loader@4.1.1(file-loader@6.2.0(webpack@5.104.1))(webpack@5.104.1):
dependencies:
loader-utils: 2.0.4
mime-types: 2.1.35
schema-utils: 3.3.0
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
optionalDependencies:
- file-loader: 6.2.0(webpack@5.99.9)
-
- url-parse@1.5.10:
- dependencies:
- querystringify: 2.2.0
- requires-port: 1.0.0
+ file-loader: 6.2.0(webpack@5.104.1)
utif2@4.1.0:
dependencies:
@@ -15981,11 +16366,9 @@ snapshots:
uuid@8.3.2: {}
- v8-compile-cache@2.4.0: {}
-
v8-to-istanbul@9.3.0:
dependencies:
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/trace-mapping': 0.3.31
'@types/istanbul-lib-coverage': 2.0.6
convert-source-map: 2.0.0
@@ -16009,19 +16392,15 @@ snapshots:
vue-custom-element@https://codeload.github.com/learningequality/vue-custom-element/tar.gz/131aa5995e94a98d550c972fa5e96a5266a8ac0b: {}
- vue-demi@0.14.10(vue@2.7.16):
- dependencies:
- vue: 2.7.16
-
vue-eslint-parser@9.4.3(eslint@8.57.1):
dependencies:
- debug: 4.4.1
+ debug: 4.4.3
eslint: 8.57.1
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.6.0
- lodash: 4.17.21
+ lodash: 4.18.1
semver: 7.7.2
transitivePeerDependencies:
- supports-color
@@ -16036,9 +16415,9 @@ snapshots:
intl-relativeformat: 1.3.0
vue: 2.7.16
- vue-jest@3.0.7(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(vue-template-compiler@2.7.16)(vue@2.7.16):
+ vue-jest@3.0.7(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(vue-template-compiler@2.7.16)(vue@2.7.16):
dependencies:
- babel-core: 7.0.0-bridge.0(@babel/core@7.28.0)
+ babel-core: 7.0.0-bridge.0(@babel/core@7.29.0)
babel-plugin-transform-es2015-modules-commonjs: 6.26.2
chalk: 2.4.2
deasync: 0.1.30
@@ -16055,15 +16434,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vue-loader@15.11.1(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(css-loader@7.1.2(webpack@5.99.9))(ejs@3.1.10)(lodash@4.17.21)(vue-template-compiler@2.7.16)(webpack@5.99.9):
+ vue-loader@15.11.1(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(css-loader@7.1.2(webpack@5.104.1))(ejs@3.1.10)(lodash@4.18.1)(vue-template-compiler@2.7.16)(webpack@5.104.1):
dependencies:
- '@vue/component-compiler-utils': 3.3.0(babel-core@7.0.0-bridge.0(@babel/core@7.28.0))(ejs@3.1.10)(lodash@4.17.21)
- css-loader: 7.1.2(webpack@5.99.9)
+ '@vue/component-compiler-utils': 3.3.0(babel-core@7.0.0-bridge.0(@babel/core@7.29.0))(ejs@3.1.10)(lodash@4.18.1)
+ css-loader: 7.1.2(webpack@5.104.1)
hash-sum: 1.0.2
loader-utils: 1.4.2
vue-hot-reload-api: 2.3.4
vue-style-loader: 4.1.3
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
optionalDependencies:
vue-template-compiler: 2.7.16
transitivePeerDependencies:
@@ -16121,10 +16500,6 @@ snapshots:
- walrus
- whiskers
- vue-meta@2.4.0:
- dependencies:
- deepmerge: 4.3.1
-
vue-router@3.6.5(vue@2.7.16):
dependencies:
vue: 2.7.16
@@ -16166,15 +16541,15 @@ snapshots:
w3c-keyname@2.2.8: {}
- w3c-xmlserializer@4.0.0:
+ w3c-xmlserializer@5.0.0:
dependencies:
- xml-name-validator: 4.0.0
+ xml-name-validator: 5.0.0
walker@1.0.8:
dependencies:
makeerror: 1.0.12
- watchpack@2.4.4:
+ watchpack@2.5.1:
dependencies:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
@@ -16193,12 +16568,12 @@ snapshots:
webidl-conversions@7.0.0: {}
- webpack-cli@6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9):
+ webpack-cli@6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1):
dependencies:
'@discoveryjs/json-ext': 0.6.3
- '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.99.9)
- '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.99.9)
- '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.104.1)
+ '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.104.1)
+ '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack-dev-server@5.2.2)(webpack@5.104.1)
colorette: 2.0.20
commander: 12.1.0
cross-spawn: 7.0.6
@@ -16207,23 +16582,77 @@ snapshots:
import-local: 3.2.0
interpret: 3.1.1
rechoir: 0.8.0
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-merge: 6.0.1
+ optionalDependencies:
+ webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.104.1)
+
+ webpack-cli@7.0.2(webpack-dev-server@5.2.2)(webpack@5.104.1):
+ dependencies:
+ '@discoveryjs/json-ext': 1.0.0
+ commander: 14.0.3
+ cross-spawn: 7.0.6
+ envinfo: 7.14.0
+ fastest-levenshtein: 1.0.16
+ import-local: 3.2.0
+ interpret: 3.1.1
+ rechoir: 0.8.0
+ webpack: 5.104.1(webpack-cli@6.0.1)
webpack-merge: 6.0.1
optionalDependencies:
- webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.99.9)
+ webpack-dev-server: 5.2.2(webpack-cli@6.0.1)(webpack@5.104.1)
- webpack-dev-middleware@7.4.2(webpack@5.99.9):
+ webpack-dev-middleware@7.4.2(webpack@5.104.1):
dependencies:
colorette: 2.0.20
memfs: 4.17.2
mime-types: 2.1.35
on-finished: 2.4.1
range-parser: 1.2.1
+ schema-utils: 4.3.3
+ optionalDependencies:
+ webpack: 5.104.1(webpack-cli@6.0.1)
+
+ webpack-dev-server@5.2.2(webpack-cli@6.0.1)(webpack@5.104.1):
+ dependencies:
+ '@types/bonjour': 3.5.13
+ '@types/connect-history-api-fallback': 1.5.4
+ '@types/express': 4.17.23
+ '@types/express-serve-static-core': 4.19.6
+ '@types/serve-index': 1.9.4
+ '@types/serve-static': 1.15.8
+ '@types/sockjs': 0.3.36
+ '@types/ws': 8.18.1
+ ansi-html-community: 0.0.8
+ bonjour-service: 1.3.0
+ chokidar: 3.6.0
+ colorette: 2.0.20
+ compression: 1.8.0
+ connect-history-api-fallback: 2.0.0
+ express: 4.21.2
+ graceful-fs: 4.2.11
+ http-proxy-middleware: 2.0.9(@types/express@4.17.23)
+ ipaddr.js: 2.2.0
+ launch-editor: 2.10.0
+ open: 10.1.2
+ p-retry: 6.2.1
schema-utils: 4.3.2
+ selfsigned: 2.4.1
+ serve-index: 1.9.1
+ sockjs: 0.3.24
+ spdy: 4.0.2
+ webpack-dev-middleware: 7.4.2(webpack@5.104.1)
+ ws: 8.18.3
optionalDependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1)
+ transitivePeerDependencies:
+ - bufferutil
+ - debug
+ - supports-color
+ - utf-8-validate
- webpack-dev-server@5.2.2(webpack-cli@6.0.1)(webpack@5.99.9):
+ webpack-dev-server@5.2.2(webpack-cli@7.0.2)(webpack@5.104.1):
dependencies:
'@types/bonjour': 3.5.13
'@types/connect-history-api-fallback': 1.5.4
@@ -16251,11 +16680,11 @@ snapshots:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
- webpack-dev-middleware: 7.4.2(webpack@5.99.9)
+ webpack-dev-middleware: 7.4.2(webpack@5.104.1)
ws: 8.18.3
optionalDependencies:
- webpack: 5.99.9(webpack-cli@6.0.1)
- webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ webpack: 5.104.1(webpack-cli@6.0.1)
+ webpack-cli: 7.0.2(webpack-dev-server@5.2.2)(webpack@5.104.1)
transitivePeerDependencies:
- bufferutil
- debug
@@ -16275,7 +16704,7 @@ snapshots:
webpack-sources@3.3.3: {}
- webpack@5.99.9(webpack-cli@6.0.1):
+ webpack@5.104.1(webpack-cli@6.0.1):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
@@ -16284,25 +16713,26 @@ snapshots:
'@webassemblyjs/wasm-edit': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
- browserslist: 4.25.1
+ acorn-import-phases: 1.0.4(acorn@8.15.0)
+ browserslist: 4.28.1
chrome-trace-event: 1.0.4
- enhanced-resolve: 5.18.2
- es-module-lexer: 1.7.0
+ enhanced-resolve: 5.19.0
+ es-module-lexer: 2.0.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
+ loader-runner: 4.3.1
mime-types: 2.1.35
neo-async: 2.6.2
- schema-utils: 4.3.2
- tapable: 2.2.2
- terser-webpack-plugin: 5.3.14(webpack@5.99.9)
- watchpack: 2.4.4
+ schema-utils: 4.3.3
+ tapable: 2.3.0
+ terser-webpack-plugin: 5.3.16(webpack@5.104.1)
+ watchpack: 2.5.1
webpack-sources: 3.3.3
optionalDependencies:
- webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.99.9)
+ webpack-cli: 6.0.1(webpack-dev-server@5.2.2)(webpack@5.104.1)
transitivePeerDependencies:
- '@swc/core'
- esbuild
@@ -16316,17 +16746,17 @@ snapshots:
websocket-extensions@0.1.4: {}
- whatwg-encoding@2.0.0:
+ whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
whatwg-fetch@3.6.20: {}
- whatwg-mimetype@3.0.0: {}
+ whatwg-mimetype@4.0.0: {}
- whatwg-url@11.0.0:
+ whatwg-url@14.2.0:
dependencies:
- tr46: 3.0.0
+ tr46: 5.1.1
webidl-conversions: 7.0.0
whatwg-url@2.0.1:
@@ -16386,6 +16816,16 @@ snapshots:
gopd: 1.2.0
has-tostringtag: 1.0.2
+ which-typed-array@1.1.20:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
which@1.3.1:
dependencies:
isexe: 2.0.0
@@ -16402,32 +16842,32 @@ snapshots:
word-wrap@1.2.5: {}
- workbox-background-sync@7.3.0:
+ workbox-background-sync@7.4.0:
dependencies:
idb: 7.1.1
- workbox-core: 7.3.0
+ workbox-core: 7.4.0
- workbox-broadcast-update@7.3.0:
+ workbox-broadcast-update@7.4.0:
dependencies:
- workbox-core: 7.3.0
+ workbox-core: 7.4.0
- workbox-build@7.3.0(@types/babel__core@7.20.5):
+ workbox-build@7.4.0(@types/babel__core@7.20.5):
dependencies:
- '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1)
- '@babel/core': 7.28.0
- '@babel/preset-env': 7.28.0(@babel/core@7.28.0)
- '@babel/runtime': 7.27.6
- '@rollup/plugin-babel': 5.3.1(@babel/core@7.28.0)(@types/babel__core@7.20.5)(rollup@2.79.2)
+ '@apideck/better-ajv-errors': 0.3.6(ajv@8.18.0)
+ '@babel/core': 7.29.0
+ '@babel/preset-env': 7.29.2(@babel/core@7.29.0)
+ '@babel/runtime': 7.28.6
+ '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@2.79.2)
'@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2)
'@rollup/plugin-replace': 2.4.2(rollup@2.79.2)
'@rollup/plugin-terser': 0.4.4(rollup@2.79.2)
'@surma/rollup-plugin-off-main-thread': 2.2.3
- ajv: 8.17.1
+ ajv: 8.18.0
common-tags: 1.8.2
fast-json-stable-stringify: 2.1.0
fs-extra: 9.1.0
- glob: 7.2.3
- lodash: 4.17.21
+ glob: 11.1.0
+ lodash: 4.18.1
pretty-bytes: 5.6.0
rollup: 2.79.2
source-map: 0.8.0-beta.0
@@ -16435,89 +16875,99 @@ snapshots:
strip-comments: 2.0.1
tempy: 0.6.0
upath: 1.2.0
- workbox-background-sync: 7.3.0
- workbox-broadcast-update: 7.3.0
- workbox-cacheable-response: 7.3.0
- workbox-core: 7.3.0
- workbox-expiration: 7.3.0
- workbox-google-analytics: 7.3.0
- workbox-navigation-preload: 7.3.0
- workbox-precaching: 7.3.0
- workbox-range-requests: 7.3.0
- workbox-recipes: 7.3.0
- workbox-routing: 7.3.0
- workbox-strategies: 7.3.0
- workbox-streams: 7.3.0
- workbox-sw: 7.3.0
- workbox-window: 7.3.0
+ workbox-background-sync: 7.4.0
+ workbox-broadcast-update: 7.4.0
+ workbox-cacheable-response: 7.4.0
+ workbox-core: 7.4.0
+ workbox-expiration: 7.4.0
+ workbox-google-analytics: 7.4.0
+ workbox-navigation-preload: 7.4.0
+ workbox-precaching: 7.4.0
+ workbox-range-requests: 7.4.0
+ workbox-recipes: 7.4.0
+ workbox-routing: 7.4.0
+ workbox-strategies: 7.4.0
+ workbox-streams: 7.4.0
+ workbox-sw: 7.4.0
+ workbox-window: 7.4.0
transitivePeerDependencies:
- '@types/babel__core'
- supports-color
- workbox-cacheable-response@7.3.0:
+ workbox-cacheable-response@7.4.0:
dependencies:
- workbox-core: 7.3.0
+ workbox-core: 7.4.0
workbox-core@7.3.0: {}
- workbox-expiration@7.3.0:
+ workbox-core@7.4.0: {}
+
+ workbox-expiration@7.4.0:
dependencies:
idb: 7.1.1
- workbox-core: 7.3.0
+ workbox-core: 7.4.0
- workbox-google-analytics@7.3.0:
+ workbox-google-analytics@7.4.0:
dependencies:
- workbox-background-sync: 7.3.0
- workbox-core: 7.3.0
- workbox-routing: 7.3.0
- workbox-strategies: 7.3.0
+ workbox-background-sync: 7.4.0
+ workbox-core: 7.4.0
+ workbox-routing: 7.4.0
+ workbox-strategies: 7.4.0
- workbox-navigation-preload@7.3.0:
+ workbox-navigation-preload@7.4.0:
dependencies:
- workbox-core: 7.3.0
+ workbox-core: 7.4.0
- workbox-precaching@7.3.0:
+ workbox-precaching@7.4.0:
dependencies:
- workbox-core: 7.3.0
- workbox-routing: 7.3.0
- workbox-strategies: 7.3.0
+ workbox-core: 7.4.0
+ workbox-routing: 7.4.0
+ workbox-strategies: 7.4.0
- workbox-range-requests@7.3.0:
+ workbox-range-requests@7.4.0:
dependencies:
- workbox-core: 7.3.0
+ workbox-core: 7.4.0
- workbox-recipes@7.3.0:
+ workbox-recipes@7.4.0:
dependencies:
- workbox-cacheable-response: 7.3.0
- workbox-core: 7.3.0
- workbox-expiration: 7.3.0
- workbox-precaching: 7.3.0
- workbox-routing: 7.3.0
- workbox-strategies: 7.3.0
+ workbox-cacheable-response: 7.4.0
+ workbox-core: 7.4.0
+ workbox-expiration: 7.4.0
+ workbox-precaching: 7.4.0
+ workbox-routing: 7.4.0
+ workbox-strategies: 7.4.0
workbox-routing@7.3.0:
dependencies:
workbox-core: 7.3.0
+ workbox-routing@7.4.0:
+ dependencies:
+ workbox-core: 7.4.0
+
workbox-strategies@7.3.0:
dependencies:
workbox-core: 7.3.0
- workbox-streams@7.3.0:
+ workbox-strategies@7.4.0:
dependencies:
- workbox-core: 7.3.0
- workbox-routing: 7.3.0
+ workbox-core: 7.4.0
+
+ workbox-streams@7.4.0:
+ dependencies:
+ workbox-core: 7.4.0
+ workbox-routing: 7.4.0
- workbox-sw@7.3.0: {}
+ workbox-sw@7.4.0: {}
- workbox-webpack-plugin@7.3.0(@types/babel__core@7.20.5)(webpack@5.99.9):
+ workbox-webpack-plugin@7.4.0(@types/babel__core@7.20.5)(webpack@5.104.1):
dependencies:
fast-json-stable-stringify: 2.1.0
pretty-bytes: 5.6.0
upath: 1.2.0
- webpack: 5.99.9(webpack-cli@6.0.1)
+ webpack: 5.104.1(webpack-cli@6.0.1)
webpack-sources: 1.4.3
- workbox-build: 7.3.0(@types/babel__core@7.20.5)
+ workbox-build: 7.4.0(@types/babel__core@7.20.5)
transitivePeerDependencies:
- '@types/babel__core'
- supports-color
@@ -16527,6 +16977,11 @@ snapshots:
'@types/trusted-types': 2.0.7
workbox-core: 7.3.0
+ workbox-window@7.4.0:
+ dependencies:
+ '@types/trusted-types': 2.0.7
+ workbox-core: 7.4.0
+
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@@ -16535,9 +16990,9 @@ snapshots:
wrap-ansi@8.1.0:
dependencies:
- ansi-styles: 6.2.1
+ ansi-styles: 6.2.3
string-width: 5.1.2
- strip-ansi: 7.1.0
+ strip-ansi: 7.2.0
wrappy@1.0.2: {}
@@ -16553,10 +17008,14 @@ snapshots:
ws@8.18.3: {}
+ ws@8.20.0: {}
+
xml-name-validator@2.0.1: {}
xml-name-validator@4.0.0: {}
+ xml-name-validator@5.0.0: {}
+
xmlchars@2.2.0: {}
xmldom@0.1.31: {}
@@ -16571,6 +17030,8 @@ snapshots:
yallist@4.0.0: {}
+ yallist@5.0.0: {}
+
yargs-parser@20.2.9: {}
yargs-parser@21.1.1: {}
@@ -16585,6 +17046,9 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
- yocto-queue@0.1.0: {}
+ yauzl@3.3.0:
+ dependencies:
+ buffer-crc32: 0.2.13
+ pend: 1.2.0
- yocto-queue@1.2.1: {}
+ yocto-queue@0.1.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000000..7c7369ae41
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,10 @@
+# minimum number of minutes
+minimumReleaseAge: 10080
+minimumReleaseAgeExclude:
+ - kolibri-build
+ - kolibri-constants
+ - kolibri-design-system
+ - kolibri-format
+ - kolibri-glob
+ - kolibri-i18n
+ - kolibri-logging
diff --git a/requirements-dev.in b/requirements-dev.in
index 595ad607ce..8a9224102c 100644
--- a/requirements-dev.in
+++ b/requirements-dev.in
@@ -5,8 +5,6 @@ mixer==7.2.2
pytest
pytest-django
pytest-timeout
-pytest-subtests
-pre-commit==4.2.0
+pre-commit==4.5.1
nodeenv
-pip-tools==7.5.0
drf-yasg==1.21.10
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 295cd1744c..ac6539f932 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,25 +1,11 @@
-#
-# This file is autogenerated by pip-compile with Python 3.10
-# by the following command:
-#
-# pip-compile requirements-dev.in
-#
+# This file was autogenerated by uv via the following command:
+# uv pip compile requirements-dev.in --output-file requirements-dev.txt
asgiref==3.3.4
# via
# -c requirements.txt
# django
-attrs==23.1.0
- # via
- # -c requirements.txt
- # pytest-subtests
-build==1.2.1
- # via pip-tools
cfgv==3.3.1
# via pre-commit
-click==8.1.3
- # via
- # -c requirements.txt
- # pip-tools
distlib==0.3.9
# via virtualenv
django==3.2.24
@@ -35,11 +21,13 @@ djangorestframework==3.15.1
# drf-yasg
drf-yasg==1.21.10
# via -r requirements-dev.in
-exceptiongroup==1.2.2
- # via pytest
+exceptiongroup==1.3.1
+ # via
+ # -c requirements.txt
+ # pytest
faker==12.0.1
# via mixer
-filelock==3.16.1
+filelock==3.20.3
# via virtualenv
identify==2.4.4
# via pre-commit
@@ -53,39 +41,29 @@ mock==5.2.0
# via
# -r requirements-dev.in
# django-concurrent-test-helper
-nodeenv==1.9.1
+nodeenv==1.10.0
# via
# -r requirements-dev.in
# pre-commit
-packaging==25.0
+packaging==26.0
# via
# -c requirements.txt
- # build
# drf-yasg
# pytest
-pip-tools==7.5.0
- # via -r requirements-dev.in
platformdirs==4.3.6
# via virtualenv
pluggy==1.5.0
# via pytest
-pre-commit==4.2.0
+pre-commit==4.5.1
# via -r requirements-dev.in
-pygments==2.19.1
+pygments==2.20.0
# via pytest
-pyproject-hooks==1.1.0
- # via
- # build
- # pip-tools
-pytest==8.4.1
+pytest==9.0.3
# via
# -r requirements-dev.in
# pytest-django
- # pytest-subtests
# pytest-timeout
-pytest-django==4.11.1
- # via -r requirements-dev.in
-pytest-subtests==0.14.2
+pytest-django==4.12.0
# via -r requirements-dev.in
pytest-timeout==2.4.0
# via -r requirements-dev.in
@@ -114,17 +92,13 @@ sqlparse==0.4.1
tblib==1.7.0
# via django-concurrent-test-helper
tomli==1.2.3
+ # via pytest
+typing-extensions==4.15.0
# via
- # build
- # pip-tools
- # pytest
+ # -c requirements.txt
+ # exceptiongroup
+ # virtualenv
uritemplate==3.0.1
# via drf-yasg
-virtualenv==20.26.6
+virtualenv==20.36.1
# via pre-commit
-wheel==0.38.1
- # via pip-tools
-
-# The following packages are considered to be unsafe in a requirements file:
-# pip
-# setuptools
diff --git a/requirements.in b/requirements.in
index c86a1d37c3..2d59962414 100644
--- a/requirements.in
+++ b/requirements.in
@@ -2,38 +2,40 @@ django-cte==1.3.3
django-mptt==0.16.0
django-filter==23.5
djangorestframework==3.15.1
-psycopg2-binary==2.9.10
+psycopg2-binary==2.9.11
django-js-reverse==0.10.2
django-registration==3.4
-le-utils==0.2.14
-gunicorn==23.0.0
+le-utils==0.2.17
+gunicorn==25.1.0
django-postmark==0.1.6
jsonfield==3.1.0
-celery==5.5.3
+celery==5.6.3
redis
-python-postmark==0.6.1
+python-postmark==0.7.0
Django==3.2.24
django-webpack-loader==0.7.0
google-cloud-error-reporting
-google-cloud-storage
+google-cloud-storage==2.19.0
django-s3-storage==0.15.0
requests>=2.20.0
google-cloud-core
django-db-readonly==0.7.0
-google-cloud-kms==2.10.0
-google-crc32c==1.7.1
+google-cloud-kms==2.24.2
+google-crc32c==1.8.0
backoff
django-model-utils==5.0.0
django-redis
django-prometheus
sentry-sdk
html5lib==1.1
-pillow==11.3.0
+pillow==12.2.0
python-dateutil>=2.8.1
jsonschema>=3.2.0
django-celery-results
packaging>=21.0
-langcodes==3.5.0
-pydantic==2.11.7
-latex2mathml==3.78.0
-markdown-it-py==3.0.0
+pycountry
+langcodes==3.5.1
+pydantic==2.12.5
+latex2mathml==3.78.1
+markdown-it-py==4.0.0
+stripe>=5.0.0,<6.0.0
diff --git a/requirements.txt b/requirements.txt
index 2e227661d1..0d66015b7a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,5 @@
-#
-# This file is autogenerated by pip-compile with Python 3.10
-# by the following command:
-#
-# pip-compile requirements.in
-#
+# This file was autogenerated by uv via the following command:
+# uv pip compile requirements.in --output-file requirements.txt
amqp==5.1.1
# via kombu
annotated-types==0.7.0
@@ -26,16 +22,16 @@ botocore==1.20.75
# via
# boto3
# s3transfer
-cachetools==4.2.2
- # via google-auth
-celery==5.5.3
+celery==5.6.3
# via
# -r requirements.in
# django-celery-results
-certifi==2020.12.5
+certifi==2026.2.25
# via
# requests
# sentry-sdk
+cffi==2.0.0
+ # via cryptography
charset-normalizer==3.3.2
# via requests
click==8.1.3
@@ -52,6 +48,8 @@ click-repl==0.2.0
# via celery
confusable-homoglyphs==3.2.0
# via django-registration
+cryptography==46.0.7
+ # via google-auth
django==3.2.24
# via
# -r requirements.in
@@ -95,48 +93,67 @@ django-webpack-loader==0.7.0
# via -r requirements.in
djangorestframework==3.15.1
# via -r requirements.in
-google-api-core[grpc]==1.27.0
+exceptiongroup==1.3.1
+ # via celery
+google-api-core==2.30.0
# via
+ # google-cloud-appengine-logging
# google-cloud-core
# google-cloud-error-reporting
# google-cloud-kms
# google-cloud-logging
-google-auth==1.30.0
+ # google-cloud-storage
+google-auth==2.48.0
# via
# google-api-core
+ # google-cloud-appengine-logging
# google-cloud-core
+ # google-cloud-kms
# google-cloud-storage
-google-cloud-core==1.7.3
+google-cloud-appengine-logging==1.8.0
+ # via google-cloud-logging
+google-cloud-audit-log==0.4.0
+ # via google-cloud-logging
+google-cloud-core==2.5.0
# via
# -r requirements.in
# google-cloud-logging
# google-cloud-storage
google-cloud-error-reporting==1.4.0
# via -r requirements.in
-google-cloud-kms==2.10.0
+google-cloud-kms==2.24.2
# via -r requirements.in
-google-cloud-logging==2.3.1
+google-cloud-logging==2.7.1
# via google-cloud-error-reporting
-google-cloud-storage==1.41.1
+google-cloud-storage==2.19.0
# via -r requirements.in
-google-crc32c==1.7.1
+google-crc32c==1.8.0
# via
# -r requirements.in
+ # google-cloud-storage
# google-resumable-media
-google-resumable-media==1.3.0
+google-resumable-media==2.8.0
# via google-cloud-storage
-googleapis-common-protos[grpc]==1.57.0
+googleapis-common-protos==1.57.0
# via
# google-api-core
+ # google-cloud-audit-log
# grpc-google-iam-v1
+ # grpcio-status
grpc-google-iam-v1==0.12.4
- # via google-cloud-kms
-grpcio==1.53.2
+ # via
+ # google-cloud-kms
+ # google-cloud-logging
+grpcio==1.78.0
# via
# google-api-core
+ # google-cloud-appengine-logging
# googleapis-common-protos
# grpc-google-iam-v1
-gunicorn==23.0.0
+ # grpcio-status
+grpcio-status==1.62.3
+ # via google-api-core
+gunicorn==25.1.0
# via -r requirements.in
html5lib==1.1
# via -r requirements.in
@@ -150,51 +167,52 @@ jmespath==0.10.0
# botocore
jsonfield==3.1.0
# via -r requirements.in
-jsonschema==4.25.0
+jsonschema==4.26.0
# via -r requirements.in
jsonschema-specifications==2024.10.1
# via jsonschema
-kombu==5.5.2
+kombu==5.6.1
# via celery
-langcodes==3.5.0
+langcodes==3.5.1
# via -r requirements.in
-language-data==1.3.0
- # via langcodes
-latex2mathml==3.78.0
+latex2mathml==3.78.1
# via -r requirements.in
-le-utils==0.2.14
+le-utils==0.2.17
# via -r requirements.in
-marisa-trie==1.2.1
- # via language-data
-markdown-it-py==3.0.0
+markdown-it-py==4.0.0
# via -r requirements.in
mdurl==0.1.2
# via markdown-it-py
-packaging==25.0
+packaging==26.0
# via
# -r requirements.in
# django-js-reverse
- # google-api-core
# google-cloud-error-reporting
- # google-cloud-kms
# gunicorn
-pillow==11.3.0
+ # kombu
+pillow==12.2.0
# via -r requirements.in
prometheus-client==0.10.1
# via django-prometheus
prompt-toolkit==3.0.23
# via click-repl
-proto-plus==1.18.1
+proto-plus==1.27.1
# via
+ # google-api-core
+ # google-cloud-appengine-logging
# google-cloud-error-reporting
# google-cloud-kms
# google-cloud-logging
protobuf==4.25.8
# via
# google-api-core
+ # google-cloud-appengine-logging
+ # google-cloud-audit-log
+ # google-cloud-kms
# googleapis-common-protos
+ # grpcio-status
# proto-plus
-psycopg2-binary==2.9.10
+psycopg2-binary==2.9.11
# via -r requirements.in
pyasn1==0.4.8
# via
@@ -202,9 +220,13 @@ pyasn1==0.4.8
# rsa
pyasn1-modules==0.2.8
# via google-auth
-pydantic==2.11.7
+pycountry==26.2.16
# via -r requirements.in
-pydantic-core==2.33.2
+pycparser==3.0
+ # via cffi
+pydantic==2.12.5
+ # via -r requirements.in
+pydantic-core==2.41.5
# via pydantic
pyparsing==2.4.7
# via httplib2
@@ -213,14 +235,13 @@ python-dateutil==2.9.0.post0
# -r requirements.in
# botocore
# celery
-python-postmark==0.6.1
+python-postmark==0.7.0
# via -r requirements.in
pytz==2022.1
# via
# django
# django-postmark
- # google-api-core
-redis==6.4.0
+redis==7.1.0
# via
# -r requirements.in
# django-redis
@@ -228,12 +249,13 @@ referencing==0.36.2
# via
# jsonschema
# jsonschema-specifications
-requests==2.32.4
+requests==2.33.0
# via
# -r requirements.in
# google-api-core
# google-cloud-storage
-rpds-py==0.24.0
+ # stripe
+rpds-py==0.30.0
# via
# jsonschema
# referencing
@@ -241,29 +263,32 @@ rsa==4.7.2
# via google-auth
s3transfer==0.4.2
# via boto3
-sentry-sdk==2.34.1
+sentry-sdk==2.48.0
# via -r requirements.in
six==1.16.0
# via
# click-repl
- # google-api-core
- # google-auth
- # google-cloud-core
- # google-resumable-media
# html5lib
# python-dateutil
sqlparse==0.4.1
# via django
-typing-extensions==4.13.0
+stripe==5.5.0
+ # via -r requirements.in
+typing-extensions==4.15.0
# via
+ # cryptography
+ # exceptiongroup
+ # grpcio
# pydantic
# pydantic-core
# referencing
# typing-inspection
-typing-inspection==0.4.1
+typing-inspection==0.4.2
# via pydantic
tzdata==2025.2
# via kombu
+tzlocal==5.3.1
+ # via celery
urllib3==1.26.18
# via
# botocore
@@ -278,6 +303,3 @@ wcwidth==0.2.5
# via prompt-toolkit
webencodings==0.5.1
# via html5lib
-
-# The following packages are considered to be unsafe in a requirements file:
-# setuptools
diff --git a/webpack.config.js b/webpack.config.js
index 04bb82bb5e..8c33e23a07 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -5,19 +5,23 @@ const process = require('node:process');
const fs = require('node:fs');
const { execSync } = require('node:child_process');
-const { NormalModuleReplacementPlugin } = require('webpack');
-const baseConfig = require('kolibri-tools/lib/webpack.config.base');
+const baseConfig = require('kolibri-build/src/webpack.config.base');
const { merge } = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const BundleTracker = require('kolibri-tools/lib/webpackBundleTracker');
+const BundleTracker = require('kolibri-build/src/webpackBundleTracker');
const CircularDependencyPlugin = require('circular-dependency-plugin');
-const WebpackRTLPlugin = require('kolibri-tools/lib/webpackRtlPlugin');
+const WebpackRTLPlugin = require('kolibri-build/src/webpackRtlPlugin');
const { InjectManifest } = require('workbox-webpack-plugin');
-// Function to detect if running in WSL
+const DEFAULT_WEBPACK_DEV_HOST = '127.0.0.1';
+
+/**
+ * Function to detect if running in WSL
+ * @return {boolean}
+ */
function isWSL() {
try {
const version = fs.readFileSync('/proc/version', 'utf8');
@@ -27,21 +31,30 @@ function isWSL() {
}
}
-// Function to get WSL IP address
-function getWSLIP() {
+/**
+ * Get the host for the webpack dev server.
+ * @return {string}
+ */
+function getWebpackDevHost() {
+ if (process.env.WEBPACK_DEV_HOST) {
+ return process.env.WEBPACK_DEV_HOST;
+ }
+
+ if (!isWSL()) {
+ return DEFAULT_WEBPACK_DEV_HOST;
+ }
+
try {
- const ip = execSync('hostname -I').toString().trim().split(' ')[0];
- return ip;
+ return execSync('hostname -I').toString().trim().split(' ')[0];
} catch (err) {
console.warn('Failed to get WSL IP address:', err);
- return '127.0.0.1';
+ return DEFAULT_WEBPACK_DEV_HOST;
}
}
const djangoProjectDir = path.resolve('contentcuration');
const staticFilesDir = path.resolve(djangoProjectDir, 'contentcuration', 'static');
const srcDir = path.resolve(djangoProjectDir, 'contentcuration', 'frontend');
-const dummyModule = path.resolve(srcDir, 'shared', 'styles', 'modulePlaceholder.js')
const bundleOutputDir = path.resolve(staticFilesDir, 'studio');
@@ -62,11 +75,8 @@ module.exports = (env = {}) => {
const pnpmNodeModules = path.join(rootDir, 'node_modules', '.pnpm', 'node_modules');
// Determine the appropriate dev server host and public path based on environment
- const isWSLEnvironment = isWSL();
- const devServerHost = isWSLEnvironment ? '0.0.0.0' : '127.0.0.1';
- const devPublicPath = isWSLEnvironment ?
- `http://${getWSLIP()}:4000/dist/` :
- 'http://127.0.0.1:4000/dist/';
+ const devServerHost = getWebpackDevHost();
+ const devPublicPath = `http://${devServerHost}:4000/dist/`;
const workboxPlugin = new InjectManifest({
swSrc: path.resolve(srcDir, 'serviceWorker/index.js'),
@@ -96,11 +106,12 @@ module.exports = (env = {}) => {
context: srcDir,
entry: {
// Use arrays for every entry to allow for hot reloading.
- channel_edit: ['./channelEdit/index.js'],
- channel_list: ['./channelList/index.js'],
- settings: ['./settings/index.js'],
- accounts: ['./accounts/index.js'],
- administration: ['./administration/index.js'],
+ // The rtlcss-stub must come first to set up the window global before any CSS chunk loading.
+ channel_edit: ['./shared/rtlcss-stub.js', './channelEdit/index.js'],
+ channel_list: ['./shared/rtlcss-stub.js', './channelList/index.js'],
+ settings: ['./shared/rtlcss-stub.js', './settings/index.js'],
+ accounts: ['./shared/rtlcss-stub.js', './accounts/index.js'],
+ administration: ['./shared/rtlcss-stub.js', './administration/index.js'],
// A simple code sandbox to play with components in
pdfJSWorker: ['pdfjs-dist/build/pdf.worker.entry.js'],
// Utility for taking screenshots inside an iframe sandbox
@@ -122,10 +133,8 @@ module.exports = (env = {}) => {
allowedHosts: [
'127.0.0.1',
'localhost',
- ].concat(
- // For WSL, allow the WSL IP address
- isWSLEnvironment ? [getWSLIP()] : []
- ),
+ getWebpackDevHost(),
+ ]
},
module: {
rules: [
@@ -186,15 +195,5 @@ module.exports = (env = {}) => {
],
stats: 'normal',
});
- if (dev) {
- config.entry.editorDev = './editorDev/index.js';
- } else {
- config.plugins.push(
- new NormalModuleReplacementPlugin(
- /styl$/,
- dummyModule
- ),
- )
- }
return config;
};