diff --git a/.github/translators.txt b/.github/translators.txt
index 14c51bd0b..fcbb9675d 100644
--- a/.github/translators.txt
+++ b/.github/translators.txt
@@ -530,3 +530,6 @@ Shadluk Avan (quldosh) :: Uzbek
Marci (MartonPoto) :: Hungarian
Michał Sadurski (wheeskeey) :: Polish
JanDziaslo :: Polish
+Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian
+Ilgiz Zigangirov (inov8) :: Russian
+Max Israelsson (Blezie) :: Swedish
diff --git a/app/Access/Oidc/OidcService.php b/app/Access/Oidc/OidcService.php
index d6f6ef156..a84bd3205 100644
--- a/app/Access/Oidc/OidcService.php
+++ b/app/Access/Oidc/OidcService.php
@@ -49,6 +49,11 @@ class OidcService
$url = $provider->getAuthorizationUrl();
session()->put('oidc_pkce_code', $provider->getPkceCode() ?? '');
+ $returnUrl = Theme::dispatch(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $url);
+ if (is_string($returnUrl)) {
+ $url = $returnUrl;
+ }
+
return [
'url' => $url,
'state' => $provider->getState(),
diff --git a/app/App/Providers/AppServiceProvider.php b/app/App/Providers/AppServiceProvider.php
index debba7944..5264c0dcc 100644
--- a/app/App/Providers/AppServiceProvider.php
+++ b/app/App/Providers/AppServiceProvider.php
@@ -65,6 +65,13 @@ class AppServiceProvider extends ServiceProvider
URL::forceScheme($isHttps ? 'https' : 'http');
}
+ // Set SMTP mail driver to use a local domain matching the app domain,
+ // which helps avoid defaulting to a 127.0.0.1 domain
+ if ($appUrl) {
+ $hostName = parse_url($appUrl, PHP_URL_HOST) ?: null;
+ config()->set('mail.mailers.smtp.local_domain', $hostName);
+ }
+
// Allow longer string lengths after upgrade to utf8mb4
Schema::defaultStringLength(191);
diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php
index 2cf581d38..671e5e1df 100644
--- a/app/App/Providers/ThemeServiceProvider.php
+++ b/app/App/Providers/ThemeServiceProvider.php
@@ -4,6 +4,8 @@ namespace BookStack\App\Providers;
use BookStack\Theming\ThemeEvents;
use BookStack\Theming\ThemeService;
+use BookStack\Theming\ThemeViews;
+use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class ThemeServiceProvider extends ServiceProvider
@@ -24,7 +26,26 @@ class ThemeServiceProvider extends ServiceProvider
{
// Boot up the theme system
$themeService = $this->app->make(ThemeService::class);
+ $viewFactory = $this->app->make('view');
+ $themeViews = new ThemeViews($viewFactory->getFinder());
+
+ // Use a custom include so that we can insert theme views before/after includes.
+ // This is done, even if no theme is active, so that view caching does not create problems
+ // when switching between themes or when switching a theme on/off.
+ $viewFactory->share('__themeViews', $themeViews);
+ Blade::directive('include', function ($expression) {
+ return "handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>";
+ });
+
+ if (!$themeService->getTheme()) {
+ return;
+ }
+
+ $themeService->loadModules();
$themeService->readThemeActions();
$themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
+
+ $themeViews->registerViewPathsForTheme($themeService->getModules());
+ $themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews);
}
}
diff --git a/app/App/helpers.php b/app/App/helpers.php
index 0e357e36a..8f210ecaf 100644
--- a/app/App/helpers.php
+++ b/app/App/helpers.php
@@ -81,8 +81,7 @@ function setting(?string $key = null, mixed $default = null): mixed
/**
* Get a path to a theme resource.
- * Returns null if a theme is not configured and
- * therefore a full path is not available for use.
+ * Returns null if a theme is not configured, and therefore a full path is not available for use.
*/
function theme_path(string $path = ''): ?string
{
diff --git a/app/Config/view.php b/app/Config/view.php
index 80bc9ef8f..2eb30b4c9 100644
--- a/app/Config/view.php
+++ b/app/Config/view.php
@@ -8,12 +8,6 @@
* Do not edit this file unless you're happy to maintain any changes yourself.
*/
-// Join up possible view locations
-$viewPaths = [realpath(base_path('resources/views'))];
-if ($theme = env('APP_THEME', false)) {
- array_unshift($viewPaths, base_path('themes/' . $theme));
-}
-
return [
// App theme
@@ -26,7 +20,7 @@ return [
// Most templating systems load templates from disk. Here you may specify
// an array of paths that should be checked for your views. Of course
// the usual Laravel view path has already been registered for you.
- 'paths' => $viewPaths,
+ 'paths' => [realpath(base_path('resources/views'))],
// Compiled View Path
// This option determines where all the compiled Blade templates will be
diff --git a/app/Console/Commands/InstallModuleCommand.php b/app/Console/Commands/InstallModuleCommand.php
new file mode 100644
index 000000000..20252525d
--- /dev/null
+++ b/app/Console/Commands/InstallModuleCommand.php
@@ -0,0 +1,312 @@
+argument('location');
+
+ // Get the ZIP file containing the module files
+ $zipPath = $this->getPathToZip($location);
+ if (!$zipPath) {
+ $this->cleanup();
+ return 1;
+ }
+
+ // Validate module zip file (metadata, size, etc...) and get module instance
+ $zip = new ThemeModuleZip($zipPath);
+ $themeModule = $this->validateAndGetModuleInfoFromZip($zip);
+ if (!$themeModule) {
+ $this->cleanup();
+ return 1;
+ }
+
+ // Get the theme folder in use, attempting to create one if no active theme in use
+ $themeFolder = $this->getThemeFolder();
+ if (!$themeFolder) {
+ $this->cleanup();
+ return 1;
+ }
+
+ // Get the modules folder of the theme, attempting to create it if not existing,
+ // and create a new module manager instance.
+ $moduleFolder = $this->getModuleFolder($themeFolder);
+ if (!$moduleFolder) {
+ $this->cleanup();
+ return 1;
+ }
+
+ $manager = new ThemeModuleManager($moduleFolder);
+
+ // Handle existing modules with the same name
+ $exitingModulesWithName = $manager->getByName($themeModule->name);
+ $shouldContinue = $this->handleExistingModulesWithSameName($exitingModulesWithName, $manager);
+ if (!$shouldContinue) {
+ $this->cleanup();
+ return 1;
+ }
+
+ // Extract module ZIP into the theme modules folder
+ try {
+ $newModule = $manager->addFromZip($themeModule->name, $zip);
+ } catch (ThemeModuleException $exception) {
+ $this->error("ERROR: Failed to install module with error: {$exception->getMessage()}");
+ $this->cleanup();
+ return 1;
+ }
+
+ $this->info("Module \"{$newModule->name}\" ({$newModule->getVersion()}) successfully installed!");
+ $this->info("Install location: {$moduleFolder}/{$newModule->folderName}");
+ $this->cleanup();
+ return 0;
+ }
+
+ /**
+ * @param ThemeModule[] $existingModules
+ */
+ protected function handleExistingModulesWithSameName(array $existingModules, ThemeModuleManager $manager): bool
+ {
+ if (count($existingModules) === 0) {
+ return true;
+ }
+
+ $this->warn("The following modules already exist with the same name:");
+ foreach ($existingModules as $folder => $module) {
+ $this->line("{$module->name} ({$folder}:{$module->getVersion()}) - {$module->description}");
+ }
+ $this->line('');
+
+ $choices = ['Cancel module install', 'Add alongside existing module'];
+ if (count($existingModules) === 1) {
+ $choices[] = 'Replace existing module';
+ }
+ $choice = $this->choice("What would you like to do?", $choices, 0, null, false);
+ if ($choice === 'Cancel module install') {
+ return false;
+ }
+
+ if ($choice === 'Replace existing module') {
+ $existingModuleFolder = array_key_first($existingModules);
+ $this->info("Replacing existing module in {$existingModuleFolder} folder");
+ $manager->deleteModuleFolder($existingModuleFolder);
+ }
+
+ return true;
+ }
+
+ protected function getModuleFolder(string $themeFolder): string|null
+ {
+ $path = $themeFolder . DIRECTORY_SEPARATOR . 'modules';
+
+ if (file_exists($path) && !is_dir($path)) {
+ $this->error("ERROR: Cannot create a modules folder, file already exists at {$path}");
+ return null;
+ }
+
+ if (!file_exists($path)) {
+ $created = mkdir($path, 0755, true);
+ if (!$created) {
+ $this->error("ERROR: Failed to create a modules folder at {$path}");
+ return null;
+ }
+ }
+
+ return $path;
+ }
+
+ protected function getThemeFolder(): string|null
+ {
+ $path = theme_path('');
+ if (!$path || !is_dir($path)) {
+ $shouldCreate = $this->confirm('No active theme folder found, would you like to create one?');
+ if (!$shouldCreate) {
+ return null;
+ }
+
+ $folder = 'custom';
+ while (file_exists(base_path("themes" . DIRECTORY_SEPARATOR . $folder))) {
+ $folder = 'custom-' . Str::random(4);
+ }
+
+ $path = base_path("themes/{$folder}");
+ $created = mkdir($path, 0755, true);
+ if (!$created) {
+ $this->error('Failed to create a theme folder to use. This may be a permissions issue. Try manually configuring an active theme');
+ return null;
+ }
+
+ $this->info("Created theme folder at {$path}");
+ $this->warn("You will need to set APP_THEME={$folder} in your BookStack env configuration to enable this theme!");
+ }
+
+ return $path;
+ }
+
+ protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip): ThemeModule|null
+ {
+ if (!$zip->exists()) {
+ $this->error("ERROR: Cannot open ZIP file at {$zip->getPath()}");
+ return null;
+ }
+
+ if ($zip->getContentsSize() > (50 * 1024 * 1024)) {
+ $this->error("ERROR: Module ZIP file contents are too large. Maximum size is 50MB");
+ return null;
+ }
+
+ try {
+ $themeModule = $zip->getModuleInstance();
+ } catch (ThemeModuleException $exception) {
+ $this->error("ERROR: Failed to read module metadata with error: {$exception->getMessage()}");
+ return null;
+ }
+
+ return $themeModule;
+ }
+
+ protected function downloadModuleFile(string $location): string|null
+ {
+ $httpRequests = app()->make(HttpRequestService::class);
+ $client = $httpRequests->buildClient(30, ['stream' => true]);
+ $originalUrl = parse_url($location);
+ $currentLocation = $location;
+ $maxRedirects = 3;
+ $redirectCount = 0;
+
+ // Follow redirects up to 3 times for the same hostname
+ do {
+ $resp = $client->sendRequest(new Request('GET', $currentLocation));
+ $statusCode = $resp->getStatusCode();
+
+ if ($statusCode >= 300 && $statusCode < 400 && $redirectCount < $maxRedirects) {
+ $redirectLocation = $resp->getHeaderLine('Location');
+ if ($redirectLocation) {
+ $redirectUrl = parse_url($redirectLocation);
+ if (
+ ($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '')
+ && ($originalUrl['scheme'] ?? '') === ($redirectUrl['scheme'] ?? '')
+ && ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? '')
+ ) {
+ $currentLocation = $redirectLocation;
+ $redirectCount++;
+ continue;
+ }
+ }
+ }
+
+ break;
+ } while (true);
+
+ if ($resp->getStatusCode() >= 300) {
+ $this->error("ERROR: Failed to download module from {$location}");
+ $this->error("Download failed with status code {$resp->getStatusCode()}");
+ return null;
+ }
+
+ $tempFile = tempnam(sys_get_temp_dir(), 'bookstack_module_');
+ $fileHandle = fopen($tempFile, 'w');
+ $respBody = $resp->getBody();
+ $size = 0;
+ $maxSize = 50 * 1024 * 1024;
+
+ while (!$respBody->eof()) {
+ fwrite($fileHandle, $respBody->read(1024));
+ $size += 1024;
+ if ($size > $maxSize) {
+ fclose($fileHandle);
+ unlink($tempFile);
+ $this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB");
+ return '';
+ }
+ }
+
+ fclose($fileHandle);
+
+ $this->cleanupActions[] = function () use ($tempFile) {
+ unlink($tempFile);
+ };
+
+ return $tempFile;
+ }
+
+ protected function getPathToZip(string $location): string|null
+ {
+ $lowerLocation = strtolower($location);
+ $isRemote = str_starts_with($lowerLocation, 'http://') || str_starts_with($lowerLocation, 'https://');
+
+ if ($isRemote) {
+ // Warning about fetching from source
+ $host = parse_url($location, PHP_URL_HOST);
+ $this->warn("\nThis will download a module from: {$host}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.");
+ $trustHost = $this->confirm('Are you sure you trust this source?');
+ if (!$trustHost) {
+ return null;
+ }
+
+ // Check if the connection is http. If so, warn the user.
+ if (str_starts_with($lowerLocation, 'http://')) {
+ $this->warn("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks.");
+ if (!$this->confirm('Are you sure you want to continue without HTTPS?')) {
+ return null;
+ }
+ }
+
+ // Download ZIP and get its location
+ return $this->downloadModuleFile($location);
+ }
+
+ // Validate the file and get the full location
+ $zipPath = realpath($location);
+
+ if (!$zipPath || !is_file($zipPath)) {
+ $this->error("ERROR: Module file not found at {$location}");
+ return null;
+ }
+
+ $this->warn("\nThis will install a module from: {$zipPath}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.");
+ $trustHost = $this->confirm('Are you sure you want to install this module?');
+ if (!$trustHost) {
+ return null;
+ }
+
+ return $zipPath;
+ }
+
+ protected function cleanup(): void
+ {
+ foreach ($this->cleanupActions as $action) {
+ $action();
+ }
+ }
+}
diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php
index 325f0583c..c47ece225 100644
--- a/app/Entities/Controllers/BookApiController.php
+++ b/app/Entities/Controllers/BookApiController.php
@@ -7,11 +7,14 @@ use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Queries\BookQueries;
+use BookStack\Entities\Queries\BookshelfQueries;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Tools\BookContents;
use BookStack\Http\ApiController;
use BookStack\Permissions\Permission;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
@@ -21,6 +24,7 @@ class BookApiController extends ApiController
protected BookRepo $bookRepo,
protected BookQueries $queries,
protected PageQueries $pageQueries,
+ protected BookshelfQueries $shelfQueries,
) {
}
@@ -60,13 +64,20 @@ class BookApiController extends ApiController
* View the details of a single book.
* The response data will contain a 'content' property listing the chapter and pages directly within, in
* the same structure as you'd see within the BookStack interface when viewing a book. Top-level
- * contents will have a 'type' property to distinguish between pages & chapters.
+ * contents will have a 'type' property to distinguish between pages and chapters.
*/
public function read(string $id)
{
$book = $this->queries->findVisibleByIdOrFail(intval($id));
$book = $this->forJsonDisplay($book);
- $book->load(['createdBy', 'updatedBy', 'ownedBy']);
+ $book->load([
+ 'createdBy',
+ 'updatedBy',
+ 'ownedBy',
+ 'shelves' => function (BelongsToMany $query) {
+ $query->select(['id', 'name', 'slug'])->scopes('visible');
+ }
+ ]);
$contents = (new BookContents($book))->getTree(true, false)->all();
$contentsApiData = (new ApiEntityListFormatter($contents))
diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php
index c94057fa9..fca530f8a 100644
--- a/app/Entities/Controllers/BookController.php
+++ b/app/Entities/Controllers/BookController.php
@@ -224,9 +224,14 @@ class BookController extends Controller
{
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
$this->checkOwnablePermission(Permission::BookDelete, $book);
+ $contextShelf = $this->shelfContext->getContextualShelfForBook($book);
$this->bookRepo->destroy($book);
+ if ($contextShelf) {
+ return redirect($contextShelf->getUrl());
+ }
+
return redirect('/books');
}
diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php
index 42dcc8f8f..320346512 100644
--- a/app/Entities/Models/Bookshelf.php
+++ b/app/Entities/Models/Bookshelf.php
@@ -19,7 +19,7 @@ class Bookshelf extends Entity implements HasDescriptionInterface, HasCoverInter
public float $searchFactor = 1.2;
- protected $hidden = ['image_id', 'deleted_at', 'description_html', 'priority', 'default_template_id', 'sort_rule_id', 'entity_id', 'entity_type', 'chapter_id', 'book_id'];
+ protected $hidden = ['pivot', 'image_id', 'deleted_at', 'description_html', 'priority', 'default_template_id', 'sort_rule_id', 'entity_id', 'entity_type', 'chapter_id', 'book_id'];
protected $fillable = ['name'];
/**
diff --git a/app/Entities/Tools/EntityHtmlDescription.php b/app/Entities/Tools/EntityHtmlDescription.php
index 6bbfb9b66..052088c04 100644
--- a/app/Entities/Tools/EntityHtmlDescription.php
+++ b/app/Entities/Tools/EntityHtmlDescription.php
@@ -51,6 +51,11 @@ class EntityHtmlDescription
return $html;
}
+ $isEmpty = empty(trim(strip_tags($html)));
+ if ($isEmpty) {
+ return '
';
+ }
+
$filter = new HtmlContentFilter(new HtmlContentFilterConfig());
return $filter->filterString($html);
}
diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php
index 4f72e7c49..8d89a86cf 100644
--- a/app/Entities/Tools/PageContent.php
+++ b/app/Entities/Tools/PageContent.php
@@ -39,7 +39,14 @@ class PageContent
public function setNewHTML(string $html, User $updater): void
{
$html = $this->extractBase64ImagesFromHtml($html, $updater);
- $this->page->html = $this->formatHtml($html);
+ $html = $this->formatHtml($html);
+
+ $themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_PRE_STORE, $html, $this->page);
+ if (is_string($themeResult)) {
+ $html = $themeResult;
+ }
+
+ $this->page->html = $html;
$this->page->text = $this->toPlainText();
$this->page->markdown = '';
}
@@ -52,7 +59,14 @@ class PageContent
$markdown = $this->extractBase64ImagesFromMarkdown($markdown, $updater);
$this->page->markdown = $markdown;
$html = (new MarkdownToHtml($markdown))->convert();
- $this->page->html = $this->formatHtml($html);
+ $html = $this->formatHtml($html);
+
+ $themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_PRE_STORE, $html, $this->page);
+ if (is_string($themeResult)) {
+ $html = $themeResult;
+ }
+
+ $this->page->html = $html;
$this->page->text = $this->toPlainText();
}
@@ -81,7 +95,7 @@ class PageContent
/**
* Convert all inline base64 content to uploaded image files.
- * Regex is used to locate the start of data-uri definitions then
+ * Regex is used to locate the start of data-uri definitions, then
* manual looping over content is done to parse the whole data uri.
* Attempting to capture the whole data uri using regex can cause PHP
* PCRE limits to be hit with larger, multi-MB, files.
@@ -301,7 +315,7 @@ class PageContent
$html = $this->page->html ?? '';
if (empty($html)) {
- return $html;
+ return $this->handlePostRender('');
}
$doc = new HtmlDocument($html);
@@ -322,7 +336,7 @@ class PageContent
$cacheKey = $this->getContentCacheKey($doc->getBodyInnerHtml());
$cached = cache()->get($cacheKey, null);
if ($cached !== null) {
- return $cached;
+ return $this->handlePostRender($cached);
}
$filterConfig = HtmlContentFilterConfig::fromConfigString(config('app.content_filtering'));
@@ -332,7 +346,13 @@ class PageContent
$cacheTime = 86400 * 7; // 1 week
cache()->put($cacheKey, $filtered, $cacheTime);
- return $filtered;
+ return $this->handlePostRender($filtered);
+ }
+
+ protected function handlePostRender(string $html): string
+ {
+ $themeResult = Theme::dispatch(ThemeEvents::PAGE_CONTENT_POST_RENDER, $html, $this->page);
+ return is_string($themeResult) ? $themeResult : $html;
}
protected function getContentCacheKey(string $html): string
diff --git a/app/Theming/CustomHtmlHeadContentProvider.php b/app/Theming/CustomHtmlHeadContentProvider.php
index 9f794a077..209070997 100644
--- a/app/Theming/CustomHtmlHeadContentProvider.php
+++ b/app/Theming/CustomHtmlHeadContentProvider.php
@@ -12,7 +12,8 @@ class CustomHtmlHeadContentProvider
{
public function __construct(
protected CspService $cspService,
- protected Cache $cache
+ protected Cache $cache,
+ protected ThemeService $themeService,
) {
}
@@ -23,8 +24,9 @@ class CustomHtmlHeadContentProvider
public function forWeb(): string
{
$content = $this->getSourceContent();
- $hash = md5($content);
+ $hash = md5($content) . ':' . $this->themeService->getModulesHash();
$html = $this->cache->remember('custom-head-web:' . $hash, 86400, function () use ($content) {
+ $content .= "\n" . $this->getModuleHeadContent();
return HtmlNonceApplicator::prepare($content);
});
@@ -53,4 +55,23 @@ class CustomHtmlHeadContentProvider
{
return setting('app-custom-head', '');
}
+
+ /**
+ * Get any custom head content from installed modules.
+ */
+ protected function getModuleHeadContent(): string
+ {
+ $content = '';
+ foreach ($this->themeService->getModules() as $module) {
+ $headContentPath = $module->path('head');
+ if (file_exists($headContentPath) && is_dir($headContentPath)) {
+ $htmlFiles = glob($headContentPath . '/*.html');
+ foreach ($htmlFiles as $file) {
+ $content .= file_get_contents($file);
+ }
+ }
+ }
+
+ return $content;
+ }
}
diff --git a/app/Theming/ThemeController.php b/app/Theming/ThemeController.php
index 1eecc6974..c26767803 100644
--- a/app/Theming/ThemeController.php
+++ b/app/Theming/ThemeController.php
@@ -5,21 +5,22 @@ namespace BookStack\Theming;
use BookStack\Facades\Theme;
use BookStack\Http\Controller;
use BookStack\Util\FilePathNormalizer;
+use Symfony\Component\HttpFoundation\StreamedResponse;
class ThemeController extends Controller
{
/**
* Serve a public file from the configured theme.
*/
- public function publicFile(string $theme, string $path)
+ public function publicFile(string $theme, string $path): StreamedResponse
{
$cleanPath = FilePathNormalizer::normalize($path);
if ($theme !== Theme::getTheme() || !$cleanPath) {
abort(404);
}
- $filePath = theme_path("public/{$cleanPath}");
- if (!file_exists($filePath)) {
+ $filePath = Theme::findFirstFile("public/{$cleanPath}");
+ if (!$filePath) {
abort(404);
}
diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php
index 44630acae..511a9c1de 100644
--- a/app/Theming/ThemeEvents.php
+++ b/app/Theming/ThemeEvents.php
@@ -87,6 +87,17 @@ class ThemeEvents
*/
const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
+ /**
+ * OIDC auth pre-redirect event.
+ * Runs just before BookStack redirects the user to the identity provider for authentication.
+ * Provides the redirect URL that will be used.
+ * If the listener returns a string value, that will be used as the redirect URL instead.
+ *
+ * @param string $redirectUrl
+ * @return string|null
+ */
+ const OIDC_AUTH_PRE_REDIRECT = 'oidc_auth_pre_redirect';
+
/**
* OIDC ID token pre-validate event.
* Runs just before BookStack validates the user ID token data upon login.
@@ -100,6 +111,31 @@ class ThemeEvents
*/
const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate';
+ /**
+ * Page content post-render event.
+ * Runs after any display rendering of page content, typically when page content is being processed for viewing.
+ * Rendering typically includes parsing of page includes, and content filtering.
+ * Provides the HTML content about to be shown, along with the related page instance.
+ * If the listener returns a string value, that will be used as the HTML content instead.
+ *
+ * @param string $html
+ * @param \BookStack\Entities\Models\Page $page
+ * @return string|null
+ */
+ const PAGE_CONTENT_POST_RENDER = 'page_content_post_render';
+
+ /**
+ * Page content pre-store event.
+ * Runs just before page HTML is stored in the database, after BookStack's own processing.
+ * Provides the HTML content about to be stored, along with the related page instance.
+ * If the listener returns a string value, that will be used as the HTML content instead.
+ *
+ * @param string $html
+ * @param \BookStack\Entities\Models\Page $page
+ * @return string|null
+ */
+ const PAGE_CONTENT_PRE_STORE = 'page_content_pre_store';
+
/**
* Page include parse event.
* Runs when a page include tag is being parsed, typically when page content is being processed for viewing.
@@ -134,6 +170,16 @@ class ThemeEvents
*/
const ROUTES_REGISTER_WEB_AUTH = 'routes_register_web_auth';
+
+ /**
+ * Theme register views event.
+ * Called by the theme system when a theme is active, so that custom view templates can be registered
+ * to be rendered in addition to existing app views.
+ *
+ * @param \BookStack\Theming\ThemeViews $themeViews
+ */
+ const THEME_REGISTER_VIEWS = 'theme_register_views';
+
/**
* Web before middleware action.
* Runs before the request is handled but after all other middleware apart from those
diff --git a/app/Theming/ThemeModule.php b/app/Theming/ThemeModule.php
new file mode 100644
index 000000000..ab5fc6145
--- /dev/null
+++ b/app/Theming/ThemeModule.php
@@ -0,0 +1,59 @@
+folderName}/{$component}");
+ }
+
+ public function getVersion(): string
+ {
+ return str_starts_with($this->version, 'v') ? $this->version : 'v' . $this->version;
+ }
+}
diff --git a/app/Theming/ThemeModuleException.php b/app/Theming/ThemeModuleException.php
new file mode 100644
index 000000000..4d296a64f
--- /dev/null
+++ b/app/Theming/ThemeModuleException.php
@@ -0,0 +1,7 @@
+|null */
+ protected array|null $loadedModules = null;
+
+ public function __construct(
+ protected string $modulesFolderPath
+ ) {
+ }
+
+ /**
+ * @return array
+ */
+ public function getByName(string $name): array
+ {
+ return array_filter($this->load(), fn(ThemeModule $module) => $module->name === $name);
+ }
+
+ public function deleteModuleFolder(string $moduleFolderName): void
+ {
+ $modules = $this->load();
+ $module = $modules[$moduleFolderName] ?? null;
+ if (!$module) {
+ return;
+ }
+
+ $moduleFolderPath = $module->path('');
+ if (!file_exists($moduleFolderPath)) {
+ return;
+ }
+
+ $this->deleteDirectoryRecursively($moduleFolderPath);
+ unset($this->loadedModules[$moduleFolderName]);
+ }
+
+ /**
+ * @throws ThemeModuleException
+ */
+ public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule
+ {
+ $baseFolderName = Str::limit(Str::slug($name), 40, '');
+ $folderName = $baseFolderName;
+ while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) {
+ $folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4);
+ }
+
+ $folderPath = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName;
+ $zip->extractTo($folderPath);
+
+ $module = $this->loadFromFolder($folderName);
+ if (!$module) {
+ throw new ThemeModuleException("Failed to load module from zip file after extraction");
+ }
+
+ return $module;
+ }
+
+ protected function deleteDirectoryRecursively(string $path): void
+ {
+ $items = array_diff(scandir($path), ['.', '..']);
+ foreach ($items as $item) {
+ $itemPath = $path . DIRECTORY_SEPARATOR . $item;
+ if (is_dir($itemPath)) {
+ $this->deleteDirectoryRecursively($itemPath);
+ } else {
+ $deleted = unlink($itemPath);
+ if (!$deleted) {
+ throw new ThemeModuleException("Failed to delete file at \"{$itemPath}\"");
+ }
+ }
+ }
+ rmdir($path);
+ }
+
+ public function load(): array
+ {
+ if ($this->loadedModules !== null) {
+ return $this->loadedModules;
+ }
+
+ if (!is_dir($this->modulesFolderPath)) {
+ return [];
+ }
+
+ $subFolders = array_filter(scandir($this->modulesFolderPath), function ($item) {
+ return $item !== '.' && $item !== '..' && is_dir($this->modulesFolderPath . DIRECTORY_SEPARATOR . $item);
+ });
+
+ $modules = [];
+
+ foreach ($subFolders as $folderName) {
+ $module = $this->loadFromFolder($folderName);
+ if ($module) {
+ $modules[$folderName] = $module;
+ }
+ }
+
+ $this->loadedModules = $modules;
+
+ return $modules;
+ }
+
+ protected function loadFromFolder(string $folderName): ThemeModule|null
+ {
+ $moduleJsonFile = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName . DIRECTORY_SEPARATOR . 'bookstack-module.json';
+ if (!file_exists($moduleJsonFile)) {
+ return null;
+ }
+
+ try {
+ $jsonContent = file_get_contents($moduleJsonFile);
+ $jsonData = json_decode($jsonContent, true);
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new ThemeModuleException("Invalid JSON in module file at \"{$moduleJsonFile}\": " . json_last_error_msg());
+ }
+
+ $module = ThemeModule::fromJson($jsonData, $folderName);
+ } catch (ThemeModuleException $exception) {
+ throw $exception;
+ } catch (\Exception $exception) {
+ throw new ThemeModuleException("Failed loading module from \"{$moduleJsonFile}\" with error: {$exception->getMessage()}");
+ }
+
+ return $module;
+ }
+}
diff --git a/app/Theming/ThemeModuleZip.php b/app/Theming/ThemeModuleZip.php
new file mode 100644
index 000000000..7029fa0c6
--- /dev/null
+++ b/app/Theming/ThemeModuleZip.php
@@ -0,0 +1,98 @@
+open($this->path);
+ $zip->extractTo($destinationPath);
+ $zip->close();
+ }
+
+ /**
+ * Read the module's JSON metadata to read it into a ThemeModule instance.
+ * @throws ThemeModuleException
+ */
+ public function getModuleInstance(): ThemeModule
+ {
+ $zip = new ZipArchive();
+ $open = $zip->open($this->path);
+ if ($open !== true) {
+ throw new ThemeModuleException("Unable to open zip file at {$this->path}");
+ }
+
+ $moduleJsonText = $zip->getFromName('bookstack-module.json');
+ $zip->close();
+
+ if ($moduleJsonText === false) {
+ throw new ThemeModuleException("bookstack-module.json not found within module ZIP at {$this->path}");
+ }
+
+ $moduleJson = json_decode($moduleJsonText, true);
+ if ($moduleJson === null) {
+ throw new ThemeModuleException("Could not read JSON from bookstack-module.json within module ZIP at {$this->path}");
+ }
+
+ return ThemeModule::fromJson($moduleJson, '_temp');
+ }
+
+ /**
+ * Get the path to the zip file.
+ */
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ /**
+ * Check if the zip file exists and that it appears to be a valid zip file.
+ */
+ public function exists(): bool
+ {
+ if (!file_exists($this->path)) {
+ return false;
+ }
+
+ $zip = new ZipArchive();
+ $open = $zip->open($this->path, ZipArchive::RDONLY);
+ if ($open === true) {
+ $zip->close();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the total size of the zip file contents when uncompressed.
+ */
+ public function getContentsSize(): int
+ {
+ $zip = new ZipArchive();
+
+ if ($zip->open($this->path) !== true) {
+ return 0;
+ }
+
+ $totalSize = 0;
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $stat = $zip->statIndex($i);
+ if ($stat !== false) {
+ $totalSize += $stat['size'];
+ }
+ }
+
+ $zip->close();
+
+ return $totalSize;
+ }
+}
diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php
index 4bdb6836b..864061c1c 100644
--- a/app/Theming/ThemeService.php
+++ b/app/Theming/ThemeService.php
@@ -6,6 +6,7 @@ use BookStack\Access\SocialDriverManager;
use BookStack\Exceptions\ThemeException;
use Illuminate\Console\Application;
use Illuminate\Console\Application as Artisan;
+use Illuminate\View\FileViewFinder;
use Symfony\Component\Console\Command\Command;
class ThemeService
@@ -15,6 +16,11 @@ class ThemeService
*/
protected array $listeners = [];
+ /**
+ * @var array
+ */
+ protected array $modules = [];
+
/**
* Get the currently configured theme.
* Returns an empty string if not configured.
@@ -76,20 +82,85 @@ class ThemeService
}
/**
- * Read any actions from the set theme path if the 'functions.php' file exists.
+ * Read any actions from the 'functions.php' file of the active theme or its modules.
*/
public function readThemeActions(): void
{
- $themeActionsFile = theme_path('functions.php');
- if ($themeActionsFile && file_exists($themeActionsFile)) {
+ $moduleFunctionFiles = array_map(function (ThemeModule $module): string {
+ return $module->path('functions.php');
+ }, $this->modules);
+ $allFunctionFiles = array_merge(array_values($moduleFunctionFiles), [theme_path('functions.php')]);
+ $filteredFunctionFiles = array_filter($allFunctionFiles, function (string $file): bool {
+ return $file && file_exists($file);
+ });
+
+ foreach ($filteredFunctionFiles as $functionFile) {
try {
- require $themeActionsFile;
+ require $functionFile;
} catch (\Error $exception) {
- throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}");
+ throw new ThemeException("Failed loading theme functions file at \"{$functionFile}\" with error: {$exception->getMessage()}");
}
}
}
+ /**
+ * Read the modules folder and load in any valid theme modules.
+ * @throws ThemeModuleException
+ */
+ public function loadModules(): void
+ {
+ $modulesFolder = theme_path('modules');
+ if (!$modulesFolder) {
+ return;
+ }
+
+ $this->modules = (new ThemeModuleManager($modulesFolder))->load();
+ }
+
+ /**
+ * Get all loaded theme modules.
+ * @return array
+ */
+ public function getModules(): array
+ {
+ return $this->modules;
+ }
+
+ /**
+ * Get a hash to represent the currently loaded modules.
+ */
+ public function getModulesHash(): string
+ {
+ $key = "";
+
+ foreach ($this->modules as $module) {
+ $key .= $module->name . ':' . $module->version . ';';
+ }
+
+ return md5($key);
+ }
+
+ /**
+ * Look for a specific file within the theme or its modules.
+ * Returns the first file found or null if not found.
+ */
+ public function findFirstFile(string $path): ?string
+ {
+ $themePath = theme_path($path);
+ if (file_exists($themePath)) {
+ return $themePath;
+ }
+
+ foreach ($this->modules as $module) {
+ $customizedFile = $module->path($path);
+ if (file_exists($customizedFile)) {
+ return $customizedFile;
+ }
+ }
+
+ return null;
+ }
+
/**
* @see SocialDriverManager::addSocialDriver
*/
diff --git a/app/Theming/ThemeViews.php b/app/Theming/ThemeViews.php
new file mode 100644
index 000000000..630ff9d8d
--- /dev/null
+++ b/app/Theming/ThemeViews.php
@@ -0,0 +1,115 @@
+>
+ */
+ protected array $beforeViews = [];
+
+ /**
+ * @var array>
+ */
+ protected array $afterViews = [];
+
+ public function __construct(
+ protected FileViewFinder $finder
+ ) {
+ }
+
+ /**
+ * Register any extra paths for where we may expect views to be located
+ * with the FileViewFinder, to make custom views available for use.
+ * @param ThemeModule[] $modules
+ */
+ public function registerViewPathsForTheme(array $modules): void
+ {
+ foreach ($modules as $module) {
+ $moduleViewsPath = $module->path('views');
+ if (file_exists($moduleViewsPath) && is_dir($moduleViewsPath)) {
+ $this->finder->prependLocation($moduleViewsPath);
+ }
+ }
+
+ $this->finder->prependLocation(theme_path());
+ }
+
+ /**
+ * Provide the response for a blade template view include.
+ */
+ public function handleViewInclude(string $viewPath, array $data = [], array $mergeData = []): string
+ {
+ if (!$this->hasRegisteredViews()) {
+ return view()->make($viewPath, $data, $mergeData)->render();
+ }
+
+ if (str_contains('book-tree', $viewPath)) {
+ dd($viewPath, $data);
+ }
+
+ $viewsContent = [
+ ...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data, $mergeData),
+ view()->make($viewPath, $data, $mergeData)->render(),
+ ...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data, $mergeData),
+ ];
+
+ return implode("\n", $viewsContent);
+ }
+
+ /**
+ * Register a custom view to be rendered before the given target view is included in the template system.
+ */
+ public function renderBefore(string $targetView, string $localView, int $priority = 50): void
+ {
+ $this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority);
+ }
+
+ /**
+ * Register a custom view to be rendered after the given target view is included in the template system.
+ */
+ public function renderAfter(string $targetView, string $localView, int $priority = 50): void
+ {
+ $this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority);
+ }
+
+ public function hasRegisteredViews(): bool
+ {
+ return !empty($this->beforeViews) || !empty($this->afterViews);
+ }
+
+ protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void
+ {
+ try {
+ $viewPath = $this->finder->find($localView);
+ } catch (\InvalidArgumentException $exception) {
+ throw new ThemeException("Expected registered view file with name \"{$localView}\" could not be found.");
+ }
+
+ if (!isset($location[$targetView])) {
+ $location[$targetView] = [];
+ }
+
+ $location[$targetView][$viewPath] = $priority;
+ }
+
+ /**
+ * @param array $viewSet
+ * @return string[]
+ */
+ protected function renderViewSets(array $viewSet, array $data, array $mergeData): array
+ {
+ $paths = array_keys($viewSet);
+ usort($paths, function (string $a, string $b) use ($viewSet) {
+ return $viewSet[$a] <=> $viewSet[$b];
+ });
+
+ return array_map(function (string $viewPath) use ($data, $mergeData) {
+ return view()->file($viewPath, $data, $mergeData)->render();
+ }, $paths);
+ }
+}
diff --git a/app/Translation/FileLoader.php b/app/Translation/FileLoader.php
index 1fec4d18b..6212506dd 100644
--- a/app/Translation/FileLoader.php
+++ b/app/Translation/FileLoader.php
@@ -2,6 +2,7 @@
namespace BookStack\Translation;
+use BookStack\Facades\Theme;
use Illuminate\Translation\FileLoader as BaseLoader;
class FileLoader extends BaseLoader
@@ -12,11 +13,6 @@ class FileLoader extends BaseLoader
* Extends Laravel's translation FileLoader to look in multiple directories
* so that we can load in translation overrides from the theme file if wanted.
*
- * Note: As of using Laravel 10, this may now be redundant since Laravel's
- * file loader supports multiple paths. This needs further testing though
- * to confirm if Laravel works how we expect, since we specifically need
- * the theme folder to be able to partially override core lang files.
- *
* @param string $locale
* @param string $group
* @param string|null $namespace
@@ -32,9 +28,18 @@ class FileLoader extends BaseLoader
if (is_null($namespace) || $namespace === '*') {
$themePath = theme_path('lang');
$themeTranslations = $themePath ? $this->loadPaths([$themePath], $locale, $group) : [];
- $originalTranslations = $this->loadPaths($this->paths, $locale, $group);
- return array_merge($originalTranslations, $themeTranslations);
+ $modules = Theme::getModules();
+ $moduleTranslations = [];
+ foreach ($modules as $module) {
+ $modulePath = $module->path('lang');
+ if (file_exists($modulePath)) {
+ $moduleTranslations = array_merge($moduleTranslations, $this->loadPaths([$modulePath], $locale, $group));
+ }
+ }
+
+ $originalTranslations = $this->loadPaths($this->paths, $locale, $group);
+ return array_merge($originalTranslations, $moduleTranslations, $themeTranslations);
}
return $this->loadNamespaced($locale, $group, $namespace);
diff --git a/app/Util/SvgIcon.php b/app/Util/SvgIcon.php
index ce6e1c23e..b1b14a487 100644
--- a/app/Util/SvgIcon.php
+++ b/app/Util/SvgIcon.php
@@ -2,6 +2,8 @@
namespace BookStack\Util;
+use BookStack\Facades\Theme;
+
class SvgIcon
{
public function __construct(
@@ -23,12 +25,9 @@ class SvgIcon
$attrString .= $attrName . '="' . $attr . '" ';
}
- $iconPath = resource_path('icons/' . $this->name . '.svg');
- $themeIconPath = theme_path('icons/' . $this->name . '.svg');
-
- if ($themeIconPath && file_exists($themeIconPath)) {
- $iconPath = $themeIconPath;
- } elseif (!file_exists($iconPath)) {
+ $defaultIconPath = resource_path('icons/' . $this->name . '.svg');
+ $iconPath = Theme::findFirstFile("icons/{$this->name}.svg") ?? $defaultIconPath;
+ if (!file_exists($iconPath)) {
return '';
}
diff --git a/composer.lock b/composer.lock
index d8ea00662..030cd1a73 100644
--- a/composer.lock
+++ b/composer.lock
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.373.0",
+ "version": "3.373.2",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "fb74a2dca7ae2363e929c5cea33a4a4db0d22690"
+ "reference": "483fba51c28b3a0c0647bf5100e0edca82090b18"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fb74a2dca7ae2363e929c5cea33a4a4db0d22690",
- "reference": "fb74a2dca7ae2363e929c5cea33a4a4db0d22690",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/483fba51c28b3a0c0647bf5100e0edca82090b18",
+ "reference": "483fba51c28b3a0c0647bf5100e0edca82090b18",
"shasum": ""
},
"require": {
@@ -92,12 +92,12 @@
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"composer/composer": "^2.7.8",
- "dms/phpunit-arraysubset-asserts": "^0.4.0",
+ "dms/phpunit-arraysubset-asserts": "^v0.5.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
"ext-sockets": "*",
- "phpunit/phpunit": "^9.6",
+ "phpunit/phpunit": "^10.0",
"psr/cache": "^2.0 || ^3.0",
"psr/simple-cache": "^2.0 || ^3.0",
"sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0",
@@ -153,9 +153,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.373.0"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.373.2"
},
- "time": "2026-03-11T18:33:36+00:00"
+ "time": "2026-03-13T18:08:30+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -4941,16 +4941,16 @@
},
{
"name": "robrichards/xmlseclibs",
- "version": "3.1.4",
+ "version": "3.1.5",
"source": {
"type": "git",
"url": "https://github.com/robrichards/xmlseclibs.git",
- "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd"
+ "reference": "03062be78178cbb5e8f605cd255dc32a14981f92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/bc87389224c6de95802b505e5265b0ec2c5bcdbd",
- "reference": "bc87389224c6de95802b505e5265b0ec2c5bcdbd",
+ "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92",
+ "reference": "03062be78178cbb5e8f605cd255dc32a14981f92",
"shasum": ""
},
"require": {
@@ -4977,9 +4977,9 @@
],
"support": {
"issues": "https://github.com/robrichards/xmlseclibs/issues",
- "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.4"
+ "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5"
},
- "time": "2025-12-08T11:57:53+00:00"
+ "time": "2026-03-13T10:31:56+00:00"
},
{
"name": "sabberworm/php-css-parser",
diff --git a/dev/api/responses/books-read.json b/dev/api/responses/books-read.json
index 582744f99..0c8ff473d 100644
--- a/dev/api/responses/books-read.json
+++ b/dev/api/responses/books-read.json
@@ -79,5 +79,17 @@
"path": "/uploads/images/cover_book/2020-01/sjovall_m117hUWMu40.jpg",
"type": "cover_book",
"uploaded_to": 16
- }
+ },
+ "shelves": [
+ {
+ "id": 1,
+ "name": "Great reads",
+ "slug": "great-reads"
+ },
+ {
+ "id": 5,
+ "name": "Personal Books",
+ "slug": "personal-books"
+ }
+ ]
}
\ No newline at end of file
diff --git a/dev/docs/logical-theme-system.md b/dev/docs/logical-theme-system.md
index 0063c4e8b..9457ca78b 100644
--- a/dev/docs/logical-theme-system.md
+++ b/dev/docs/logical-theme-system.md
@@ -99,6 +99,41 @@ Theme::listen(ThemeEvents::APP_BOOT, function($app) {
});
```
+## Custom View Registration Example
+
+Using the logical theme system, you can register custom views to be rendered before/after other existing views, providing a flexible way to add content without needing to override and/or replicate existing content. This is done by listening to the `THEME_REGISTER_VIEWS`.
+
+**Note:** You don't need to use this to override existing views, or register whole new main views to use, since that's done automatically based on their existence. This is just for advanced capabilities like inserting before/after existing views.
+
+This event provides a `ThemeViews` instance which has the following methods made available:
+
+- `renderBefore(string $targetView, string $localView, int $priority)`
+- `renderAfter(string $targetView, string $localView, int $priority)`
+
+The target view is the name of that which we want to insert our custom view relative to.
+The local view is the name of the view we want to add and render.
+The priority provides a suggestion to the ordering of view display, with lower numbers being shown first. This defaults to 50 if not provided.
+
+Here's an example of this in use:
+
+```php
+renderBefore('layouts.parts.header', 'welcome-banner', 4);
+ $themeViews->renderAfter('layouts.parts.header', 'information-alert');
+ $themeViews->renderAfter('layouts.parts.header', 'additions.password-notice', 20);
+});
+```
+
+In this example, we're inserting custom views before and after the main header bar.
+BookStack will look for a `welcome-banner.blade.php` file within our theme folder (or a theme module view folder) to render before the header. It'll look for the `information-alert.blade.php` and `additions/password-notice.blade.php` views to render afterwards.
+The password notice will be shown above the information alert view, since it has a specified priority of 20, whereas the information alert view would default to a priority of 50.
+
## Custom Command Registration Example
The logical theme system supports adding custom [artisan commands](https://laravel.com/docs/8.x/artisan) to BookStack.
diff --git a/dev/docs/theme-system-modules.md b/dev/docs/theme-system-modules.md
new file mode 100644
index 000000000..8aa9370ed
--- /dev/null
+++ b/dev/docs/theme-system-modules.md
@@ -0,0 +1,72 @@
+# Theme System Modules
+
+A theme system module is a collection of customizations using the [visual](visual-theme-system.md) and [logical](logical-theme-system.md) theme systems, provided along with some metadata, that can be installed alongside other modules within a theme. They can effectively be thought of as "plugins" or "extensions" that can be applied in addition to any customizations in the active theme.
+
+### Module Location
+
+Modules are contained within a folder themselves, which should be located inside a `modules` folder within a [BookStack theme folder](visual-theme-system.md#getting-started).
+As an example, starting from the `themes/` top-level folder of a BookStack instance:
+
+```txt
+themes
+└── my-theme
+ └── modules
+ ├── module-a
+ │ └── bookstack-module.json
+ └── module-b
+ └── bookstack-module.json
+```
+
+### Module Format
+
+A module exists as a folder in the location [as detailed above](#module-location).
+The content within the module folder should then follow this format:
+
+- `bookstack-module.json` - REQUIRED - A JSON file containing [the metadata](#module-json-metadata) for the module.
+- `functions.php` - OPTIONAL - A PHP file containing code for the [logical theme system](logical-theme-system.md).
+- `head/` - OPTIONAL - A folder containing HTML files which will be included into the HTML head of app-views.
+- `icons/` - OPTIONAL - A folder containing any icons to use as per [the visual theme system](visual-theme-system.md#customizing-icons).
+- `lang/` - OPTIONAL - A folder containing any language files to use as per [the visual theme system](visual-theme-system.md#customizing-text-content).
+- `public/` - OPTIONAL - A folder containing any files to expose into public web-space as per [the visual theme system](visual-theme-system.md#publicly-accessible-files).
+- `views/` - OPTIONAL - A folder containing any view additions or overrides as per [the visual theme system](visual-theme-system.md#customizing-view-files).
+
+You can create additional directories/files for your own needs within the module, but ideally name them something unique to prevent conflicts with the above structure.
+
+### Module JSON Metadata
+
+Modules are required to have a `bookstack-module.json` file in the top level directory of the module.
+This must be a JSON file with the following properties:
+
+- `name` - string - An (ideally unique) name for the module.
+- `description` - string - A short description of the module.
+- `version` - string - A string version number generally following [semantic versioning](https://semver.org/).
+ - Examples: `v0.4.0`, `4.3.12`, `v0.1.0-beta4`.
+
+### Customization Order/Precedence
+
+It's possible that multiple modules may override/customize the same content.
+Right now, there's no assurance in regard to the order in which modules may be loaded.
+Generally they will be used/searched in order of their module folder name, but this is not assured and should not be relied upon.
+
+It's also possible that modules customize the same content as the configured theme.
+In this scenario, the theme takes precedence. Modules are designed to be more portable and instance abstract, whereas the theme folder would typically be specific to the instance.
+This allows the theme to be used to customize or override module content for the BookStack instance, without altering the module code itself.
+
+### Module Best Practices
+
+Here are some general best practices when it comes to creating modules:
+
+- Use a unique name and clear description so the user can understand the purpose of the module.
+- Increment the metadata version on change, keeping to [semver](https://semver.org/) to indicate compatibility of new versions.
+- Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles.
+- When using/registering custom views, use some level of unique namespacing within the view path to prevent potential conflicts with other customizations.
+ - For example, I may store a view within my module as `views/my-module-name-welcome.blade.php`, to be registered as 'my-module-name-welcome'.
+ - This is important since views may be resolved from other modules or the active theme, which may/will override your module level view.
+
+### Distribution Format
+
+Modules are expected to be distributed as a compressed ZIP file, where the ZIP contents follow that of a module folder.
+BookStack provides a `php artisan bookstack:install-module` command which allows modules to be installed from these ZIP files, either from a local path or from a web URL.
+Currently, there's a hardcoded total filesize limit of 50MB for module contents installed via this method.
+
+There is not yet any direct update mechanism for modules, although this is something we may introduce in the future.
\ No newline at end of file
diff --git a/dev/docs/visual-theme-system.md b/dev/docs/visual-theme-system.md
index 8a76ddb00..327660be5 100644
--- a/dev/docs/visual-theme-system.md
+++ b/dev/docs/visual-theme-system.md
@@ -4,7 +4,7 @@ BookStack allows visual customization via the theme system which enables you to
This is part of the theme system alongside the [logical theme system](./logical-theme-system.md).
-**Note:** This theme system itself is maintained and supported but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates.
+**Note:** This theme system itself is maintained and supported, but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates.
## Getting Started
@@ -18,6 +18,9 @@ You'll need to tell BookStack to use your theme via the `APP_THEME` option in yo
Content placed in your `themes//` folder will override the original view files found in the `resources/views` folder. These files are typically [Laravel Blade](https://laravel.com/docs/10.x/blade) files.
As an example, I could override the `resources/views/books/parts/list-item.blade.php` file with my own template at the path `themes//books/parts/list-item.blade.php`.
+In addition to overriding original views, this could be used to add new views for use via the [logical theme system](logical-theme-system.md).
+By using the `THEME_REGISTER_VIEWS` logical event, you can also register your views to be rendered before/after existing views. An example of this can be found in our [logical theme guidance](logical-theme-system.md#custom-view-registration-example).
+
## Customizing Icons
SVG files placed in a `themes//icons` folder will override any icons of the same name within `resources/icons`. You'd typically want to follow the format convention of the existing icons, where no XML deceleration is included and no width & height attributes are set, to ensure optimal compatibility.
@@ -50,7 +53,7 @@ configured application theme.
There are some considerations to these publicly served files:
-- Only a predetermined range "web safe" content-types are currently served.
+- Only a predetermined range of "web safe" content-types are currently served.
- This limits running into potential insecure scenarios in serving problematic file types.
- A static 1-day cache time it set on files served from this folder.
- You can use alternative cache-breaking techniques (change of query string) upon changes if needed.
diff --git a/dev/licensing/js-library-licenses.txt b/dev/licensing/js-library-licenses.txt
index d7ad4ecc8..1c23c0937 100644
--- a/dev/licensing/js-library-licenses.txt
+++ b/dev/licensing/js-library-licenses.txt
@@ -3321,20 +3321,6 @@ Copyright: Copyright 2022 Romain Menke, Antonio Laguna <*******@******.**>
Source: git+https://github.com/csstools/postcss-plugins.git
Link: https://github.com/csstools/postcss-plugins/tree/main/packages/css-tokenizer#readme
-----------
-@emnapi/core
-License: MIT
-License File: node_modules/@emnapi/core/LICENSE
-Copyright: Copyright (c) 2021-present Toyobayashi
-Source: git+https://github.com/toyobayashi/emnapi.git
-Link: https://github.com/toyobayashi/emnapi#readme
------------
-@emnapi/runtime
-License: MIT
-License File: node_modules/@emnapi/runtime/LICENSE
-Copyright: Copyright (c) 2021-present Toyobayashi
-Source: git+https://github.com/toyobayashi/emnapi.git
-Link: https://github.com/toyobayashi/emnapi#readme
------------
@esbuild/linux-x64
License: MIT
Source: git+https://github.com/evanw/esbuild.git
@@ -3784,11 +3770,6 @@ Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/tsconfig/bases.git
Link: https://github.com/tsconfig/bases.git
-----------
-@tybys/wasm-util
-License: MIT
-Source: https://github.com/toyobayashi/wasm-util.git
-Link: https://github.com/toyobayashi/wasm-util.git
------------
@types/babel__core
License: MIT
License File: node_modules/@types/babel__core/LICENSE
diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt
index 6ae0b86b9..348fce2ff 100644
--- a/dev/licensing/php-library-licenses.txt
+++ b/dev/licensing/php-library-licenses.txt
@@ -8,7 +8,7 @@ aws/aws-sdk-php
License: Apache-2.0
License File: vendor/aws/aws-sdk-php/LICENSE
Source: https://github.com/aws/aws-sdk-php.git
-Link: http://aws.amazon.com/sdkforphp
+Link: https://aws.amazon.com/sdk-for-php
-----------
bacon/bacon-qr-code
License: BSD-2-Clause
diff --git a/lang/ar/settings.php b/lang/ar/settings.php
index dc95ac846..3191bbe3a 100644
--- a/lang/ar/settings.php
+++ b/lang/ar/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'الفصول الأولى',
'sort_rule_op_chapters_last' => 'الفصول الأخيرة',
'sorting_page_limits' => 'حدود العرض لكل صفحة',
- 'sorting_page_limits_desc' => 'تعيين عدد العناصر لإظهار كل صفحة في قوائم مختلفة داخل النظام. عادةً ما يكون الرقم الأقل هو الأكثر أداء، بينما وضع رقم أعلى يغني عن النقر على صفحات متعددة. يوصى باستخدام مضاعفات رقم ٣ (18 و 24 و 30 و إلخ...).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'الصيانة',
diff --git a/lang/bg/settings.php b/lang/bg/settings.php
index ae770c559..a1297e446 100644
--- a/lang/bg/settings.php
+++ b/lang/bg/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Поддръжка',
diff --git a/lang/bn/settings.php b/lang/bn/settings.php
index 6d0f4ab88..94ad059d4 100644
--- a/lang/bn/settings.php
+++ b/lang/bn/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/bs/settings.php b/lang/bs/settings.php
index c68605fe1..c4d1eb136 100644
--- a/lang/bs/settings.php
+++ b/lang/bs/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/ca/settings.php b/lang/ca/settings.php
index 352291fe5..a890b9809 100644
--- a/lang/ca/settings.php
+++ b/lang/ca/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Capítols a l\'inici',
'sort_rule_op_chapters_last' => 'Capítols al final',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Manteniment',
diff --git a/lang/cs/errors.php b/lang/cs/errors.php
index 176320642..5d086456b 100644
--- a/lang/cs/errors.php
+++ b/lang/cs/errors.php
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => 'Poskytnutý Token Secret neodpovídá použitému API tokenu',
'api_user_no_api_permission' => 'Vlastník použitého API tokenu nemá oprávnění provádět API volání',
'api_user_token_expired' => 'Platnost autorizačního tokenu vypršela',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Při používání API s ověřováním pomocí souborů cookie jsou povoleny pouze požadavky GET',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Při posílání testovacího e-mailu nastala chyba:',
diff --git a/lang/cs/settings.php b/lang/cs/settings.php
index 73ba6bfb0..ef25f1a20 100644
--- a/lang/cs/settings.php
+++ b/lang/cs/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Kapitoly jako první',
'sort_rule_op_chapters_last' => 'Kapitoly jako poslední',
'sorting_page_limits' => 'Počet zobrazených položek na stránce',
- 'sorting_page_limits_desc' => 'Nastavte, kolik položek se má zobrazit na stránce v různých seznamech na webu. Obvykle bude nižší počet výkonnější, zatímco vyšší počet eliminuje nutnost proklikávat se několika stránkami. Doporučuje se použít sudý násobek čísla 3 (18, 24, 30 atd.).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Údržba',
diff --git a/lang/cy/settings.php b/lang/cy/settings.php
index 29e86e28b..f4fbf0bba 100644
--- a/lang/cy/settings.php
+++ b/lang/cy/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Cynnal',
diff --git a/lang/da/settings.php b/lang/da/settings.php
index 2f161ed4f..cd869c62f 100644
--- a/lang/da/settings.php
+++ b/lang/da/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Kapitler først',
'sort_rule_op_chapters_last' => 'De sidste kapitler',
'sorting_page_limits' => 'Visningsgrænser pr. side',
- 'sorting_page_limits_desc' => 'Angiv, hvor mange elementer der skal vises pr. side i forskellige lister i systemet. Typisk vil et lavere beløb være mere effektivt, mens et højere beløb undgår behovet for at klikke sig igennem flere sider. Det anbefales at bruge et lige multiplum af 3 (18, 24, 30 osv.).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Vedligeholdelse',
diff --git a/lang/de/settings.php b/lang/de/settings.php
index a87408995..e57ac52c4 100644
--- a/lang/de/settings.php
+++ b/lang/de/settings.php
@@ -105,7 +105,7 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
'sort_rule_op_chapters_first' => 'Kapitel zuerst',
'sort_rule_op_chapters_last' => 'Kapitel zuletzt',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Wartung',
diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php
index ab9a075a0..fa175187b 100644
--- a/lang/de_informal/settings.php
+++ b/lang/de_informal/settings.php
@@ -105,7 +105,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'sort_rule_op_chapters_first' => 'Kapitel zuerst',
'sort_rule_op_chapters_last' => 'Kapitel zuletzt',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Wartung',
diff --git a/lang/el/settings.php b/lang/el/settings.php
index 67461604e..6ec5c4fdd 100644
--- a/lang/el/settings.php
+++ b/lang/el/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Συντήρηση',
diff --git a/lang/en/settings.php b/lang/en/settings.php
index c68605fe1..c4d1eb136 100644
--- a/lang/en/settings.php
+++ b/lang/en/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/es/errors.php b/lang/es/errors.php
index 76581257e..25f82a5d8 100644
--- a/lang/es/errors.php
+++ b/lang/es/errors.php
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => 'El secreto proporcionado para el token API usado es incorrecto',
'api_user_no_api_permission' => 'El propietario del token API usado no tiene permiso para hacer llamadas API',
'api_user_token_expired' => 'El token de autorización usado ha caducado',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Sólo se permiten peticiones GET cuando se utiliza el API con autenticación basada en cookies',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Error al enviar un email de prueba:',
diff --git a/lang/es/settings.php b/lang/es/settings.php
index 1a9927c8e..bfd3ce1cf 100644
--- a/lang/es/settings.php
+++ b/lang/es/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Capítulos al inicio',
'sort_rule_op_chapters_last' => 'Capítulos al final',
'sorting_page_limits' => 'Límites de visualización por página',
- 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo par de 3 (18, 24, 30, etc).',
+ 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo de 6.',
// Maintenance settings
'maint' => 'Mantenimiento',
diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php
index 5e052c51d..6f6b25bcf 100644
--- a/lang/es_AR/errors.php
+++ b/lang/es_AR/errors.php
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => 'El secreto proporcionado para el token API usado es incorrecto',
'api_user_no_api_permission' => 'El propietario del token API usado no tiene permiso para hacer llamadas API',
'api_user_token_expired' => 'El token de autorización usado ha caducado',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Sólo se permiten peticiones GET cuando se utiliza el API con autenticación basada en cookies',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Error al enviar un email de prueba:',
diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php
index 3eb41d2cc..90f43a6f2 100644
--- a/lang/es_AR/settings.php
+++ b/lang/es_AR/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Capítulos al inicio',
'sort_rule_op_chapters_last' => 'Capítulos al final',
'sorting_page_limits' => 'Límites de visualización por página',
- 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo par de 3 (18, 24, 30, etc).',
+ 'sorting_page_limits_desc' => 'Establecer cuántos elementos a mostrar por página en varias listas dentro del sistema. Normalmente una cantidad más baja rendirá mejor, mientras que una cantidad más alta evita la necesidad de hacer clic a través de varias páginas. Se recomienda utilizar un múltiplo de 6.',
// Maintenance settings
'maint' => 'Mantenimiento',
diff --git a/lang/et/errors.php b/lang/et/errors.php
index c9b9fbb4e..78a9d8c47 100644
--- a/lang/et/errors.php
+++ b/lang/et/errors.php
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => 'API tunnusele lisatud salajane võti ei ole korrektne',
'api_user_no_api_permission' => 'Selle API tunnuse omanikul ei ole õigust API päringuid teha',
'api_user_token_expired' => 'Volitustunnus on aegunud',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Küpsistega autentimisel on API kasutamisel lubatud ainult GET päringud',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Test e-kirja saatmisel tekkis viga:',
diff --git a/lang/et/settings.php b/lang/et/settings.php
index fbd9d9c7e..bc5a7794e 100644
--- a/lang/et/settings.php
+++ b/lang/et/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Peatükid eespool',
'sort_rule_op_chapters_last' => 'Peatükid tagapool',
'sorting_page_limits' => 'Leheküljepõhised kuvalimiidid',
- 'sorting_page_limits_desc' => 'Seadista, mitut objekti erinevates loendites ühel leheküljel kuvada. Väiksem väärtus tähendab reeglina paremat jõudlust, samas kui suurem väärtus vähendab vajadust mitut lehekülge läbi klikkida. Soovituslik on kasutada 3-ga jaguvat väärtust (18, 24, 30 jne).',
+ 'sorting_page_limits_desc' => 'Vali, mitu objekti erinevates nimekirjades ühel lehel kuvada. Madalam väärtus tähendab reeglina paremat jõudlust, samas kui kõrgem väärtus väldib vajadust mitmeid lehti läbi klikkida. Soovituslik on kasutada 6-ga jaguvat väärtust.',
// Maintenance settings
'maint' => 'Hooldus',
diff --git a/lang/eu/settings.php b/lang/eu/settings.php
index 9a9227b83..0f764dccb 100644
--- a/lang/eu/settings.php
+++ b/lang/eu/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Mantentze-lanak',
diff --git a/lang/fa/settings.php b/lang/fa/settings.php
index abbfce470..2fa115118 100644
--- a/lang/fa/settings.php
+++ b/lang/fa/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'ابتدا فصلها',
'sort_rule_op_chapters_last' => 'فصلها در آخر',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'نگهداری',
diff --git a/lang/fi/settings.php b/lang/fi/settings.php
index 499122fe3..adc47fe2d 100644
--- a/lang/fi/settings.php
+++ b/lang/fi/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Huolto',
diff --git a/lang/fr/errors.php b/lang/fr/errors.php
index a5a3614c2..b74fb5465 100644
--- a/lang/fr/errors.php
+++ b/lang/fr/errors.php
@@ -13,7 +13,7 @@ return [
'auth_pre_register_theme_prevention' => 'Le compte utilisateur n\'a pas pu être enregistré avec les informations fournies',
'email_already_confirmed' => 'Cet e-mail a déjà été validé, vous pouvez vous connecter.',
'email_confirmation_invalid' => 'Cette confirmation est invalide. Veuillez essayer de vous inscrire à nouveau.',
- 'email_confirmation_expired' => 'Le jeton de confirmation est périmé. Un nouvel e-mail vous a été envoyé.',
+ 'email_confirmation_expired' => 'Le jeton de confirmation a expiré. Un nouvel e-mail vous a été envoyé.',
'email_confirmation_awaiting' => 'L\'adresse e-mail du compte utilisé doit être confirmée',
'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => 'Le secret fourni pour le jeton d\'API utilisé est incorrect',
'api_user_no_api_permission' => 'Le propriétaire du jeton API utilisé n\'a pas la permission de passer des requêtes API',
'api_user_token_expired' => 'Le jeton d\'autorisation utilisé a expiré',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Seules les requêtes GET sont autorisées lors de l’utilisation de l’API avec une authentification basée sur les cookies',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Erreur émise lors de l\'envoi d\'un e-mail de test :',
diff --git a/lang/fr/settings.php b/lang/fr/settings.php
index 317e777b9..7b0987a48 100644
--- a/lang/fr/settings.php
+++ b/lang/fr/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapitres en premier',
'sort_rule_op_chapters_last' => 'Chapitres en dernier',
'sorting_page_limits' => 'Limite d\'affichage par page',
- 'sorting_page_limits_desc' => 'Définissez le nombre d’éléments à afficher par page dans les différentes listes du système. En général, un nombre plus faible offre de meilleures performances, tandis qu’un nombre plus élevé réduit le besoin de naviguer entre plusieurs pages. Il est recommandé d’utiliser un multiple pair de 3 (18, 24, 30, etc.).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/he/settings.php b/lang/he/settings.php
index 0b5034475..46150081a 100644
--- a/lang/he/settings.php
+++ b/lang/he/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'תחזוקה',
diff --git a/lang/hr/settings.php b/lang/hr/settings.php
index 6465d0ea7..0692c8d7a 100644
--- a/lang/hr/settings.php
+++ b/lang/hr/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Održavanje',
diff --git a/lang/hu/settings.php b/lang/hu/settings.php
index c6810ed68..53b1cdcc4 100644
--- a/lang/hu/settings.php
+++ b/lang/hu/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Karbantartás',
diff --git a/lang/id/settings.php b/lang/id/settings.php
index cc9426683..8bdd99e68 100644
--- a/lang/id/settings.php
+++ b/lang/id/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Bab di Urutan Pertama',
'sort_rule_op_chapters_last' => 'Bab di Urutan Terakhir',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Pemeliharaan',
diff --git a/lang/is/settings.php b/lang/is/settings.php
index 5699c88d6..b1f21ac10 100644
--- a/lang/is/settings.php
+++ b/lang/is/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Viðhald',
diff --git a/lang/it/errors.php b/lang/it/errors.php
index 99700088b..1d6c71636 100644
--- a/lang/it/errors.php
+++ b/lang/it/errors.php
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => 'Il token segreto fornito per il token API utilizzato non è corretto',
'api_user_no_api_permission' => 'Il proprietario del token API utilizzato non ha il permesso di effettuare chiamate API',
'api_user_token_expired' => 'Il token di autorizzazione utilizzato è scaduto',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Solo le richieste GET sono consentite quando si utilizza l\'API con autenticazione basata sui cookie',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Si è verificato un errore durante l\'invio di una e-mail di prova:',
diff --git a/lang/it/settings.php b/lang/it/settings.php
index 88c6a4c11..2b5819b2a 100644
--- a/lang/it/settings.php
+++ b/lang/it/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Capitoli Prima',
'sort_rule_op_chapters_last' => 'Capitoli dopo',
'sorting_page_limits' => 'Limiti Visualizzazione Per Pagina',
- 'sorting_page_limits_desc' => 'Imposta il numero di elementi da visualizzare per pagina nei vari elenchi all\'interno del sistema. In genere, un numero inferiore garantisce prestazioni migliori, mentre un numero più elevato evita la necessità di cliccare su più pagine. Si consiglia di utilizzare un multiplo pari di 3 (18, 24, 30, ecc...).',
+ 'sorting_page_limits_desc' => 'Imposta il numero di elementi da visualizzare per pagina nei vari elenchi del sistema. In genere, un numero inferiore garantisce prestazioni migliori, mentre un numero maggiore evita di dover sfogliare più pagine. Si consiglia di utilizzare un multiplo di 6.',
// Maintenance settings
'maint' => 'Manutenzione',
diff --git a/lang/ja/errors.php b/lang/ja/errors.php
index 7ce8db111..0c1c8d84a 100644
--- a/lang/ja/errors.php
+++ b/lang/ja/errors.php
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => '利用されたAPIトークンに対して提供されたシークレットが正しくありません',
'api_user_no_api_permission' => '使用されているAPIトークンの所有者には、API呼び出しを行う権限がありません',
'api_user_token_expired' => '認証トークンが期限切れです。',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Cookie ベースの認証で API を使用する場合、GET リクエストのみが許可されます',
// Settings & Maintenance
'maintenance_test_email_failure' => 'テストメール送信時にエラーが発生しました:',
diff --git a/lang/ja/settings.php b/lang/ja/settings.php
index 53b14233e..717a4c10f 100644
--- a/lang/ja/settings.php
+++ b/lang/ja/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'チャプタを最初に',
'sort_rule_op_chapters_last' => 'チャプタを最後に',
'sorting_page_limits' => 'ページング表示制限',
- 'sorting_page_limits_desc' => 'システム内の各種リストで1ページに表示するアイテム数を設定します。 通常、少ない数に設定するとパフォーマンスが向上し、多い数に設定するとページの移動操作が少なくなります。 3の倍数(18、24、30など)を使用することをお勧めします。',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'メンテナンス',
diff --git a/lang/ka/settings.php b/lang/ka/settings.php
index c68605fe1..c4d1eb136 100644
--- a/lang/ka/settings.php
+++ b/lang/ka/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/ko/settings.php b/lang/ko/settings.php
index 0488bfe14..97af673c6 100644
--- a/lang/ko/settings.php
+++ b/lang/ko/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => '챕터 우선 정렬',
'sort_rule_op_chapters_last' => '챕터 나중 정렬',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => '유지관리',
diff --git a/lang/ku/settings.php b/lang/ku/settings.php
index c68605fe1..c4d1eb136 100644
--- a/lang/ku/settings.php
+++ b/lang/ku/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/lt/settings.php b/lang/lt/settings.php
index bcc7c82bd..f797e567e 100644
--- a/lang/lt/settings.php
+++ b/lang/lt/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Priežiūra',
diff --git a/lang/lv/settings.php b/lang/lv/settings.php
index fb5cf13dd..9dc6bf402 100644
--- a/lang/lv/settings.php
+++ b/lang/lv/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Nodaļas pirmās',
'sort_rule_op_chapters_last' => 'Nodaļas pēdējās',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Apkope',
diff --git a/lang/nb/settings.php b/lang/nb/settings.php
index 61b1c3367..5fcaaaca6 100644
--- a/lang/nb/settings.php
+++ b/lang/nb/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Kapitler først',
'sort_rule_op_chapters_last' => 'Kapitler sist',
'sorting_page_limits' => 'Visningsgrenser for hver side',
- 'sorting_page_limits_desc' => 'Angi hvor mange elementer som skal vises på hver side i ulike lister i systemet. Et lavere antall vil vanligvis gi bedre ytelse, mens et høyere antall reduserer behovet for å bla gjennom mange sider. Det er anbefalt å bruke en multiplikasjon av 3 som gir partall (18, 24, 30 osv.).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Vedlikehold',
diff --git a/lang/ne/settings.php b/lang/ne/settings.php
index 37e59978e..dbc7d8e9f 100644
--- a/lang/ne/settings.php
+++ b/lang/ne/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'पहिले अध्यायहरू',
'sort_rule_op_chapters_last' => 'अन्त्यमा अध्यायहरू',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'सम्भार',
diff --git a/lang/nl/settings.php b/lang/nl/settings.php
index 63805f498..c8d071119 100644
--- a/lang/nl/settings.php
+++ b/lang/nl/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Hoofdstukken Eerst',
'sort_rule_op_chapters_last' => 'Hoofdstukken Laatst',
'sorting_page_limits' => 'Weergavelimiet Per Pagina',
- 'sorting_page_limits_desc' => 'Stel in hoeveel items er op een pagina worden laten zien in de verschillende lijstweergaves. Een lager aantal verbeterd de snelheid, een hoger aantal verminderd het doorklikken door pagina\'s. Een even veelvoud van 3 (18, 24, 30, etc...) wordt aanbevolen.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Onderhoud',
diff --git a/lang/nn/settings.php b/lang/nn/settings.php
index 6d2259bd8..e4b6e6af9 100644
--- a/lang/nn/settings.php
+++ b/lang/nn/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Vedlikehold',
diff --git a/lang/pl/settings.php b/lang/pl/settings.php
index 5954b005f..775d4f25d 100644
--- a/lang/pl/settings.php
+++ b/lang/pl/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Rozdziały na początku',
'sort_rule_op_chapters_last' => 'Rozdziały na końcu',
'sorting_page_limits' => 'Limity wyświetlania per strona',
- 'sorting_page_limits_desc' => 'Ustaw ile elementów pokazywać per strona w różnych listach w systemie. Zazwyczaj mniejsza ilość będzie bardziej wydajna, podczas gdy większa ilość unika konieczności przeglądania wielu stron. Zaleca się stosowanie parzystej wielokrotności 3 (18, 24, 30 itp...).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Konserwacja',
diff --git a/lang/pt/settings.php b/lang/pt/settings.php
index c2179e640..a59335b7c 100644
--- a/lang/pt/settings.php
+++ b/lang/pt/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Manutenção',
diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php
index 4d711076f..328715433 100644
--- a/lang/pt_BR/errors.php
+++ b/lang/pt_BR/errors.php
@@ -110,7 +110,7 @@ return [
'import_zip_cant_read' => 'Não foi possível ler o arquivo ZIP.',
'import_zip_cant_decode_data' => 'Não foi possível encontrar e decodificar o conteúdo ZIP data.json.',
'import_zip_no_data' => 'Os dados do arquivo ZIP não têm o conteúdo esperado livro, capítulo ou página.',
- 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
+ 'import_zip_data_too_large' => 'O conteúdo ZIP data.json excede o tamanho máximo de upload configurado para a aplicação.',
'import_validation_failed' => 'Falhou na validação da importação do ZIP com erros:',
'import_zip_failed_notification' => 'Falhou ao importar arquivo ZIP.',
'import_perms_books' => 'Você não tem as permissões necessárias para criar livros.',
@@ -126,7 +126,7 @@ return [
'api_incorrect_token_secret' => 'O segredo fornecido para o código de API usado está incorreto',
'api_user_no_api_permission' => 'O proprietário do código de API utilizado não tem permissão para fazer requisições de API',
'api_user_token_expired' => 'O código de autenticação expirou',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => 'Somente solicitações GET são permitidas ao usar a API com autenticação baseada em cookies',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Erro encontrado ao enviar uma mensagem eletrônica de teste:',
diff --git a/lang/pt_BR/notifications.php b/lang/pt_BR/notifications.php
index 8c98467c8..7f6c100b1 100644
--- a/lang/pt_BR/notifications.php
+++ b/lang/pt_BR/notifications.php
@@ -11,8 +11,8 @@ return [
'updated_page_subject' => 'Página atualizada: :pageName',
'updated_page_intro' => 'Uma página foi atualizada em :appName:',
'updated_page_debounce' => 'Para prevenir notificações em massa, por enquanto notificações não serão enviadas para você para próximas edições nessa página pelo mesmo editor.',
- 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
- 'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
+ 'comment_mention_subject' => 'Você foi mencionado em um comentário na página: :pageName',
+ 'comment_mention_intro' => 'Você foi mencionado em um comentário sobre :appName:',
'detail_page_name' => 'Nome da Página:',
'detail_page_path' => 'Caminho da Página:',
diff --git a/lang/pt_BR/preferences.php b/lang/pt_BR/preferences.php
index d2b7fc540..a92d971c3 100644
--- a/lang/pt_BR/preferences.php
+++ b/lang/pt_BR/preferences.php
@@ -23,7 +23,7 @@ return [
'notifications_desc' => 'Controle as notificações por e-mail que você recebe quando uma determinada atividade é executada no sistema.',
'notifications_opt_own_page_changes' => 'Notificar quando houver alterações em páginas que eu possuo',
'notifications_opt_own_page_comments' => 'Notificar comentários nas páginas que eu possuo',
- 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
+ 'notifications_opt_comment_mentions' => 'Notificar quando eu for mencionado em um comentário',
'notifications_opt_comment_replies' => 'Notificar ao responder aos meus comentários',
'notifications_save' => 'Salvar Preferências',
'notifications_update_success' => 'Preferências de notificação foram atualizadas!',
diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php
index 53947a36d..97b434727 100644
--- a/lang/pt_BR/settings.php
+++ b/lang/pt_BR/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Capítulos Primeiro',
'sort_rule_op_chapters_last' => 'Capítulos por Último',
'sorting_page_limits' => 'Limites de exibição por página',
- 'sorting_page_limits_desc' => 'Defina quantos itens serão exibidos por página em diferentes listas do sistema. Normalmente, um número menor proporciona melhor desempenho, enquanto um número maior evita a necessidade de clicar em várias páginas. É recomendado o uso de um múltiplo par de 3 (18, 24, 30, etc.).',
+ 'sorting_page_limits_desc' => 'Defina quantos itens mostrar por página em várias listas no sistema. Normalmente, uma quantidade menor será mais eficiente, enquanto uma quantidade maior evita a necessidade de clicar em várias páginas. Recomenda-se usar um múltiplo de 6.',
// Maintenance settings
'maint' => 'Manutenção',
@@ -197,13 +197,13 @@ return [
'role_import_content' => 'Importar conteúdo',
'role_editor_change' => 'Alterar página de edição',
'role_notifications' => 'Receber e gerenciar notificações',
- 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.',
+ 'role_permission_note_users_and_roles' => 'Essas permissões tecnicamente também fornecerão visibilidade e busca de usuários e perfis no sistema.',
'role_asset' => 'Permissões de Ativos',
'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um usuário altere seus próprios privilégios ou privilégios de outros usuários no sistema. Apenas atribua perfis com essas permissões para usuários confiáveis.',
'role_asset_desc' => 'Essas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por essas permissões.',
'role_asset_admins' => 'Administradores recebem automaticamente acesso a todo o conteúdo, mas essas opções podem mostrar ou ocultar as opções da Interface de Usuário.',
'role_asset_image_view_note' => 'Isso está relacionado à visibilidade no gerenciador de imagens. O acesso real dos arquivos de imagem carregados dependerá da opção de armazenamento de imagem do sistema.',
- 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.',
+ 'role_asset_users_note' => 'Essas permissões tecnicamente também fornecerão visibilidade e busca de usuários do sistema.',
'role_all' => 'Todos',
'role_own' => 'Próprio',
'role_controlled_by_asset' => 'Controlado pelos ativos nos quais o upload foi realizado',
diff --git a/lang/pt_BR/validation.php b/lang/pt_BR/validation.php
index e30ebc377..4d8139c4b 100644
--- a/lang/pt_BR/validation.php
+++ b/lang/pt_BR/validation.php
@@ -106,7 +106,7 @@ return [
'uploaded' => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.',
'zip_file' => 'O :attribute precisa fazer referência a um arquivo do ZIP.',
- 'zip_file_size' => 'The file :attribute must not exceed :size MB.',
+ 'zip_file_size' => 'O arquivo :attribute não deve exceder :size MB.',
'zip_file_mime' => 'O :attribute precisa fazer referência a um arquivo do tipo :validTypes, encontrado :foundType.',
'zip_model_expected' => 'Objeto de dados esperado, mas ":type" encontrado.',
'zip_unique' => 'O :attribute deve ser único para o tipo de objeto dentro do ZIP.',
diff --git a/lang/ro/settings.php b/lang/ro/settings.php
index 02052ef3c..d65a8e071 100644
--- a/lang/ro/settings.php
+++ b/lang/ro/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Mentenanţă',
diff --git a/lang/ru/entities.php b/lang/ru/entities.php
index 50f061115..bf224c10a 100644
--- a/lang/ru/entities.php
+++ b/lang/ru/entities.php
@@ -39,21 +39,21 @@ return [
'export_pdf' => 'PDF файл',
'export_text' => 'Текстовый файл',
'export_md' => 'Файл Markdown',
- 'export_zip' => 'Portable ZIP',
+ 'export_zip' => 'Портативный ZIP',
'default_template' => 'Шаблон страницы по умолчанию',
'default_template_explain' => 'Назначить шаблон страницы, который будет использоваться в качестве содержимого по умолчанию для всех страниц, созданных в этом элементе. Имейте в виду, что это будет работать, только если создатель страницы имеет доступ к выбранной странице шаблона.',
'default_template_select' => 'Выберите страницу шаблона',
'import' => 'Импорт',
- 'import_validate' => 'Validate Import',
+ 'import_validate' => 'Проверка импорта',
'import_desc' => 'Импортировать книги, главы и страницы с помощью ZIP-файла, экспортированного из этого или другого источника. Выберите ZIP-файл, чтобы продолжить. После загрузки и проверки файла вы сможете настроить и подтвердить импорт в следующем окне.',
- 'import_zip_select' => 'Select ZIP file to upload',
- 'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:',
- 'import_pending' => 'Pending Imports',
- 'import_pending_none' => 'No imports have been started.',
- 'import_continue' => 'Continue Import',
+ 'import_zip_select' => 'Выберите ZIP файл для загрузки',
+ 'import_zip_validation_errors' => 'Были обнаружены ошибки при проверке предоставленного ZIP файла:',
+ 'import_pending' => 'Ожидается импорт',
+ 'import_pending_none' => 'Импорт не был запущен.',
+ 'import_continue' => 'Продолжить импорт',
'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.',
'import_details' => 'Import Details',
- 'import_run' => 'Run Import',
+ 'import_run' => 'Запустить импорт',
'import_size' => ':size Import ZIP Size',
'import_uploaded_at' => 'Uploaded :relativeTime',
'import_uploaded_by' => 'Uploaded by',
@@ -61,7 +61,7 @@ return [
'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.',
'import_delete_confirm' => 'Are you sure you want to delete this import?',
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
- 'import_errors' => 'Import Errors',
+ 'import_errors' => 'Ошибки импорта',
'import_errors_desc' => 'The follow errors occurred during the import attempt:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
@@ -252,7 +252,7 @@ return [
'pages_edit_switch_to_markdown_stable' => 'Полное сохранение форматирования (HTML)',
'pages_edit_switch_to_wysiwyg' => 'Переключиться в WYSIWYG',
'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG',
- 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)',
+ 'pages_edit_switch_to_new_wysiwyg_desc' => '(В бета-тестировании)',
'pages_edit_set_changelog' => 'Задать список изменений',
'pages_edit_enter_changelog_desc' => 'Введите краткое описание внесенных изменений',
'pages_edit_enter_changelog' => 'Введите список изменений',
@@ -397,11 +397,11 @@ return [
'comment' => 'Комментарий',
'comments' => 'Комментарии',
'comment_add' => 'Комментировать',
- 'comment_none' => 'No comments to display',
+ 'comment_none' => 'Нет комментариев для отображения',
'comment_placeholder' => 'Оставить комментарий здесь',
'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
- 'comment_archived_count' => ':count Archived',
- 'comment_archived_threads' => 'Archived Threads',
+ 'comment_archived_count' => ':count архивировано',
+ 'comment_archived_threads' => 'Архивированные темы',
'comment_save' => 'Сохранить комментарий',
'comment_new' => 'Новый комментарий',
'comment_created' => 'прокомментировал :createDiff',
@@ -410,14 +410,14 @@ return [
'comment_deleted_success' => 'Комментарий удален',
'comment_created_success' => 'Комментарий добавлен',
'comment_updated_success' => 'Комментарий обновлен',
- 'comment_archive_success' => 'Comment archived',
- 'comment_unarchive_success' => 'Comment un-archived',
- 'comment_view' => 'View comment',
- 'comment_jump_to_thread' => 'Jump to thread',
+ 'comment_archive_success' => 'Комментарий заархивирован',
+ 'comment_unarchive_success' => 'Комментарий разархивирован',
+ 'comment_view' => 'Просмотреть комментарий',
+ 'comment_jump_to_thread' => 'Перейти к теме',
'comment_delete_confirm' => 'Удалить этот комментарий?',
'comment_in_reply_to' => 'В ответ на :commentId',
- 'comment_reference' => 'Reference',
- 'comment_reference_outdated' => '(Outdated)',
+ 'comment_reference' => 'Ссылка',
+ 'comment_reference_outdated' => '(Устаревшее)',
'comment_editor_explain' => 'Вот комментарии, которые были оставлены на этой странице. Комментарии могут быть добавлены и управляться при просмотре сохраненной страницы.',
// Revision
diff --git a/lang/ru/notifications.php b/lang/ru/notifications.php
index 289de42b6..96e853723 100644
--- a/lang/ru/notifications.php
+++ b/lang/ru/notifications.php
@@ -11,7 +11,7 @@ return [
'updated_page_subject' => 'Обновлена страница: :pageName',
'updated_page_intro' => 'Страница была обновлена в :appName:',
'updated_page_debounce' => 'Чтобы предотвратить массовые уведомления, в течение некоторого времени вы не будете получать уведомления о дальнейших правках этой страницы этим же редактором.',
- 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
+ 'comment_mention_subject' => 'Вы были упомянуты в комментарии на странице: :pageName',
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
'detail_page_name' => 'Имя страницы:',
diff --git a/lang/ru/settings.php b/lang/ru/settings.php
index 47839d520..76a2eebbf 100644
--- a/lang/ru/settings.php
+++ b/lang/ru/settings.php
@@ -75,7 +75,7 @@ return [
'reg_confirm_restrict_domain_placeholder' => 'Без ограничений',
// Sorting Settings
- 'sorting' => 'Lists & Sorting',
+ 'sorting' => 'Списки и сортировка',
'sorting_book_default' => 'Default Book Sort Rule',
'sorting_book_default_desc' => 'Выберите правило сортировки по умолчанию для новых книг. Это не повлияет на существующие книги, и может быть изменено для каждой книги отдельно.',
'sorting_rules' => 'Правила сортировки',
@@ -101,10 +101,10 @@ return [
'sort_rule_op_name_numeric' => 'По нумерации',
'sort_rule_op_created_date' => 'Created Date',
'sort_rule_op_updated_date' => 'Updated Date',
- 'sort_rule_op_chapters_first' => 'Chapters First',
+ 'sort_rule_op_chapters_first' => 'Главы в начале',
'sort_rule_op_chapters_last' => 'Главы в конце',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Обслуживание',
diff --git a/lang/sk/settings.php b/lang/sk/settings.php
index 04855a7f9..67671f6f8 100644
--- a/lang/sk/settings.php
+++ b/lang/sk/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Údržba',
diff --git a/lang/sl/settings.php b/lang/sl/settings.php
index 6eaed0a17..947621389 100644
--- a/lang/sl/settings.php
+++ b/lang/sl/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Vzdrževanje',
diff --git a/lang/sq/settings.php b/lang/sq/settings.php
index c68605fe1..c4d1eb136 100644
--- a/lang/sq/settings.php
+++ b/lang/sq/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/sr/settings.php b/lang/sr/settings.php
index d34ff3f3b..f6c86827e 100644
--- a/lang/sr/settings.php
+++ b/lang/sr/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Одржавање',
diff --git a/lang/sv/entities.php b/lang/sv/entities.php
index 08c44ff1f..680e0908a 100644
--- a/lang/sv/entities.php
+++ b/lang/sv/entities.php
@@ -397,7 +397,7 @@ return [
'comment' => 'Kommentar',
'comments' => 'Kommentarer',
'comment_add' => 'Lägg till kommentar',
- 'comment_none' => 'No comments to display',
+ 'comment_none' => 'Inga kommentarer att visa',
'comment_placeholder' => 'Lämna en kommentar här',
'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
'comment_archived_count' => ':count Archived',
diff --git a/lang/sv/preferences.php b/lang/sv/preferences.php
index 492081e59..7ebf26813 100644
--- a/lang/sv/preferences.php
+++ b/lang/sv/preferences.php
@@ -5,7 +5,7 @@
*/
return [
- 'my_account' => 'My Account',
+ 'my_account' => 'Mitt Konto',
'shortcuts' => 'Genvägar',
'shortcuts_interface' => 'UI Shortcut Preferences',
diff --git a/lang/sv/settings.php b/lang/sv/settings.php
index 2e86241da..773c4bff3 100644
--- a/lang/sv/settings.php
+++ b/lang/sv/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Underhåll',
diff --git a/lang/tk/settings.php b/lang/tk/settings.php
index c68605fe1..c4d1eb136 100644
--- a/lang/tk/settings.php
+++ b/lang/tk/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Maintenance',
diff --git a/lang/tr/settings.php b/lang/tr/settings.php
index 71d56000f..a33d3e0ac 100644
--- a/lang/tr/settings.php
+++ b/lang/tr/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Bakım',
diff --git a/lang/uk/settings.php b/lang/uk/settings.php
index 55966c01c..afeb2c489 100644
--- a/lang/uk/settings.php
+++ b/lang/uk/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Спочатку розділи',
'sort_rule_op_chapters_last' => 'Розділи останні',
'sorting_page_limits' => 'Обмеження відображення сторінок',
- 'sorting_page_limits_desc' => 'Кількість елементів для відображення в різних списках в системі. Зазвичай менша кількість буде більш продуктивною, в той час як більша кількість уникає необхідність натискання на кілька сторінок. Рекомендується використовувати парне кратне 3 (18, 24, 30 тощо).',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Обслуговування',
diff --git a/lang/uz/settings.php b/lang/uz/settings.php
index ad191143f..259aee71a 100644
--- a/lang/uz/settings.php
+++ b/lang/uz/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Xizmat',
diff --git a/lang/vi/settings.php b/lang/vi/settings.php
index 69eadddd3..f5f2377c8 100644
--- a/lang/vi/settings.php
+++ b/lang/vi/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => 'Chương trước',
'sort_rule_op_chapters_last' => 'Chương sau',
'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => 'Bảo trì',
diff --git a/lang/zh_CN/editor.php b/lang/zh_CN/editor.php
index 6dbca56e3..4bac4cf0d 100644
--- a/lang/zh_CN/editor.php
+++ b/lang/zh_CN/editor.php
@@ -48,7 +48,7 @@ return [
'superscript' => '上标',
'subscript' => '下标',
'text_color' => '文本颜色',
- 'highlight_color' => 'Highlight color',
+ 'highlight_color' => '高亮颜色',
'custom_color' => '自定义颜色',
'remove_color' => '移除颜色',
'background_color' => '背景色',
diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php
index 826a8ec1e..c4ec0414d 100644
--- a/lang/zh_CN/entities.php
+++ b/lang/zh_CN/entities.php
@@ -63,10 +63,10 @@ return [
'import_delete_desc' => '这将删除上传的ZIP文件,不能撤消。',
'import_errors' => '导入错误',
'import_errors_desc' => '在尝试导入过程中出现了以下错误:',
- 'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
- 'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
- 'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
- 'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
+ 'breadcrumb_siblings_for_page' => '导航页面',
+ 'breadcrumb_siblings_for_chapter' => '导航章节',
+ 'breadcrumb_siblings_for_book' => '导航书籍',
+ 'breadcrumb_siblings_for_bookshelf' => '导航书架',
// Permissions and restrictions
'permissions' => '权限',
@@ -399,7 +399,7 @@ return [
'comment_add' => '添加评论',
'comment_none' => '没有要显示的评论',
'comment_placeholder' => '在这里评论',
- 'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
+ 'comment_thread_count' => ':count 条',
'comment_archived_count' => ':count 条评论已存档',
'comment_archived_threads' => '已存档的贴子',
'comment_save' => '保存评论',
diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php
index 74814c6b0..98b264072 100644
--- a/lang/zh_CN/errors.php
+++ b/lang/zh_CN/errors.php
@@ -109,7 +109,7 @@ return [
'import_zip_cant_read' => '无法读取 ZIP 文件。',
'import_zip_cant_decode_data' => '无法找到并解码 ZIP data.json 内容。',
'import_zip_no_data' => 'ZIP 文件数据没有预期的书籍、章节或页面内容。',
- 'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
+ 'import_zip_data_too_large' => '超出最大上传大小。',
'import_validation_failed' => '导入 ZIP 验证失败,出现错误:',
'import_zip_failed_notification' => 'ZIP 文件导入失败。',
'import_perms_books' => '您缺少创建书籍所需的权限。',
@@ -125,7 +125,7 @@ return [
'api_incorrect_token_secret' => '给已给出的API所提供的密钥不正确',
'api_user_no_api_permission' => '使用过的 API 令牌的所有者没有进行API 调用的权限',
'api_user_token_expired' => '所使用的身份令牌已过期',
- 'api_cookie_auth_only_get' => 'Only GET requests are allowed when using the API with cookie-based authentication',
+ 'api_cookie_auth_only_get' => '使用基于 Cookie 的身份验证 API 时,仅允许 GET 请求。',
// Settings & Maintenance
'maintenance_test_email_failure' => '发送测试电子邮件时出现错误:',
diff --git a/lang/zh_CN/notifications.php b/lang/zh_CN/notifications.php
index e4eebf5cc..55fa6824a 100644
--- a/lang/zh_CN/notifications.php
+++ b/lang/zh_CN/notifications.php
@@ -11,8 +11,8 @@ return [
'updated_page_subject' => '页面更新::pageName',
'updated_page_intro' => ':appName: 中的一个页面已被更新',
'updated_page_debounce' => '为了防止出现大量通知,一段时间内您不会收到同一编辑者再次编辑本页面的通知。',
- 'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
- 'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
+ 'comment_mention_subject' => '在页面中被提及::pageName',
+ 'comment_mention_intro' => '在 :appName 中被提及:',
'detail_page_name' => '页面名称:',
'detail_page_path' => '页面路径:',
diff --git a/lang/zh_CN/preferences.php b/lang/zh_CN/preferences.php
index f89448dd3..0a1f165bb 100644
--- a/lang/zh_CN/preferences.php
+++ b/lang/zh_CN/preferences.php
@@ -23,7 +23,7 @@ return [
'notifications_desc' => '控制在系统内发生某些活动时您会收到的电子邮件通知。',
'notifications_opt_own_page_changes' => '在我拥有的页面被修改时通知我',
'notifications_opt_own_page_comments' => '在我拥有的页面上有新评论时通知我',
- 'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
+ 'notifications_opt_comment_mentions' => '当我在评论中被提及时通知我',
'notifications_opt_comment_replies' => '在有人回复我的频率时通知我',
'notifications_save' => '保存偏好设置',
'notifications_update_success' => '通知偏好设置已更新!',
diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php
index 3469752bf..e53e67aba 100644
--- a/lang/zh_CN/settings.php
+++ b/lang/zh_CN/settings.php
@@ -75,8 +75,8 @@ return [
'reg_confirm_restrict_domain_placeholder' => '尚未设置限制',
// Sorting Settings
- 'sorting' => 'Lists & Sorting',
- 'sorting_book_default' => 'Default Book Sort Rule',
+ 'sorting' => '列表和排序',
+ 'sorting_book_default' => '默认排序规则',
'sorting_book_default_desc' => '选择要应用于新书的默认排序规则。这不会影响现有书,并且可以每本书覆盖。',
'sorting_rules' => '排序规则',
'sorting_rules_desc' => '这些是预定义的排序操作,可应用于系统中的内容。',
@@ -103,8 +103,8 @@ return [
'sort_rule_op_updated_date' => '更新时间',
'sort_rule_op_chapters_first' => '章节正序',
'sort_rule_op_chapters_last' => '章节倒序',
- 'sorting_page_limits' => 'Per-Page Display Limits',
- 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
+ 'sorting_page_limits' => '每页显示限制',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => '维护',
@@ -197,13 +197,13 @@ return [
'role_import_content' => '导入内容',
'role_editor_change' => '更改页面编辑器',
'role_notifications' => '管理和接收通知',
- 'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.',
+ 'role_permission_note_users_and_roles' => '从技术上讲,这些权限还将提供对系统中用户和角色的可见性和搜索功能。',
'role_asset' => '资源许可',
'roles_system_warning' => '请注意,拥有以上三个权限中的任何一个都会允许用户更改自己的权限或系统中其他人的权限。 请只将拥有这些权限的角色分配给你信任的用户。',
'role_asset_desc' => '对系统内资源的默认访问许可将由这些权限控制。单独设置在书籍、章节和页面上的权限将覆盖这里的权限设定。',
'role_asset_admins' => '管理员可自动获得对所有内容的访问权限,但这些选项可能会显示或隐藏UI选项。',
'role_asset_image_view_note' => '这与图像管理器中的可见性有关。已经上传的图片的实际访问取决于系统图像存储选项。',
- 'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.',
+ 'role_asset_users_note' => '从技术上讲,这些权限还将提供对系统中用户和角色的可见性和搜索功能。',
'role_all' => '全部的',
'role_own' => '拥有的',
'role_controlled_by_asset' => '由其所在的资源来控制',
diff --git a/lang/zh_CN/validation.php b/lang/zh_CN/validation.php
index 748c8f567..955381ccb 100644
--- a/lang/zh_CN/validation.php
+++ b/lang/zh_CN/validation.php
@@ -106,7 +106,7 @@ return [
'uploaded' => '无法上传文件。 服务器可能不接受此大小的文件。',
'zip_file' => ':attribute 需要引用 ZIP 内的文件。',
- 'zip_file_size' => 'The file :attribute must not exceed :size MB.',
+ 'zip_file_size' => ':attribute 不能超过 :size MB 。',
'zip_file_mime' => ':attribute 需要引用类型为 :validTypes 的文件,找到 :foundType 。',
'zip_model_expected' => '预期的数据对象,但找到了 ":type" 。',
'zip_unique' => '对于 ZIP 中的对象类型来说,:attribute 必须是唯一的。',
diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php
index 0d5d760dd..65778f77c 100644
--- a/lang/zh_TW/settings.php
+++ b/lang/zh_TW/settings.php
@@ -104,7 +104,7 @@ return [
'sort_rule_op_chapters_first' => '第一章',
'sort_rule_op_chapters_last' => '最後一章',
'sorting_page_limits' => '每頁顯示限制',
- 'sorting_page_limits_desc' => '設定系統內各類清單每頁顯示的項目數量。通常較低的數量能提升效能表現,而較高的數量則可避免使用者需點擊翻閱多頁。建議採用 3 的整數倍數(如 18、24、30 等)。',
+ 'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using a multiple of 6 is recommended.',
// Maintenance settings
'maint' => '維護',
diff --git a/package-lock.json b/package-lock.json
index e8a1493d4..b6508f1e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,62 +5,48 @@
"packages": {
"": {
"dependencies": {
- "@codemirror/commands": "^6.10.0",
+ "@codemirror/commands": "^6.10.2",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.11",
- "@codemirror/lang-javascript": "^6.2.4",
+ "@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0",
- "@codemirror/language": "^6.11.3",
+ "@codemirror/language": "^6.12.2",
"@codemirror/legacy-modes": "^6.5.2",
- "@codemirror/state": "^6.5.2",
+ "@codemirror/state": "^6.5.4",
"@codemirror/theme-one-dark": "^6.1.3",
- "@codemirror/view": "^6.38.8",
+ "@codemirror/view": "^6.39.16",
"@lezer/highlight": "^1.2.3",
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
"@types/jest": "^30.0.0",
"codemirror": "^6.0.2",
"idb-keyval": "^6.2.2",
- "markdown-it": "^14.1.0",
+ "markdown-it": "^14.1.1",
"markdown-it-task-lists": "^2.1.1",
"snabbdom": "^3.6.3",
- "sortablejs": "^1.15.6"
+ "sortablejs": "^1.15.7"
},
"devDependencies": {
- "@eslint/js": "^9.39.1",
+ "@eslint/js": "^10.0.1",
"@lezer/generator": "^1.8.0",
"@types/markdown-it": "^14.1.2",
"@types/sortablejs": "^1.15.9",
"chokidar-cli": "^3.0",
- "esbuild": "^0.27.0",
- "eslint": "^9.39.1",
- "eslint-plugin-import": "^2.32.0",
+ "esbuild": "^0.27.3",
+ "eslint": "^10.0.2",
+ "globals": "^17.4.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"npm-run-all": "^4.1.5",
- "sass": "^1.94.2",
- "ts-jest": "^29.4.5",
+ "sass": "^1.97.3",
+ "ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "5.9.*"
}
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
@@ -83,12 +69,12 @@
"license": "ISC"
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -97,9 +83,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
- "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -107,22 +93,22 @@
}
},
"node_modules/@babel/core": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
- "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@ampproject/remapping": "^2.2.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/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.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@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.1.0",
"gensync": "^1.0.0-beta.2",
@@ -138,14 +124,14 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
- "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.0",
- "@babel/types": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -155,13 +141,13 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.27.2",
+ "@babel/compat-data": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
@@ -182,29 +168,29 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
- "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.3"
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -214,9 +200,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
"engines": {
@@ -234,9 +220,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -253,27 +239,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.27.6",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
- "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.27.6"
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
- "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.0"
+ "@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -338,13 +324,13 @@
}
},
"node_modules/@babel/plugin-syntax-import-attributes": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
- "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -380,13 +366,13 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
- "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -506,13 +492,13 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
- "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -522,33 +508,33 @@
}
},
"node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
- "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.0",
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.0",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
@@ -556,14 +542,14 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
- "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -577,9 +563,9 @@
"license": "MIT"
},
"node_modules/@codemirror/autocomplete": {
- "version": "6.18.6",
- "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
- "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
+ "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -589,9 +575,9 @@
}
},
"node_modules/@codemirror/commands": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
- "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz",
+ "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -631,9 +617,9 @@
}
},
"node_modules/@codemirror/lang-javascript": {
- "version": "6.2.4",
- "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
- "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
+ "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@@ -698,14 +684,14 @@
}
},
"node_modules/@codemirror/language": {
- "version": "6.11.3",
- "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
- "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
+ "version": "6.12.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
+ "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
- "@lezer/common": "^1.1.0",
+ "@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
@@ -721,9 +707,9 @@
}
},
"node_modules/@codemirror/lint": {
- "version": "6.8.5",
- "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
- "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
+ "version": "6.9.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz",
+ "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@@ -732,20 +718,20 @@
}
},
"node_modules/@codemirror/search": {
- "version": "6.5.11",
- "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
- "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz",
+ "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
- "@codemirror/view": "^6.0.0",
+ "@codemirror/view": "^6.37.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
- "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
+ "version": "6.5.4",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz",
+ "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
@@ -764,9 +750,9 @@
}
},
"node_modules/@codemirror/view": {
- "version": "6.38.8",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
- "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
+ "version": "6.39.16",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz",
+ "integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
@@ -915,9 +901,9 @@
}
},
"node_modules/@emnapi/core": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
- "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -927,9 +913,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
- "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -949,9 +935,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz",
- "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
"cpu": [
"ppc64"
],
@@ -966,9 +952,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz",
- "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
"cpu": [
"arm"
],
@@ -983,9 +969,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz",
- "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
"cpu": [
"arm64"
],
@@ -1000,9 +986,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz",
- "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
"cpu": [
"x64"
],
@@ -1017,9 +1003,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz",
- "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
"cpu": [
"arm64"
],
@@ -1034,9 +1020,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz",
- "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
"cpu": [
"x64"
],
@@ -1051,9 +1037,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz",
- "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
"cpu": [
"arm64"
],
@@ -1068,9 +1054,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz",
- "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
"cpu": [
"x64"
],
@@ -1085,9 +1071,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz",
- "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
"cpu": [
"arm"
],
@@ -1102,9 +1088,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz",
- "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
"cpu": [
"arm64"
],
@@ -1119,9 +1105,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz",
- "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
"cpu": [
"ia32"
],
@@ -1136,9 +1122,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz",
- "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
"cpu": [
"loong64"
],
@@ -1153,9 +1139,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz",
- "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
"cpu": [
"mips64el"
],
@@ -1170,9 +1156,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz",
- "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
"cpu": [
"ppc64"
],
@@ -1187,9 +1173,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz",
- "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
"cpu": [
"riscv64"
],
@@ -1204,9 +1190,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz",
- "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
"cpu": [
"s390x"
],
@@ -1221,9 +1207,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz",
- "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
"cpu": [
"x64"
],
@@ -1238,9 +1224,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz",
- "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
"cpu": [
"arm64"
],
@@ -1255,9 +1241,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz",
- "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
"cpu": [
"x64"
],
@@ -1272,9 +1258,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz",
- "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
"cpu": [
"arm64"
],
@@ -1289,9 +1275,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz",
- "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
"cpu": [
"x64"
],
@@ -1306,9 +1292,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz",
- "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
"cpu": [
"arm64"
],
@@ -1323,9 +1309,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz",
- "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
"cpu": [
"x64"
],
@@ -1340,9 +1326,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz",
- "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
"cpu": [
"arm64"
],
@@ -1357,9 +1343,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz",
- "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
"cpu": [
"ia32"
],
@@ -1374,9 +1360,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz",
- "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
"cpu": [
"x64"
],
@@ -1391,9 +1377,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1423,9 +1409,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1433,105 +1419,89 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz",
+ "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.7",
+ "@eslint/object-schema": "^3.0.2",
"debug": "^4.3.1",
- "minimatch": "^3.1.2"
+ "minimatch": "^10.2.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz",
+ "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.17.0"
+ "@eslint/core": "^1.1.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
+ "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/js": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
- "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
+ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "eslint": "^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz",
+ "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz",
+ "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.17.0",
+ "@eslint/core": "^1.1.0",
"levn": "^0.4.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@humanfs/core": {
@@ -1545,33 +1515,19 @@
}
},
"node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
+ "@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -1618,62 +1574,6 @@
"node": ">=12"
}
},
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1691,16 +1591,6 @@
"node": ">=8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
"node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -1715,20 +1605,6 @@
"node": ">=8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.2",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
- "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -1771,16 +1647,6 @@
"node": ">=8"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -2156,9 +2022,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
- "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2166,6 +2032,17 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -2177,16 +2054,16 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
- "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.29",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
- "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2195,15 +2072,15 @@
}
},
"node_modules/@lezer/common": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz",
- "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz",
+ "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==",
"license": "MIT"
},
"node_modules/@lezer/css": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
- "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.1.tgz",
+ "integrity": "sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -2235,9 +2112,9 @@
}
},
"node_modules/@lezer/html": {
- "version": "1.3.12",
- "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
- "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
+ "version": "1.3.13",
+ "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
+ "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -2246,9 +2123,9 @@
}
},
"node_modules/@lezer/javascript": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
- "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
+ "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -2268,28 +2145,28 @@
}
},
"node_modules/@lezer/lr": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
- "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz",
+ "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/markdown": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.4.3.tgz",
- "integrity": "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz",
+ "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==",
"license": "MIT",
"dependencies": {
- "@lezer/common": "^1.0.0",
+ "@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0"
}
},
"node_modules/@lezer/php": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.4.tgz",
- "integrity": "sha512-D2dJ0t8Z28/G1guztRczMFvPDUqzeMLSQbdWQmaiHV7urc8NlEOnjYk9UrZ531OcLiRxD4Ihcbv7AsDpNKDRaQ==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz",
+ "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -2328,18 +2205,18 @@
}
},
"node_modules/@parcel/watcher": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
- "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
+ "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "detect-libc": "^1.0.3",
+ "detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
- "micromatch": "^4.0.5",
- "node-addon-api": "^7.0.0"
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
@@ -2349,25 +2226,25 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "@parcel/watcher-android-arm64": "2.5.1",
- "@parcel/watcher-darwin-arm64": "2.5.1",
- "@parcel/watcher-darwin-x64": "2.5.1",
- "@parcel/watcher-freebsd-x64": "2.5.1",
- "@parcel/watcher-linux-arm-glibc": "2.5.1",
- "@parcel/watcher-linux-arm-musl": "2.5.1",
- "@parcel/watcher-linux-arm64-glibc": "2.5.1",
- "@parcel/watcher-linux-arm64-musl": "2.5.1",
- "@parcel/watcher-linux-x64-glibc": "2.5.1",
- "@parcel/watcher-linux-x64-musl": "2.5.1",
- "@parcel/watcher-win32-arm64": "2.5.1",
- "@parcel/watcher-win32-ia32": "2.5.1",
- "@parcel/watcher-win32-x64": "2.5.1"
+ "@parcel/watcher-android-arm64": "2.5.6",
+ "@parcel/watcher-darwin-arm64": "2.5.6",
+ "@parcel/watcher-darwin-x64": "2.5.6",
+ "@parcel/watcher-freebsd-x64": "2.5.6",
+ "@parcel/watcher-linux-arm-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm-musl": "2.5.6",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm64-musl": "2.5.6",
+ "@parcel/watcher-linux-x64-glibc": "2.5.6",
+ "@parcel/watcher-linux-x64-musl": "2.5.6",
+ "@parcel/watcher-win32-arm64": "2.5.6",
+ "@parcel/watcher-win32-ia32": "2.5.6",
+ "@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
- "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+ "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
@@ -2386,9 +2263,9 @@
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
- "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+ "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
@@ -2407,9 +2284,9 @@
}
},
"node_modules/@parcel/watcher-darwin-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
- "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+ "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
@@ -2428,9 +2305,9 @@
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
- "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+ "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
@@ -2449,9 +2326,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
- "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+ "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
@@ -2470,9 +2347,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
- "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+ "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
@@ -2491,9 +2368,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
- "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+ "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
@@ -2512,9 +2389,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
- "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+ "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
@@ -2533,9 +2410,9 @@
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
- "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+ "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
@@ -2554,9 +2431,9 @@
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
- "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+ "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
@@ -2575,9 +2452,9 @@
}
},
"node_modules/@parcel/watcher-win32-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
- "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+ "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
@@ -2596,9 +2473,9 @@
}
},
"node_modules/@parcel/watcher-win32-ia32": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
- "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+ "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
@@ -2617,9 +2494,9 @@
}
},
"node_modules/@parcel/watcher-win32-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
- "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+ "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
@@ -2637,6 +2514,20 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2661,17 +2552,10 @@
"url": "https://opencollective.com/pkgr"
}
},
- "node_modules/@rtsao/scc": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
- "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@sinclair/typebox": {
- "version": "0.34.41",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
- "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==",
+ "version": "0.34.48",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
+ "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==",
"license": "MIT"
},
"node_modules/@sinonjs/commons": {
@@ -2712,9 +2596,9 @@
}
},
"node_modules/@tsconfig/node10": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
- "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
"dev": true,
"license": "MIT"
},
@@ -2795,6 +2679,13 @@
"@babel/types": "^7.28.2"
}
},
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -2855,13 +2746,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -2888,12 +2772,12 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.1.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
- "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
+ "version": "25.3.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
+ "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
"license": "MIT",
"dependencies": {
- "undici-types": "~7.8.0"
+ "undici-types": "~7.18.0"
}
},
"node_modules/@types/sortablejs": {
@@ -2917,9 +2801,9 @@
"license": "MIT"
},
"node_modules/@types/yargs": {
- "version": "17.0.33",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
- "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
"license": "MIT",
"dependencies": {
"@types/yargs-parser": "*"
@@ -3208,9 +3092,9 @@
]
},
"node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3231,9 +3115,9 @@
}
},
"node_modules/acorn-walk": {
- "version": "8.3.4",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "version": "8.3.5",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
+ "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3254,9 +3138,9 @@
}
},
"node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3336,10 +3220,14 @@
"license": "MIT"
},
"node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "license": "Python-2.0"
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
},
"node_modules/array-buffer-byte-length": {
"version": "1.0.2",
@@ -3358,89 +3246,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/array-includes": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
- "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.24.0",
- "es-object-atoms": "^1.1.1",
- "get-intrinsic": "^1.3.0",
- "is-string": "^1.1.1",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.findlastindex": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
- "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "es-shim-unscopables": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flat": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
- "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flatmap": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
- "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/arraybuffer.prototype.slice": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
@@ -3589,11 +3394,27 @@
}
},
"node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
},
"node_modules/binary-extensions": {
"version": "2.3.0",
@@ -3609,14 +3430,16 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
"node_modules/braces": {
@@ -3632,9 +3455,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.25.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
- "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true,
"funding": [
{
@@ -3652,10 +3475,11 @@
],
"license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001726",
- "electron-to-chromium": "^1.5.173",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.3"
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
},
"bin": {
"browserslist": "cli.js"
@@ -3765,9 +3589,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001727",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
- "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
+ "version": "1.0.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
"dev": true,
"funding": [
{
@@ -3856,9 +3680,9 @@
}
},
"node_modules/ci-info": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz",
- "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
"funding": [
{
"type": "github",
@@ -3871,9 +3695,9 @@
}
},
"node_modules/cjs-module-lexer": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz",
- "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
"dev": true,
"license": "MIT"
},
@@ -3899,6 +3723,68 @@
"node": ">=6"
}
},
+ "node_modules/cliui/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cliui/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/cliui/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/cliui/node_modules/strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@@ -3912,6 +3798,21 @@
"node": ">=6"
}
},
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -4088,9 +3989,9 @@
}
},
"node_modules/debug": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
- "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4123,9 +4024,9 @@
"license": "MIT"
},
"node_modules/dedent": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz",
- "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==",
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
+ "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4191,17 +4092,14 @@
}
},
"node_modules/detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
- "bin": {
- "detect-libc": "bin/detect-libc.js"
- },
"engines": {
- "node": ">=0.10"
+ "node": ">=8"
}
},
"node_modules/detect-newline": {
@@ -4215,28 +4113,15 @@
}
},
"node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
- "node_modules/doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -4260,9 +4145,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.190",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
- "integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
+ "version": "1.5.307",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
+ "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
"dev": true,
"license": "ISC"
},
@@ -4280,9 +4165,9 @@
}
},
"node_modules/emoji-regex": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
- "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
@@ -4299,9 +4184,9 @@
}
},
"node_modules/error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4309,9 +4194,9 @@
}
},
"node_modules/es-abstract": {
- "version": "1.24.0",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
- "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
+ "version": "1.24.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
+ "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4426,19 +4311,6 @@
"node": ">= 0.4"
}
},
- "node_modules/es-shim-unscopables": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
- "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/es-to-primitive": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
@@ -4458,9 +4330,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz",
- "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -4471,32 +4343,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.0",
- "@esbuild/android-arm": "0.27.0",
- "@esbuild/android-arm64": "0.27.0",
- "@esbuild/android-x64": "0.27.0",
- "@esbuild/darwin-arm64": "0.27.0",
- "@esbuild/darwin-x64": "0.27.0",
- "@esbuild/freebsd-arm64": "0.27.0",
- "@esbuild/freebsd-x64": "0.27.0",
- "@esbuild/linux-arm": "0.27.0",
- "@esbuild/linux-arm64": "0.27.0",
- "@esbuild/linux-ia32": "0.27.0",
- "@esbuild/linux-loong64": "0.27.0",
- "@esbuild/linux-mips64el": "0.27.0",
- "@esbuild/linux-ppc64": "0.27.0",
- "@esbuild/linux-riscv64": "0.27.0",
- "@esbuild/linux-s390x": "0.27.0",
- "@esbuild/linux-x64": "0.27.0",
- "@esbuild/netbsd-arm64": "0.27.0",
- "@esbuild/netbsd-x64": "0.27.0",
- "@esbuild/openbsd-arm64": "0.27.0",
- "@esbuild/openbsd-x64": "0.27.0",
- "@esbuild/openharmony-arm64": "0.27.0",
- "@esbuild/sunos-x64": "0.27.0",
- "@esbuild/win32-arm64": "0.27.0",
- "@esbuild/win32-ia32": "0.27.0",
- "@esbuild/win32-x64": "0.27.0"
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
}
},
"node_modules/escalade": {
@@ -4523,33 +4395,30 @@
}
},
"node_modules/eslint": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz",
+ "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.1",
- "@eslint/plugin-kit": "^0.4.1",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.2",
+ "@eslint/config-helpers": "^0.5.2",
+ "@eslint/core": "^1.1.0",
+ "@eslint/plugin-kit": "^0.6.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
+ "ajv": "^6.14.0",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
+ "eslint-scope": "^9.1.1",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.1.1",
+ "esquery": "^1.7.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^8.0.0",
@@ -4559,8 +4428,7 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
+ "minimatch": "^10.2.1",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -4568,7 +4436,7 @@
"eslint": "bin/eslint.js"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
@@ -4582,125 +4450,33 @@
}
}
},
- "node_modules/eslint-import-resolver-node": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
- "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7",
- "is-core-module": "^2.13.0",
- "resolve": "^1.22.4"
- }
- },
- "node_modules/eslint-import-resolver-node/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-module-utils": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
- "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependenciesMeta": {
- "eslint": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import": {
- "version": "2.32.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
- "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rtsao/scc": "^1.1.0",
- "array-includes": "^3.1.9",
- "array.prototype.findlastindex": "^1.2.6",
- "array.prototype.flat": "^1.3.3",
- "array.prototype.flatmap": "^1.3.3",
- "debug": "^3.2.7",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.9",
- "eslint-module-utils": "^2.12.1",
- "hasown": "^2.0.2",
- "is-core-module": "^2.16.1",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "object.groupby": "^1.0.3",
- "object.values": "^1.2.1",
- "semver": "^6.3.1",
- "string.prototype.trimend": "^1.0.9",
- "tsconfig-paths": "^3.15.0"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
"node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz",
+ "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -4720,18 +4496,18 @@
}
},
"node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz",
+ "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.15.0",
+ "acorn": "^8.16.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
+ "eslint-visitor-keys": "^5.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -4752,9 +4528,9 @@
}
},
"node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -4943,9 +4719,9 @@
}
},
"node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz",
+ "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==",
"dev": true,
"license": "ISC"
},
@@ -5045,6 +4821,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/generator-function": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
+ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -5149,6 +4935,7 @@
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"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",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -5179,6 +4966,13 @@
"node": ">= 6"
}
},
+ "node_modules/glob/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
@@ -5190,13 +4984,13 @@
}
},
"node_modules/glob/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -5206,9 +5000,9 @@
}
},
"node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "version": "17.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
+ "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5464,29 +5258,12 @@
}
},
"node_modules/immutable": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
- "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
+ "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"dev": true,
"license": "MIT"
},
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/import-local": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
@@ -5733,13 +5510,13 @@
}
},
"node_modules/is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=4"
+ "node": ">=8"
}
},
"node_modules/is-generator-fn": {
@@ -5753,14 +5530,15 @@
}
},
"node_modules/is-generator-function": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
- "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
+ "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.0",
+ "call-bound": "^1.0.4",
+ "generator-function": "^2.0.0",
+ "get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
@@ -6043,9 +5821,9 @@
}
},
"node_modules/istanbul-lib-instrument/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -6254,16 +6032,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/jest-cli/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/jest-cli/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -6731,9 +6499,9 @@
}
},
"node_modules/jest-snapshot/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -6863,13 +6631,14 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "argparse": "^2.0.1"
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
@@ -7096,13 +6865,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
@@ -7137,9 +6899,9 @@
}
},
"node_modules/make-dir/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -7167,9 +6929,9 @@
}
},
"node_modules/markdown-it": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
- "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
+ "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
@@ -7189,6 +6951,12 @@
"integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==",
"license": "ISC"
},
+ "node_modules/markdown-it/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -7245,16 +7013,19 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "brace-expansion": "^5.0.2"
},
"engines": {
- "node": "*"
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
@@ -7268,11 +7039,11 @@
}
},
"node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -7337,9 +7108,9 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"dev": true,
"license": "MIT"
},
@@ -7415,6 +7186,24 @@
"node": ">=4"
}
},
+ "node_modules/npm-run-all/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/npm-run-all/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
"node_modules/npm-run-all/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -7484,6 +7273,19 @@
"node": ">=4"
}
},
+ "node_modules/npm-run-all/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/npm-run-all/node_modules/path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
@@ -7567,9 +7369,9 @@
}
},
"node_modules/nwsapi": {
- "version": "2.2.22",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.22.tgz",
- "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==",
+ "version": "2.2.23",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
+ "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
"dev": true,
"license": "MIT"
},
@@ -7617,59 +7419,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/object.fromentries": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
- "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.groupby": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
- "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.values": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
- "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -7781,19 +7530,6 @@
"dev": true,
"license": "BlueOak-1.0.0"
},
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -8211,13 +7947,13 @@
"license": "ISC"
},
"node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-core-module": "^2.16.0",
+ "is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@@ -8244,7 +7980,7 @@
"node": ">=8"
}
},
- "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
@@ -8254,16 +7990,6 @@
"node": ">=8"
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
@@ -8334,9 +8060,9 @@
"license": "MIT"
},
"node_modules/sass": {
- "version": "1.94.2",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz",
- "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
+ "version": "1.97.3",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz",
+ "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8607,9 +8333,9 @@
}
},
"node_modules/sortablejs": {
- "version": "1.15.6",
- "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
- "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==",
+ "version": "1.15.7",
+ "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz",
+ "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==",
"license": "MIT"
},
"node_modules/source-map": {
@@ -8673,9 +8399,9 @@
}
},
"node_modules/spdx-license-ids": {
- "version": "3.0.21",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
- "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
+ "version": "3.0.23",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz",
+ "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==",
"dev": true,
"license": "CC0-1.0"
},
@@ -8759,18 +8485,21 @@
}
},
"node_modules/string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=6"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
@@ -8806,16 +8535,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -8829,29 +8548,6 @@
"node": ">=8"
}
},
- "node_modules/string-width/node_modules/ansi-regex": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
- "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/string-width/node_modules/strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^4.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/string.prototype.padend": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz",
@@ -8931,13 +8627,13 @@
}
},
"node_modules/strip-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
- "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.0.1"
+ "ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
@@ -9004,9 +8700,9 @@
}
},
"node_modules/style-mod": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
- "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
+ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"license": "MIT"
},
"node_modules/supports-color": {
@@ -9042,9 +8738,9 @@
"license": "MIT"
},
"node_modules/synckit": {
- "version": "0.11.11",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
- "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9072,11 +8768,29 @@
"node": ">=8"
}
},
+ "node_modules/test-exclude/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/test-exclude/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
"node_modules/test-exclude/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"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",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -9094,6 +8808,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/test-exclude/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
@@ -9160,9 +8887,9 @@
}
},
"node_modules/ts-jest": {
- "version": "29.4.5",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz",
- "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==",
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9213,9 +8940,9 @@
}
},
"node_modules/ts-jest/node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -9282,42 +9009,6 @@
}
}
},
- "node_modules/tsconfig-paths": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
- "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.2",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- }
- },
- "node_modules/tsconfig-paths/node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.0"
- },
- "bin": {
- "json5": "lib/cli.js"
- }
- },
- "node_modules/tsconfig-paths/node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -9441,9 +9132,9 @@
}
},
"node_modules/typescript": {
- "version": "5.9.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
- "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -9494,9 +9185,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
- "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/unrs-resolver": {
@@ -9535,9 +9226,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"dev": true,
"funding": [
{
@@ -9651,6 +9342,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9775,9 +9467,9 @@
"license": "ISC"
},
"node_modules/which-typed-array": {
- "version": "1.1.19",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
- "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9814,18 +9506,21 @@
"license": "MIT"
},
"node_modules/wrap-ansi": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
- "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^3.2.0",
- "string-width": "^3.0.0",
- "strip-ansi": "^5.0.0"
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=6"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
@@ -9864,16 +9559,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -9902,57 +9587,17 @@
"node": ">=8"
}
},
- "node_modules/wrap-ansi/node_modules/ansi-regex": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
- "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
"engines": {
- "node": ">=4"
- }
- },
- "node_modules/wrap-ansi/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/wrap-ansi/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi/node_modules/strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^4.1.0"
+ "node": ">=12"
},
- "engines": {
- "node": ">=6"
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrappy": {
@@ -9977,9 +9622,9 @@
}
},
"node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10058,6 +9703,23 @@
"node": ">=12"
}
},
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/yargs/node_modules/find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
@@ -10071,6 +9733,16 @@
"node": ">=6"
}
},
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/yargs/node_modules/locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
@@ -10124,6 +9796,34 @@
"node": ">=4"
}
},
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/yargs/node_modules/yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
diff --git a/package.json b/package.json
index 624ff876a..45c275863 100644
--- a/package.json
+++ b/package.json
@@ -18,45 +18,45 @@
"test": "jest"
},
"devDependencies": {
- "@eslint/js": "^9.39.1",
+ "@eslint/js": "^10.0.1",
"@lezer/generator": "^1.8.0",
"@types/markdown-it": "^14.1.2",
"@types/sortablejs": "^1.15.9",
"chokidar-cli": "^3.0",
- "esbuild": "^0.27.0",
- "eslint": "^9.39.1",
- "eslint-plugin-import": "^2.32.0",
+ "esbuild": "^0.27.3",
+ "eslint": "^10.0.2",
+ "globals": "^17.4.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"npm-run-all": "^4.1.5",
- "sass": "^1.94.2",
- "ts-jest": "^29.4.5",
+ "sass": "^1.97.3",
+ "ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"typescript": "5.9.*"
},
"dependencies": {
- "@codemirror/commands": "^6.10.0",
+ "@codemirror/commands": "^6.10.2",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.11",
- "@codemirror/lang-javascript": "^6.2.4",
+ "@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0",
- "@codemirror/language": "^6.11.3",
+ "@codemirror/language": "^6.12.2",
"@codemirror/legacy-modes": "^6.5.2",
- "@codemirror/state": "^6.5.2",
+ "@codemirror/state": "^6.5.4",
"@codemirror/theme-one-dark": "^6.1.3",
- "@codemirror/view": "^6.38.8",
+ "@codemirror/view": "^6.39.16",
"@lezer/highlight": "^1.2.3",
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
"@types/jest": "^30.0.0",
"codemirror": "^6.0.2",
"idb-keyval": "^6.2.2",
- "markdown-it": "^14.1.0",
+ "markdown-it": "^14.1.1",
"markdown-it-task-lists": "^2.1.1",
"snabbdom": "^3.6.3",
- "sortablejs": "^1.15.6"
+ "sortablejs": "^1.15.7"
}
}
diff --git a/readme.md b/readme.md
index 4054ab606..194f81e4a 100644
--- a/readme.md
+++ b/readme.md
@@ -48,17 +48,13 @@ Big thanks to these companies for supporting the project.
#### Gold Sponsor
@@ -81,26 +77,23 @@ Big thanks to these companies for supporting the project.
-
-
-
-
-
+
+
-
-
-
+
+
+
diff --git a/resources/js/wysiwyg-tinymce/plugin-drawio.js b/resources/js/wysiwyg-tinymce/plugin-drawio.js
index 197c50b0e..ad0e01800 100644
--- a/resources/js/wysiwyg-tinymce/plugin-drawio.js
+++ b/resources/js/wysiwyg-tinymce/plugin-drawio.js
@@ -57,7 +57,7 @@ async function updateContent(pngData) {
});
} catch (err) {
handleUploadError(err);
- throw new Error(`Failed to save image with error: ${err}`);
+ throw new Error(`Failed to save image with error: ${err}`, {cause: err});
}
return;
}
@@ -78,7 +78,7 @@ async function updateContent(pngData) {
} catch (err) {
pageEditor.dom.remove(wrapId);
handleUploadError(err);
- throw new Error(`Failed to save image with error: ${err}`);
+ throw new Error(`Failed to save image with error: ${err}`, {cause: err});
}
}
diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss
index cd4bb5d2e..0580d7377 100644
--- a/resources/sass/_editor.scss
+++ b/resources/sass/_editor.scss
@@ -451,6 +451,9 @@ body.editor-is-fullscreen {
outline: 1px dashed var(--editor-color-primary);
outline-offset: 1px;
}
+.editor-content-area [drawio-diagram] {
+ cursor: pointer;
+}
.editor-table-marker {
position: fixed;
diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss
index 13a4232fc..f1a2feabf 100644
--- a/resources/sass/_forms.scss
+++ b/resources/sass/_forms.scss
@@ -142,6 +142,9 @@
padding-inline-end: 12px;
max-width: 864px;
}
+ [drawio-diagram] {
+ cursor: pointer;
+ }
[drawio-diagram]:hover {
outline: 2px solid var(--color-primary);
}
diff --git a/resources/sass/_mixins.scss b/resources/sass/_mixins.scss
index fc5086008..e9f2db69c 100644
--- a/resources/sass/_mixins.scss
+++ b/resources/sass/_mixins.scss
@@ -35,9 +35,17 @@
// Define a property for both light and dark mode
@mixin lightDark($prop, $light, $dark, $important: false) {
- #{$prop}: if($important, $light !important, $light);
+ @if($important) {
+ #{$prop}: $light !important;
+ } @else {
+ #{$prop}: $light;
+ }
html.dark-mode & {
- #{$prop}: if($important, $dark !important, $dark);
+ @if($important) {
+ #{$prop}: $dark !important;
+ } @else {
+ #{$prop}: $dark;
+ }
}
}
diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss
index 8cc96df41..d662550c6 100644
--- a/resources/sass/_tinymce.scss
+++ b/resources/sass/_tinymce.scss
@@ -202,4 +202,11 @@ body.page-content.mce-content-body {
background-image: url('data:image/svg+xml;utf8, ');
background-position: 50% 50%;
background-size: 100% 100%;
-}
\ No newline at end of file
+}
+
+/**
+ * Ensure cursor indicates that drawings are clickable
+ */
+.page-content.mce-content-body [drawio-diagram] {
+ cursor: pointer;
+}
diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php
index 52d23241a..660c008df 100644
--- a/resources/views/books/index.blade.php
+++ b/resources/views/books/index.blade.php
@@ -5,58 +5,11 @@
@stop
@section('left')
- @if($recents)
-
-
{{ trans('entities.recently_viewed') }}
- @include('entities.list', ['entities' => $recents, 'style' => 'compact'])
-
- @endif
-
-
-
{{ trans('entities.books_popular') }}
- @if(count($popular) > 0)
- @include('entities.list', ['entities' => $popular, 'style' => 'compact'])
- @else
-
{{ trans('entities.books_popular_empty') }}
- @endif
-
-
-
-
{{ trans('entities.books_new') }}
- @if(count($new) > 0)
- @include('entities.list', ['entities' => $new, 'style' => 'compact'])
- @else
-
{{ trans('entities.books_new_empty') }}
- @endif
-
+ @include('books.parts.index-sidebar-section-recents', ['recents' => $recents])
+ @include('books.parts.index-sidebar-section-popular', ['popular' => $popular])
+ @include('books.parts.index-sidebar-section-new', ['new' => $new])
@stop
@section('right')
-
-
-
+ @include('books.parts.index-sidebar-section-actions', ['view' => $view])
@stop
diff --git a/resources/views/books/parts/index-sidebar-section-actions.blade.php b/resources/views/books/parts/index-sidebar-section-actions.blade.php
new file mode 100644
index 000000000..8f8b254c8
--- /dev/null
+++ b/resources/views/books/parts/index-sidebar-section-actions.blade.php
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/resources/views/books/parts/index-sidebar-section-new.blade.php b/resources/views/books/parts/index-sidebar-section-new.blade.php
new file mode 100644
index 000000000..a9aa52c59
--- /dev/null
+++ b/resources/views/books/parts/index-sidebar-section-new.blade.php
@@ -0,0 +1,8 @@
+
+
{{ trans('entities.books_new') }}
+ @if(count($new) > 0)
+ @include('entities.list', ['entities' => $new, 'style' => 'compact'])
+ @else
+
{{ trans('entities.books_new_empty') }}
+ @endif
+
\ No newline at end of file
diff --git a/resources/views/books/parts/index-sidebar-section-popular.blade.php b/resources/views/books/parts/index-sidebar-section-popular.blade.php
new file mode 100644
index 000000000..030c75eb9
--- /dev/null
+++ b/resources/views/books/parts/index-sidebar-section-popular.blade.php
@@ -0,0 +1,8 @@
+
+
{{ trans('entities.books_popular') }}
+ @if(count($popular) > 0)
+ @include('entities.list', ['entities' => $popular, 'style' => 'compact'])
+ @else
+
{{ trans('entities.books_popular_empty') }}
+ @endif
+
\ No newline at end of file
diff --git a/resources/views/books/parts/index-sidebar-section-recents.blade.php b/resources/views/books/parts/index-sidebar-section-recents.blade.php
new file mode 100644
index 000000000..f1a68ba4f
--- /dev/null
+++ b/resources/views/books/parts/index-sidebar-section-recents.blade.php
@@ -0,0 +1,6 @@
+@if($recents)
+
+
{{ trans('entities.recently_viewed') }}
+ @include('entities.list', ['entities' => $recents, 'style' => 'compact'])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/books/parts/show-sidebar-section-actions.blade.php b/resources/views/books/parts/show-sidebar-section-actions.blade.php
new file mode 100644
index 000000000..8e5b5c4d7
--- /dev/null
+++ b/resources/views/books/parts/show-sidebar-section-actions.blade.php
@@ -0,0 +1,61 @@
+
\ No newline at end of file
diff --git a/resources/views/books/parts/show-sidebar-section-activity.blade.php b/resources/views/books/parts/show-sidebar-section-activity.blade.php
new file mode 100644
index 000000000..c1c5c1d3e
--- /dev/null
+++ b/resources/views/books/parts/show-sidebar-section-activity.blade.php
@@ -0,0 +1,6 @@
+@if(count($activity) > 0)
+
+
{{ trans('entities.recent_activity') }}
+ @include('common.activity-list', ['activity' => $activity])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/books/parts/show-sidebar-section-details.blade.php b/resources/views/books/parts/show-sidebar-section-details.blade.php
new file mode 100644
index 000000000..709d0ffd9
--- /dev/null
+++ b/resources/views/books/parts/show-sidebar-section-details.blade.php
@@ -0,0 +1,21 @@
+
+
{{ trans('common.details') }}
+
+ @include('entities.meta', ['entity' => $book, 'watchOptions' => $watchOptions])
+ @if($book->hasPermissions())
+
+ @endif
+
+
\ No newline at end of file
diff --git a/resources/views/books/parts/show-sidebar-section-shelves.blade.php b/resources/views/books/parts/show-sidebar-section-shelves.blade.php
new file mode 100644
index 000000000..9de9b95c6
--- /dev/null
+++ b/resources/views/books/parts/show-sidebar-section-shelves.blade.php
@@ -0,0 +1,6 @@
+@if(count($bookParentShelves) > 0)
+
+
{{ trans('entities.shelves') }}
+ @include('entities.list', ['entities' => $bookParentShelves, 'style' => 'compact'])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/books/parts/show-sidebar-section-tags.blade.php b/resources/views/books/parts/show-sidebar-section-tags.blade.php
new file mode 100644
index 000000000..440a780c8
--- /dev/null
+++ b/resources/views/books/parts/show-sidebar-section-tags.blade.php
@@ -0,0 +1,5 @@
+@if($book->tags->count() > 0)
+
+ @include('entities.tag-list', ['entity' => $book])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php
index d510c8fd5..4eb83164f 100644
--- a/resources/views/books/show.blade.php
+++ b/resources/views/books/show.blade.php
@@ -67,114 +67,14 @@
@stop
@section('right')
-
-
{{ trans('common.details') }}
-
- @include('entities.meta', ['entity' => $book, 'watchOptions' => $watchOptions])
- @if($book->hasPermissions())
-
- @endif
-
-
-
-
-
+ @include('books.parts.show-sidebar-section-details', ['book' => $book, 'watchOptions' => $watchOptions])
+ @include('books.parts.show-sidebar-section-actions', ['book' => $book, 'watchOptions' => $watchOptions])
@stop
@section('left')
-
@include('entities.search-form', ['label' => trans('entities.books_search_this')])
-
- @if($book->tags->count() > 0)
-
- @include('entities.tag-list', ['entity' => $book])
-
- @endif
-
- @if(count($bookParentShelves) > 0)
-
-
{{ trans('entities.shelves') }}
- @include('entities.list', ['entities' => $bookParentShelves, 'style' => 'compact'])
-
- @endif
-
- @if(count($activity) > 0)
-
-
{{ trans('entities.recent_activity') }}
- @include('common.activity-list', ['activity' => $activity])
-
- @endif
+ @include('books.parts.show-sidebar-section-tags', ['book' => $book])
+ @include('books.parts.show-sidebar-section-shelves', ['bookParentShelves' => $bookParentShelves])
+ @include('books.parts.show-sidebar-section-activity', ['activity' => $activity])
@stop
diff --git a/resources/views/chapters/parts/show-sidebar-section-actions.blade.php b/resources/views/chapters/parts/show-sidebar-section-actions.blade.php
new file mode 100644
index 000000000..55df999a2
--- /dev/null
+++ b/resources/views/chapters/parts/show-sidebar-section-actions.blade.php
@@ -0,0 +1,65 @@
+
+
{{ trans('common.actions') }}
+
+
+ @if(userCan(\BookStack\Permissions\Permission::PageCreate, $chapter))
+
+ @icon('add')
+ {{ trans('entities.pages_new') }}
+
+ @endif
+
+
+
+ @if(userCan(\BookStack\Permissions\Permission::ChapterUpdate, $chapter))
+
+ @icon('edit')
+ {{ trans('common.edit') }}
+
+ @endif
+ @if(userCanOnAny(\BookStack\Permissions\Permission::Create, \BookStack\Entities\Models\Book::class) || userCan(\BookStack\Permissions\Permission::ChapterCreateAll) || userCan(\BookStack\Permissions\Permission::ChapterCreateOwn))
+
+ @icon('copy')
+ {{ trans('common.copy') }}
+
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::ChapterUpdate, $chapter) && userCan(\BookStack\Permissions\Permission::ChapterDelete, $chapter))
+
+ @icon('folder')
+ {{ trans('common.move') }}
+
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::RestrictionsManage, $chapter))
+
+ @icon('lock')
+ {{ trans('entities.permissions') }}
+
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::ChapterDelete, $chapter))
+
+ @icon('delete')
+ {{ trans('common.delete') }}
+
+ @endif
+
+ @if($chapter->book && userCan(\BookStack\Permissions\Permission::BookUpdate, $chapter->book))
+
+
+ @icon('sort')
+ {{ trans('entities.chapter_sort_book') }}
+
+ @endif
+
+
+
+ @if($watchOptions->canWatch() && !$watchOptions->isWatching())
+ @include('entities.watch-action', ['entity' => $chapter])
+ @endif
+ @if(!user()->isGuest())
+ @include('entities.favourite-action', ['entity' => $chapter])
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::ContentExport))
+ @include('entities.export-menu', ['entity' => $chapter])
+ @endif
+
+
\ No newline at end of file
diff --git a/resources/views/chapters/parts/show-sidebar-section-details.blade.php b/resources/views/chapters/parts/show-sidebar-section-details.blade.php
new file mode 100644
index 000000000..a424b8d3f
--- /dev/null
+++ b/resources/views/chapters/parts/show-sidebar-section-details.blade.php
@@ -0,0 +1,38 @@
+
+
{{ trans('common.details') }}
+
+ @include('entities.meta', ['entity' => $chapter, 'watchOptions' => $watchOptions])
+
+ @if($book->hasPermissions())
+
+ @endif
+
+ @if($chapter->hasPermissions())
+
+ @endif
+
+
\ No newline at end of file
diff --git a/resources/views/chapters/parts/show-sidebar-section-tags.blade.php b/resources/views/chapters/parts/show-sidebar-section-tags.blade.php
new file mode 100644
index 000000000..d28ff6383
--- /dev/null
+++ b/resources/views/chapters/parts/show-sidebar-section-tags.blade.php
@@ -0,0 +1,5 @@
+@if($chapter->tags->count() > 0)
+
+ @include('entities.tag-list', ['entity' => $chapter])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php
index 585bf8a3b..7ef877661 100644
--- a/resources/views/chapters/show.blade.php
+++ b/resources/views/chapters/show.blade.php
@@ -63,123 +63,13 @@
@stop
@section('right')
-
-
-
{{ trans('common.details') }}
-
- @include('entities.meta', ['entity' => $chapter, 'watchOptions' => $watchOptions])
-
- @if($book->hasPermissions())
-
- @endif
-
- @if($chapter->hasPermissions())
-
- @endif
-
-
-
-
-
{{ trans('common.actions') }}
-
-
- @if(userCan(\BookStack\Permissions\Permission::PageCreate, $chapter))
-
- @icon('add')
- {{ trans('entities.pages_new') }}
-
- @endif
-
-
-
- @if(userCan(\BookStack\Permissions\Permission::ChapterUpdate, $chapter))
-
- @icon('edit')
- {{ trans('common.edit') }}
-
- @endif
- @if(userCanOnAny(\BookStack\Permissions\Permission::Create, \BookStack\Entities\Models\Book::class) || userCan(\BookStack\Permissions\Permission::ChapterCreateAll) || userCan(\BookStack\Permissions\Permission::ChapterCreateOwn))
-
- @icon('copy')
- {{ trans('common.copy') }}
-
- @endif
- @if(userCan(\BookStack\Permissions\Permission::ChapterUpdate, $chapter) && userCan(\BookStack\Permissions\Permission::ChapterDelete, $chapter))
-
- @icon('folder')
- {{ trans('common.move') }}
-
- @endif
- @if(userCan(\BookStack\Permissions\Permission::RestrictionsManage, $chapter))
-
- @icon('lock')
- {{ trans('entities.permissions') }}
-
- @endif
- @if(userCan(\BookStack\Permissions\Permission::ChapterDelete, $chapter))
-
- @icon('delete')
- {{ trans('common.delete') }}
-
- @endif
-
- @if($chapter->book && userCan(\BookStack\Permissions\Permission::BookUpdate, $chapter->book))
-
-
- @icon('sort')
- {{ trans('entities.chapter_sort_book') }}
-
- @endif
-
-
-
- @if($watchOptions->canWatch() && !$watchOptions->isWatching())
- @include('entities.watch-action', ['entity' => $chapter])
- @endif
- @if(!user()->isGuest())
- @include('entities.favourite-action', ['entity' => $chapter])
- @endif
- @if(userCan(\BookStack\Permissions\Permission::ContentExport))
- @include('entities.export-menu', ['entity' => $chapter])
- @endif
-
-
+ @include('chapters.parts.show-sidebar-section-details', ['chapter' => $chapter, 'book' => $book, 'watchOptions' => $watchOptions])
+ @include('chapters.parts.show-sidebar-section-actions', ['chapter' => $chapter, 'watchOptions' => $watchOptions])
@stop
@section('left')
-
@include('entities.search-form', ['label' => trans('entities.chapters_search_this')])
-
- @if($chapter->tags->count() > 0)
-
- @include('entities.tag-list', ['entity' => $chapter])
-
- @endif
-
+ @include('chapters.parts.show-sidebar-section-tags', ['chapter' => $chapter])
@include('entities.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
@stop
diff --git a/resources/views/form/description-html-input.blade.php b/resources/views/form/description-html-input.blade.php
index 983d2fb83..4b0a74df1 100644
--- a/resources/views/form/description-html-input.blade.php
+++ b/resources/views/form/description-html-input.blade.php
@@ -1,7 +1,7 @@
+ @if($errors->has('description_html')) class="text-neg" @endif>@if(isset($model) || old('description_html')){{ old('description_html') ?? $model->descriptionInfo()->getHtml() }}@else{{ '
' }}@endif
@if($errors->has('description_html'))
{{ $errors->first('description_html') }}
@endif
\ No newline at end of file
diff --git a/resources/views/layouts/parts/custom-head.blade.php b/resources/views/layouts/parts/custom-head.blade.php
index a13215cf8..caa177030 100644
--- a/resources/views/layouts/parts/custom-head.blade.php
+++ b/resources/views/layouts/parts/custom-head.blade.php
@@ -1,6 +1,6 @@
@inject('headContent', 'BookStack\Theming\CustomHtmlHeadContentProvider')
-@if(setting('app-custom-head') && !request()->routeIs('settings.category'))
+@if(!request()->routeIs('settings.category'))
{!! $headContent->forWeb() !!}
diff --git a/resources/views/pages/parts/show-sidebar-section-actions.blade.php b/resources/views/pages/parts/show-sidebar-section-actions.blade.php
new file mode 100644
index 000000000..ae115b69e
--- /dev/null
+++ b/resources/views/pages/parts/show-sidebar-section-actions.blade.php
@@ -0,0 +1,57 @@
+
+
{{ trans('common.actions') }}
+
+
+
+ {{--User Actions--}}
+ @if(userCan(\BookStack\Permissions\Permission::PageUpdate, $page))
+
+ @icon('edit')
+ {{ trans('common.edit') }}
+
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::PageCreateAll) || userCan(\BookStack\Permissions\Permission::PageCreateOwn) || userCanOnAny(\BookStack\Permissions\Permission::Create, \BookStack\Entities\Models\Book::class) || userCanOnAny(\BookStack\Permissions\Permission::Create, \BookStack\Entities\Models\Chapter::class))
+
+ @icon('copy')
+ {{ trans('common.copy') }}
+
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::PageUpdate, $page))
+ @if(userCan(\BookStack\Permissions\Permission::PageDelete, $page))
+
+ @icon('folder')
+ {{ trans('common.move') }}
+
+ @endif
+ @endif
+
+ @icon('history')
+ {{ trans('entities.revisions') }}
+
+ @if(userCan(\BookStack\Permissions\Permission::RestrictionsManage, $page))
+
+ @icon('lock')
+ {{ trans('entities.permissions') }}
+
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::PageDelete, $page))
+
+ @icon('delete')
+ {{ trans('common.delete') }}
+
+ @endif
+
+
+
+ @if($watchOptions->canWatch() && !$watchOptions->isWatching())
+ @include('entities.watch-action', ['entity' => $page])
+ @endif
+ @if(!user()->isGuest())
+ @include('entities.favourite-action', ['entity' => $page])
+ @endif
+ @if(userCan(\BookStack\Permissions\Permission::ContentExport))
+ @include('entities.export-menu', ['entity' => $page])
+ @endif
+
+
+
\ No newline at end of file
diff --git a/resources/views/pages/parts/show-sidebar-section-attachments.blade.php b/resources/views/pages/parts/show-sidebar-section-attachments.blade.php
new file mode 100644
index 000000000..975724015
--- /dev/null
+++ b/resources/views/pages/parts/show-sidebar-section-attachments.blade.php
@@ -0,0 +1,8 @@
+@if($page->attachments->count() > 0)
+
+
{{ trans('entities.pages_attachments') }}
+
+ @include('attachments.list', ['attachments' => $page->attachments])
+
+
+@endif
\ No newline at end of file
diff --git a/resources/views/pages/parts/show-sidebar-section-details.blade.php b/resources/views/pages/parts/show-sidebar-section-details.blade.php
new file mode 100644
index 000000000..391f30ce4
--- /dev/null
+++ b/resources/views/pages/parts/show-sidebar-section-details.blade.php
@@ -0,0 +1,61 @@
+
+
{{ trans('common.details') }}
+
+ @include('entities.meta', ['entity' => $page, 'watchOptions' => $watchOptions])
+
+ @if($book->hasPermissions())
+
+ @endif
+
+ @if($page->chapter && $page->chapter->hasPermissions())
+
+ @endif
+
+ @if($page->hasPermissions())
+
+ @endif
+
+ @if($page->template)
+
+ @endif
+
+
\ No newline at end of file
diff --git a/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php b/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php
new file mode 100644
index 000000000..88db87e64
--- /dev/null
+++ b/resources/views/pages/parts/show-sidebar-section-page-nav.blade.php
@@ -0,0 +1,15 @@
+@if(isset($pageNav) && count($pageNav))
+
+ {{ trans('entities.pages_navigation') }}
+
+
+
+
+@endif
\ No newline at end of file
diff --git a/resources/views/pages/parts/show-sidebar-section-tags.blade.php b/resources/views/pages/parts/show-sidebar-section-tags.blade.php
new file mode 100644
index 000000000..354da627c
--- /dev/null
+++ b/resources/views/pages/parts/show-sidebar-section-tags.blade.php
@@ -0,0 +1,5 @@
+@if($page->tags->count() > 0)
+
+ @include('entities.tag-list', ['entity' => $page])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php
index fcec90157..d34bfc6ae 100644
--- a/resources/views/pages/show.blade.php
+++ b/resources/views/pages/show.blade.php
@@ -22,7 +22,7 @@
class="page-content clearfix">
@include('pages.parts.page-display')
- @include('pages.parts.pointer', ['page' => $page])
+ @include('pages.parts.pointer', ['page' => $page, 'commentTree' => $commentTree])
@include('entities.sibling-navigation', ['next' => $next, 'previous' => $previous])
@@ -36,159 +36,13 @@
@stop
@section('left')
-
- @if($page->tags->count() > 0)
-
- @include('entities.tag-list', ['entity' => $page])
-
- @endif
-
- @if ($page->attachments->count() > 0)
-
-
{{ trans('entities.pages_attachments') }}
-
- @include('attachments.list', ['attachments' => $page->attachments])
-
-
- @endif
-
- @if (isset($pageNav) && count($pageNav))
-
- {{ trans('entities.pages_navigation') }}
-
-
-
-
- @endif
-
+ @include('pages.parts.show-sidebar-section-tags', ['page' => $page])
+ @include('pages.parts.show-sidebar-section-attachments', ['page' => $page])
+ @include('pages.parts.show-sidebar-section-page-nav', ['pageNav' => $pageNav])
@include('entities.book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
@stop
@section('right')
-
-
{{ trans('common.details') }}
-
- @include('entities.meta', ['entity' => $page, 'watchOptions' => $watchOptions])
-
- @if($book->hasPermissions())
-
- @endif
-
- @if($page->chapter && $page->chapter->hasPermissions())
-
- @endif
-
- @if($page->hasPermissions())
-
- @endif
-
- @if($page->template)
-
- @endif
-
-
-
-
-
{{ trans('common.actions') }}
-
-
-
- {{--User Actions--}}
- @if(userCan(\BookStack\Permissions\Permission::PageUpdate, $page))
-
- @icon('edit')
- {{ trans('common.edit') }}
-
- @endif
- @if(userCan(\BookStack\Permissions\Permission::PageCreateAll) || userCan(\BookStack\Permissions\Permission::PageCreateOwn) || userCanOnAny(\BookStack\Permissions\Permission::Create, \BookStack\Entities\Models\Book::class) || userCanOnAny(\BookStack\Permissions\Permission::Create, \BookStack\Entities\Models\Chapter::class))
-
- @icon('copy')
- {{ trans('common.copy') }}
-
- @endif
- @if(userCan(\BookStack\Permissions\Permission::PageUpdate, $page))
- @if(userCan(\BookStack\Permissions\Permission::PageDelete, $page))
-
- @icon('folder')
- {{ trans('common.move') }}
-
- @endif
- @endif
-
- @icon('history')
- {{ trans('entities.revisions') }}
-
- @if(userCan(\BookStack\Permissions\Permission::RestrictionsManage, $page))
-
- @icon('lock')
- {{ trans('entities.permissions') }}
-
- @endif
- @if(userCan(\BookStack\Permissions\Permission::PageDelete, $page))
-
- @icon('delete')
- {{ trans('common.delete') }}
-
- @endif
-
-
-
- @if($watchOptions->canWatch() && !$watchOptions->isWatching())
- @include('entities.watch-action', ['entity' => $page])
- @endif
- @if(!user()->isGuest())
- @include('entities.favourite-action', ['entity' => $page])
- @endif
- @if(userCan(\BookStack\Permissions\Permission::ContentExport))
- @include('entities.export-menu', ['entity' => $page])
- @endif
-
-
-
+ @include('pages.parts.show-sidebar-section-details', ['page' => $page, 'watchOptions' => $watchOptions, 'book' => $book])
+ @include('pages.parts.show-sidebar-section-actions', ['page' => $page, 'watchOptions' => $watchOptions])
@stop
diff --git a/resources/views/shelves/index.blade.php b/resources/views/shelves/index.blade.php
index bb7c57e0f..70357068d 100644
--- a/resources/views/shelves/index.blade.php
+++ b/resources/views/shelves/index.blade.php
@@ -5,51 +5,11 @@
@stop
@section('right')
-
-
-
+ @include('shelves.parts.index-sidebar-section-actions', ['view' => $view])
@stop
@section('left')
- @if($recents)
-
-
{{ trans('entities.recently_viewed') }}
- @include('entities.list', ['entities' => $recents, 'style' => 'compact'])
-
- @endif
-
-
-
{{ trans('entities.shelves_popular') }}
- @if(count($popular) > 0)
- @include('entities.list', ['entities' => $popular, 'style' => 'compact'])
- @else
-
{{ trans('entities.shelves_popular_empty') }}
- @endif
-
-
-
-
{{ trans('entities.shelves_new') }}
- @if(count($new) > 0)
- @include('entities.list', ['entities' => $new, 'style' => 'compact'])
- @else
-
{{ trans('entities.shelves_new_empty') }}
- @endif
-
+ @include('shelves.parts.index-sidebar-section-recents', ['recents' => $recents])
+ @include('shelves.parts.index-sidebar-section-popular', ['popular' => $popular])
+ @include('shelves.parts.index-sidebar-section-new', ['new' => $new])
@stop
\ No newline at end of file
diff --git a/resources/views/shelves/parts/index-sidebar-section-actions.blade.php b/resources/views/shelves/parts/index-sidebar-section-actions.blade.php
new file mode 100644
index 000000000..d5cdb4056
--- /dev/null
+++ b/resources/views/shelves/parts/index-sidebar-section-actions.blade.php
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/resources/views/shelves/parts/index-sidebar-section-new.blade.php b/resources/views/shelves/parts/index-sidebar-section-new.blade.php
new file mode 100644
index 000000000..602f60ebe
--- /dev/null
+++ b/resources/views/shelves/parts/index-sidebar-section-new.blade.php
@@ -0,0 +1,8 @@
+
+
{{ trans('entities.shelves_new') }}
+ @if(count($new) > 0)
+ @include('entities.list', ['entities' => $new, 'style' => 'compact'])
+ @else
+
{{ trans('entities.shelves_new_empty') }}
+ @endif
+
\ No newline at end of file
diff --git a/resources/views/shelves/parts/index-sidebar-section-popular.blade.php b/resources/views/shelves/parts/index-sidebar-section-popular.blade.php
new file mode 100644
index 000000000..956321c5e
--- /dev/null
+++ b/resources/views/shelves/parts/index-sidebar-section-popular.blade.php
@@ -0,0 +1,8 @@
+
+
{{ trans('entities.shelves_popular') }}
+ @if(count($popular) > 0)
+ @include('entities.list', ['entities' => $popular, 'style' => 'compact'])
+ @else
+
{{ trans('entities.shelves_popular_empty') }}
+ @endif
+
\ No newline at end of file
diff --git a/resources/views/shelves/parts/index-sidebar-section-recents.blade.php b/resources/views/shelves/parts/index-sidebar-section-recents.blade.php
new file mode 100644
index 000000000..f1a68ba4f
--- /dev/null
+++ b/resources/views/shelves/parts/index-sidebar-section-recents.blade.php
@@ -0,0 +1,6 @@
+@if($recents)
+
+
{{ trans('entities.recently_viewed') }}
+ @include('entities.list', ['entities' => $recents, 'style' => 'compact'])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/shelves/parts/show-sidebar-section-actions.blade.php b/resources/views/shelves/parts/show-sidebar-section-actions.blade.php
new file mode 100644
index 000000000..ba92e5f70
--- /dev/null
+++ b/resources/views/shelves/parts/show-sidebar-section-actions.blade.php
@@ -0,0 +1,43 @@
+
\ No newline at end of file
diff --git a/resources/views/shelves/parts/show-sidebar-section-activity.blade.php b/resources/views/shelves/parts/show-sidebar-section-activity.blade.php
new file mode 100644
index 000000000..c1c5c1d3e
--- /dev/null
+++ b/resources/views/shelves/parts/show-sidebar-section-activity.blade.php
@@ -0,0 +1,6 @@
+@if(count($activity) > 0)
+
+
{{ trans('entities.recent_activity') }}
+ @include('common.activity-list', ['activity' => $activity])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/shelves/parts/show-sidebar-section-details.blade.php b/resources/views/shelves/parts/show-sidebar-section-details.blade.php
new file mode 100644
index 000000000..8933cc419
--- /dev/null
+++ b/resources/views/shelves/parts/show-sidebar-section-details.blade.php
@@ -0,0 +1,21 @@
+
+
{{ trans('common.details') }}
+
+ @include('entities.meta', ['entity' => $shelf, 'watchOptions' => null])
+ @if($shelf->hasPermissions())
+
+ @endif
+
+
\ No newline at end of file
diff --git a/resources/views/shelves/parts/show-sidebar-section-tags.blade.php b/resources/views/shelves/parts/show-sidebar-section-tags.blade.php
new file mode 100644
index 000000000..265d61cd0
--- /dev/null
+++ b/resources/views/shelves/parts/show-sidebar-section-tags.blade.php
@@ -0,0 +1,5 @@
+@if($shelf->tags->count() > 0)
+
+ @include('entities.tag-list', ['entity' => $shelf])
+
+@endif
\ No newline at end of file
diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php
index 9ee14f1bf..9d07e5da0 100644
--- a/resources/views/shelves/show.blade.php
+++ b/resources/views/shelves/show.blade.php
@@ -69,87 +69,13 @@
@stop
@section('left')
-
- @if($shelf->tags->count() > 0)
-
- @include('entities.tag-list', ['entity' => $shelf])
-
- @endif
-
-
-
{{ trans('common.details') }}
-
- @include('entities.meta', ['entity' => $shelf, 'watchOptions' => null])
- @if($shelf->hasPermissions())
-
- @endif
-
-
-
- @if(count($activity) > 0)
-
-
{{ trans('entities.recent_activity') }}
- @include('common.activity-list', ['activity' => $activity])
-
- @endif
+ @include('shelves.parts.show-sidebar-section-tags', ['shelf' => $shelf])
+ @include('shelves.parts.show-sidebar-section-details', ['shelf' => $shelf])
+ @include('shelves.parts.show-sidebar-section-activity', ['activity' => $activity])
@stop
@section('right')
-
+ @include('shelves.parts.show-sidebar-section-actions', ['shelf' => $shelf, 'view' => $view])
@stop
diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php
index 86e10f58a..74f558f38 100644
--- a/tests/Api/BooksApiTest.php
+++ b/tests/Api/BooksApiTest.php
@@ -188,6 +188,37 @@ class BooksApiTest extends TestCase
$resp->assertJsonMissing(['name' => $customName]);
}
+ public function test_read_endpoint_lists_visible_shelves_the_book_is_assigned_to()
+ {
+ $this->actingAsApiEditor();
+ $shelf = $this->entities->shelf();
+ $otherShelf = $this->entities->shelf();
+ $book = $this->entities->book();
+ $book->shelves()->detach();
+
+ $book->shelves()->attach($shelf);
+ $book->shelves()->attach($otherShelf);
+
+ $this->assertEquals(2, $book->shelves()->count());
+
+ $this->permissions->disableEntityInheritedPermissions($otherShelf);
+
+ $resp = $this->getJson("{$this->baseEndpoint}/{$book->id}");
+ $resp->assertOk();
+ $resp->assertJsonCount(1, 'shelves');
+ $resp->assertJson([
+ 'shelves' => [
+ [
+ 'id' => $shelf->id,
+ 'name' => $shelf->name,
+ 'slug' => $shelf->slug,
+ ]
+ ]
+ ]);
+ $resp->assertJsonMissingPath('shelves.0.description');
+ $resp->assertJsonMissingPath('shelves.0.pivot');
+ }
+
public function test_update_endpoint()
{
$this->actingAsApiEditor();
diff --git a/tests/Auth/OidcTest.php b/tests/Auth/OidcTest.php
index 710e53757..8508568f1 100644
--- a/tests/Auth/OidcTest.php
+++ b/tests/Auth/OidcTest.php
@@ -822,6 +822,34 @@ class OidcTest extends TestCase
]);
}
+ public function test_oidc_auth_pre_redirect_theme_event_with_return()
+ {
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ return 'https://cats.example.com?beans=true';
+ };
+ Theme::listen(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $callback);
+
+ $resp = $this->post('/oidc/login');
+ $resp->assertRedirect('https://cats.example.com?beans=true');
+
+ $this->assertCount(1, $args);
+ $this->assertStringStartsWith('https://oidc.local/auth', $args[0]);
+ }
+
+ public function test_oidc_auth_pre_redirect_theme_event_with_no_return()
+ {
+ $callback = function ($redirectUrl) {
+ $redirectUrl = 'cat';
+ };
+ Theme::listen(ThemeEvents::OIDC_AUTH_PRE_REDIRECT, $callback);
+
+ $resp = $this->post('/oidc/login');
+ $redirect = $resp->headers->get('Location');
+ $this->assertStringStartsWith('https://oidc.local/auth?', $redirect);
+ }
+
public function test_pkce_used_on_authorize_and_access()
{
// Start auth
diff --git a/tests/Commands/InstallModuleCommandTest.php b/tests/Commands/InstallModuleCommandTest.php
new file mode 100644
index 000000000..8ffc4ead3
--- /dev/null
+++ b/tests/Commands/InstallModuleCommandTest.php
@@ -0,0 +1,299 @@
+usingThemeFolder(function () {
+ $zip = $this->getModuleZipPath();
+ $expectedInstallPath = theme_path('modules/test-module');
+ $this->artisan('bookstack:install-module', ['location' => $zip])
+ ->expectsOutput("\nThis will install a module from: {$zip}\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!')
+ ->expectsOutput("Install location: {$expectedInstallPath}")
+ ->assertExitCode(0);
+
+ $this->assertDirectoryExists($expectedInstallPath);
+ $this->assertFileExists($expectedInstallPath . '/bookstack-module.json');
+ });
+ }
+
+ public function test_remote_module_install_with_active_theme()
+ {
+ $this->usingThemeFolder(function () {
+ $zip = $this->getModuleZipPath();
+
+ $http = $this->mockHttpClient([
+ new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip))
+ ]);
+ $expectedInstallPath = theme_path('modules/test-module');
+
+ $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip'])
+ ->expectsOutput("\nThis will download a module from: example.com\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
+ ->expectsConfirmation('Are you sure you trust this source?', 'yes')
+ ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!')
+ ->expectsOutput("Install location: {$expectedInstallPath}")
+ ->assertExitCode(0);
+
+ $this->assertEquals(1, $http->requestCount());
+ $request = $http->requestAt(0);
+ $this->assertEquals('/test-module.zip', $request->getUri()->getPath());
+
+ $this->assertDirectoryExists($expectedInstallPath);
+ $this->assertFileExists($expectedInstallPath . '/bookstack-module.json');
+ });
+ }
+
+ public function test_remote_http_module_warns_and_prompts_users()
+ {
+ $this->usingThemeFolder(function () {
+ $zip = $this->getModuleZipPath();
+
+ $http = $this->mockHttpClient([
+ new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip))
+ ]);
+ $expectedInstallPath = theme_path('modules/test-module');
+
+ $this->artisan('bookstack:install-module', ['location' => 'http://example.com/test-module.zip'])
+ ->expectsOutput("\nThis will download a module from: example.com\n\nModules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.")
+ ->expectsConfirmation('Are you sure you trust this source?', 'yes')
+ ->expectsOutput("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks.")
+ ->expectsConfirmation('Are you sure you want to continue without HTTPS?', 'yes')
+ ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!')
+ ->expectsOutput("Install location: {$expectedInstallPath}")
+ ->assertExitCode(0);
+
+ $request = $http->requestAt(0);
+ $this->assertEquals('/test-module.zip', $request->getUri()->getPath());
+ });
+ }
+
+ public function test_remote_module_install_follows_redirects()
+ {
+ $this->usingThemeFolder(function () {
+ $zip = $this->getModuleZipPath();
+
+ $http = $this->mockHttpClient([
+ new Response(302, ['Location' => 'https://example.com/a-test-module.zip']),
+ new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip))
+ ]);
+
+ $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip'])
+ ->expectsConfirmation('Are you sure you trust this source?', 'yes')
+ ->assertExitCode(0);
+
+ $this->assertEquals(2, $http->requestCount());
+ $this->assertEquals('/test-module.zip', $http->requestAt(0)->getUri()->getPath());
+ $this->assertEquals('/a-test-module.zip', $http->requestAt(1)->getUri()->getPath());
+ });
+ }
+
+ public function test_remote_module_install_does_not_follow_redirects_to_different_origin()
+ {
+ $this->usingThemeFolder(function () {
+ $zip = $this->getModuleZipPath();
+
+ $http = $this->mockHttpClient([
+ new Response(302, ['Location' => 'http://example.com/a-test-module.zip']),
+ new Response(200, ['Content-Length' => filesize($zip)], file_get_contents($zip))
+ ]);
+
+ $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip'])
+ ->expectsConfirmation('Are you sure you trust this source?', 'yes')
+ ->assertExitCode(1);
+
+ $this->assertEquals(1, $http->requestCount());
+ $this->assertEquals('https', $http->requestAt(0)->getUri()->getScheme());
+ });
+ }
+
+ public function test_remote_module_install_download_failures_are_announced_to_user()
+ {
+ $this->usingThemeFolder(function () {
+ $http = $this->mockHttpClient([
+ new Response(404),
+ ]);
+
+ $this->artisan('bookstack:install-module', ['location' => 'https://example.com/test-module.zip'])
+ ->expectsConfirmation('Are you sure you trust this source?', 'yes')
+ ->expectsOutput('ERROR: Failed to download module from https://example.com/test-module.zip')
+ ->expectsOutput('Download failed with status code 404')
+ ->assertExitCode(1);
+ $this->assertEquals(1, $http->requestCount());
+ });
+ }
+
+ public function test_run_with_invalid_path_exits_early()
+ {
+ $this->artisan('bookstack:install-module', ['location' => '/not-found.zip'])
+ ->expectsOutput('ERROR: Module file not found at /not-found.zip')
+ ->assertExitCode(1);
+ }
+
+ public function test_run_with_invalid_zip_has_early_exit()
+ {
+ $zip = $this->getModuleZipPath();
+ file_put_contents($zip, 'invalid zip');
+
+ $this->artisan('bookstack:install-module', ['location' => $zip])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput("ERROR: Cannot open ZIP file at {$zip}")
+ ->assertExitCode(1);
+ }
+
+ public function test_run_with_large_zip_has_early_exit()
+ {
+ $zip = $this->getModuleZipPath(null, [
+ 'large-file.txt' => str_repeat('a', 1024 * 1024 * 51)
+ ]);
+
+ $this->artisan('bookstack:install-module', ['location' => $zip])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput("ERROR: Module ZIP file contents are too large. Maximum size is 50MB")
+ ->assertExitCode(1);
+ }
+
+ public function test_run_with_invalid_module_data_has_early_exit()
+ {
+ $zip = $this->getModuleZipPath([
+ 'name' => 'Invalid Module',
+ 'description' => 'A module with invalid data',
+ 'version' => 'dog',
+ ]);
+
+ $this->artisan('bookstack:install-module', ['location' => $zip])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput("ERROR: Failed to read module metadata with error: Module in folder \"_temp\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'")
+ ->assertExitCode(1);
+ }
+
+ public function test_local_module_install_without_active_theme_can_setup_theme_folder()
+ {
+ $zip = $this->getModuleZipPath();
+ $expectedThemePath = base_path('themes/custom');
+ File::deleteDirectory($expectedThemePath);
+
+ $this->artisan('bookstack:install-module', ['location' => $zip])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsConfirmation('No active theme folder found, would you like to create one?', 'yes')
+ ->expectsOutput("Created theme folder at {$expectedThemePath}")
+ ->expectsOutput("You will need to set APP_THEME=custom in your BookStack env configuration to enable this theme!")
+ ->expectsOutput('Module "Test Module" (v1.0.0) successfully installed!')
+ ->assertExitCode(0);
+
+ $this->assertDirectoryExists($expectedThemePath . '/modules/test-module');
+
+ File::deleteDirectory($expectedThemePath);
+ }
+
+ public function test_local_module_install_with_active_theme_and_conflicting_modules_file_causes_early_exit()
+ {
+ $this->usingThemeFolder(function () {
+ $zip = $this->getModuleZipPath();
+ File::put(theme_path('modules'), '{}');
+
+ $this->artisan('bookstack:install-module', ['location' => $zip])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput("ERROR: Cannot create a modules folder, file already exists at " . theme_path('modules'))
+ ->assertExitCode(1);
+ });
+ }
+
+ public function test_single_existing_module_with_same_name_replace()
+ {
+ $this->usingThemeFolder(function () {
+ $original = $this->createModuleFolderInCurrentTheme(['name' => 'Test Module', 'description' => 'cat', 'version' => '1.0.0']);
+ $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']);
+
+ $this->artisan('bookstack:install-module', ['location' => $new])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput('The following modules already exist with the same name:')
+ ->expectsOutput('Test Module (test-module:v1.0.0) - cat')
+ ->expectsChoice('What would you like to do?', 'Replace existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module'])
+ ->expectsOutput("Replacing existing module in test-module folder")
+ ->assertExitCode(0);
+
+ $this->assertFileExists($original . '/bookstack-module.json');
+ $metadata = json_decode(file_get_contents($original . '/bookstack-module.json'), true);
+ $this->assertEquals('2.0.0', $metadata['version']);
+ });
+ }
+
+ public function test_single_existing_module_with_same_name_cancel()
+ {
+ $this->usingThemeFolder(function () {
+ $original = $this->createModuleFolderInCurrentTheme(['name' => 'Test Module', 'description' => 'cat', 'version' => '1.0.0']);
+ $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']);
+
+ $this->artisan('bookstack:install-module', ['location' => $new])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput('The following modules already exist with the same name:')
+ ->expectsOutput('Test Module (test-module:v1.0.0) - cat')
+ ->expectsChoice('What would you like to do?', 'Cancel module install', ['Cancel module install', 'Add alongside existing module', 'Replace existing module'])
+ ->assertExitCode(1);
+
+ $this->assertFileExists($original . '/bookstack-module.json');
+ $metadata = json_decode(file_get_contents($original . '/bookstack-module.json'), true);
+ $this->assertEquals('1.0.0', $metadata['version']);
+ });
+ }
+
+ public function test_single_existing_module_with_same_name_add()
+ {
+ $this->usingThemeFolder(function () {
+ $original = $this->createModuleFolderInCurrentTheme(['name' => 'Test Module', 'description' => 'cat', 'version' => '1.0.0']);
+ $new = $this->getModuleZipPath(['name' => 'Test Module', 'description' => '', 'version' => '2.0.0']);
+
+ $this->artisan('bookstack:install-module', ['location' => $new])
+ ->expectsConfirmation('Are you sure you want to install this module?', 'yes')
+ ->expectsOutput('The following modules already exist with the same name:')
+ ->expectsOutput('Test Module (test-module:v1.0.0) - cat')
+ ->expectsChoice('What would you like to do?', 'Add alongside existing module', ['Cancel module install', 'Add alongside existing module', 'Replace existing module'])
+ ->assertExitCode(0);
+
+ $dirs = File::directories(theme_path('modules/'));
+ $this->assertCount(2, $dirs);
+ });
+ }
+
+ protected function createModuleFolderInCurrentTheme(array|null $metadata = null, array $extraFiles = []): string
+ {
+ $original = $this->getModuleZipPath($metadata, $extraFiles);
+ $targetPath = theme_path('modules/test-module');
+ mkdir($targetPath, 0777, true);
+ $originalZip = new ZipArchive();
+ $originalZip->open($original);
+ $originalZip->extractTo($targetPath);
+ $originalZip->close();
+
+ return $targetPath;
+ }
+
+ protected function getModuleZipPath(array|null $metadata = null, array $extraFiles = []): string
+ {
+ $zip = new ZipArchive();
+ $tmpFile = tempnam(sys_get_temp_dir(), 'bs-test-module');
+ $zip->open($tmpFile, ZipArchive::CREATE);
+
+ $zip->addFromString('bookstack-module.json', json_encode($metadata ?? [
+ 'name' => 'Test Module',
+ 'description' => 'A test module for BookStack',
+ 'version' => '1.0.0',
+ ]));
+
+ foreach ($extraFiles as $path => $contents) {
+ $zip->addFromString($path, $contents);
+ }
+
+ $zip->close();
+ return $tmpFile;
+ }
+}
diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php
index a7142f037..6082c59de 100644
--- a/tests/Entity/BookTest.php
+++ b/tests/Entity/BookTest.php
@@ -154,6 +154,20 @@ class BookTest extends TestCase
$this->assertNotificationContains($redirectReq, 'Book Successfully Deleted');
}
+ public function test_delete_with_shelf_context_returns_to_shelf_view_after_delete()
+ {
+ $shelf = $this->entities->shelfHasBooks();
+ /** @var Book $book */
+ $book = $shelf->books()->first();
+
+ $this->asEditor()->get($shelf->getUrl());
+ $this->get($book->getUrl());
+ $this->get($book->getUrl('/delete'));
+ $resp = $this->delete($book->getUrl());
+
+ $resp->assertRedirect($shelf->getUrl());
+ }
+
public function test_cancel_on_create_page_leads_back_to_books_listing()
{
$resp = $this->asEditor()->get('/create-book');
@@ -264,4 +278,25 @@ class BookTest extends TestCase
$resp = $this->asEditor()->get($book->getUrl());
$resp->assertSee("My great \ndescription \n \nwith newlines
", false);
}
+
+ public function test_description_with_only_br_tags_results_in_empty_p_tag_used_on_show()
+ {
+ $descriptions = [
+ '
',
+ '
',
+ '
',
+ ];
+ $book = $this->entities->book();
+ $this->asEditor();
+
+ foreach ($descriptions as $descriptionTestCase) {
+ $book->description_html = $descriptionTestCase;
+ $book->save();
+
+ $resp = $this->get($book->getUrl());
+ $html = $this->withHtml($resp);
+ $descriptionHtml = $html->getInnerHtml('.book-content > div.text-muted:first-child');
+ $this->assertEquals('
', $descriptionHtml);
+ }
+ }
}
diff --git a/tests/Helpers/EntityProvider.php b/tests/Helpers/EntityProvider.php
index 5163cef14..d4cdce512 100644
--- a/tests/Helpers/EntityProvider.php
+++ b/tests/Helpers/EntityProvider.php
@@ -110,6 +110,14 @@ class EntityProvider
return $shelf;
}
+ /**
+ * Get a shelf that has books assigned.
+ */
+ public function shelfHasBooks(): Bookshelf
+ {
+ return $this->shelf(fn(Builder $query) => $query->whereHas('books'));
+ }
+
/**
* Get all entity types from the system.
* @return array{page: Page, chapter: Chapter, book: Book, bookshelf: Bookshelf}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index f69f20d4c..c6f811b31 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -13,6 +13,7 @@ use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Env;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Testing\Assert as PHPUnit;
use Illuminate\Testing\Constraints\HasInDatabase;
@@ -157,6 +158,23 @@ abstract class TestCase extends BaseTestCase
}
}
+ protected function usingThemeFolder(callable $callback): void
+ {
+ // Create a folder and configure a theme
+ $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '='));
+ config()->set('view.theme', $themeFolderName);
+ $themeFolderPath = theme_path('');
+
+ // Create a theme folder and clean it up on application tear-down
+ File::makeDirectory($themeFolderPath);
+ $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath));
+
+ // Run provided callback with the theme env option set
+ $this->runWithEnv(['APP_THEME' => $themeFolderName], function () use ($callback, $themeFolderName) {
+ call_user_func($callback, $themeFolderName);
+ });
+ }
+
/**
* Check the keys and properties in the given map to include
* exist, albeit not exclusively, within the map to check.
diff --git a/tests/Theme/LogicalThemeEventsTest.php b/tests/Theme/LogicalThemeEventsTest.php
new file mode 100644
index 000000000..2add83868
--- /dev/null
+++ b/tests/Theme/LogicalThemeEventsTest.php
@@ -0,0 +1,397 @@
+assertInstanceOf(Environment::class, $environment);
+ $callbackCalled = true;
+
+ return $environment;
+ };
+ Theme::listen(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $callback);
+
+ $page = $this->entities->page();
+ $content = new PageContent($page);
+ $content->setNewMarkdown('# test', $this->users->editor());
+
+ $this->assertTrue($callbackCalled);
+ }
+
+ public function test_web_middleware_before()
+ {
+ $callbackCalled = false;
+ $requestParam = null;
+ $callback = function ($request) use (&$callbackCalled, &$requestParam) {
+ $requestParam = $request;
+ $callbackCalled = true;
+ };
+
+ Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback);
+ $this->get('/login', ['Donkey' => 'cat']);
+
+ $this->assertTrue($callbackCalled);
+ $this->assertInstanceOf(Request::class, $requestParam);
+ $this->assertEquals('cat', $requestParam->header('donkey'));
+ }
+
+ public function test_web_middleware_before_return_val_used_as_response()
+ {
+ $callback = function (Request $request) {
+ return response('cat', 412);
+ };
+
+ Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback);
+ $resp = $this->get('/login', ['Donkey' => 'cat']);
+ $resp->assertSee('cat');
+ $resp->assertStatus(412);
+ }
+
+ public function test_web_middleware_after()
+ {
+ $callbackCalled = false;
+ $requestParam = null;
+ $responseParam = null;
+ $callback = function ($request, Response $response) use (&$callbackCalled, &$requestParam, &$responseParam) {
+ $requestParam = $request;
+ $responseParam = $response;
+ $callbackCalled = true;
+ $response->header('donkey', 'cat123');
+ };
+
+ Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback);
+
+ $resp = $this->get('/login', ['Donkey' => 'cat']);
+ $this->assertTrue($callbackCalled);
+ $this->assertInstanceOf(Request::class, $requestParam);
+ $this->assertInstanceOf(Response::class, $responseParam);
+ $resp->assertHeader('donkey', 'cat123');
+ }
+
+ public function test_web_middleware_after_return_val_used_as_response()
+ {
+ $callback = function () {
+ return response('cat456', 443);
+ };
+
+ Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback);
+
+ $resp = $this->get('/login', ['Donkey' => 'cat']);
+ $resp->assertSee('cat456');
+ $resp->assertStatus(443);
+ }
+
+ public function test_auth_login_standard()
+ {
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ };
+
+ Theme::listen(ThemeEvents::AUTH_LOGIN, $callback);
+ $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals('standard', $args[0]);
+ $this->assertInstanceOf(User::class, $args[1]);
+ }
+
+ public function test_auth_register_standard()
+ {
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ };
+ Theme::listen(ThemeEvents::AUTH_REGISTER, $callback);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $user = User::factory()->make();
+ $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals('standard', $args[0]);
+ $this->assertInstanceOf(User::class, $args[1]);
+ }
+
+ public function test_auth_pre_register()
+ {
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ };
+ Theme::listen(ThemeEvents::AUTH_PRE_REGISTER, $callback);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $user = User::factory()->make();
+ $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals('standard', $args[0]);
+ $this->assertEquals([
+ 'email' => $user->email,
+ 'name' => $user->name,
+ 'password' => 'password',
+ ], $args[1]);
+ $this->assertDatabaseHas('users', ['email' => $user->email]);
+ }
+
+ public function test_auth_pre_register_with_false_return_blocks_registration()
+ {
+ $callback = function () {
+ return false;
+ };
+ Theme::listen(ThemeEvents::AUTH_PRE_REGISTER, $callback);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $user = User::factory()->make();
+ $resp = $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
+ $resp->assertRedirect('/login');
+ $this->assertSessionError('User account could not be registered for the provided details');
+ $this->assertDatabaseMissing('users', ['email' => $user->email]);
+ }
+
+ public function test_webhook_call_before()
+ {
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+
+ return ['test' => 'hello!'];
+ };
+ Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, $callback);
+
+ $responses = $this->mockHttpClient([new \GuzzleHttp\Psr7\Response(200, [], '')]);
+
+ $webhook = new Webhook(['name' => 'Test webhook', 'endpoint' => 'https://example.com']);
+ $webhook->save();
+ $event = ActivityType::PAGE_UPDATE;
+ $detail = Page::query()->first();
+
+ dispatch((new DispatchWebhookJob($webhook, $event, $detail)));
+
+ $this->assertCount(5, $args);
+ $this->assertEquals($event, $args[0]);
+ $this->assertEquals($webhook->id, $args[1]->id);
+ $this->assertEquals($detail->id, $args[2]->id);
+
+ $this->assertEquals(1, $responses->requestCount());
+ $request = $responses->latestRequest();
+ $reqData = json_decode($request->getBody(), true);
+ $this->assertEquals('hello!', $reqData['test']);
+ }
+
+ public function test_activity_logged()
+ {
+ $book = $this->entities->book();
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ };
+
+ Theme::listen(ThemeEvents::ACTIVITY_LOGGED, $callback);
+ $this->asEditor()->put($book->getUrl(), ['name' => 'My cool update book!']);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals(ActivityType::BOOK_UPDATE, $args[0]);
+ $this->assertTrue($args[1] instanceof Book);
+ $this->assertEquals($book->id, $args[1]->id);
+ }
+
+ public function test_page_content_pre_store_fires_on_page_save()
+ {
+ $page = $this->entities->page();
+
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ return 'New Content!
';
+ };
+
+ Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, $callback);
+
+ $this->asEditor();
+ $this->entities->updatePage($page, ['name' => 'My cool update page!', 'html' => 'Old content!
']);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals($page->id, $args[1]->id);
+ $this->assertEquals('Old content!
', $args[0]);
+
+ $newPageHtml = $page->refresh()->html;
+ $this->assertEquals('New Content!
', $newPageHtml);
+ }
+
+ public function test_page_content_pre_store_does_not_change_content_if_nothing_returned()
+ {
+ $page = $this->entities->page();
+ Theme::listen(ThemeEvents::PAGE_CONTENT_PRE_STORE, fn() => null);
+
+ $this->asEditor();
+ $this->entities->updatePage($page, ['name' => 'My cool update page!', 'html' => 'Old content!
']);
+
+ $newPageHtml = $page->refresh()->html;
+ $this->assertEquals('Old content!
', $newPageHtml);
+ }
+
+ public function test_page_content_post_render_fires_on_page_view()
+ {
+ $page = $this->entities->page();
+ $page->html = 'Old content!
';
+ $page->save();
+
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ return 'New postrendercontentforyou!
';
+ };
+
+ Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, $callback);
+
+ $resp = $this->asEditor()->get($page->getUrl());
+ $resp->assertSee('New postrendercontentforyou!
', false);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals($page->id, $args[1]->id);
+ $this->assertEquals('Old content!
', $args[0]);
+ }
+
+ public function test_page_content_post_render_returns_original_content_if_no_return()
+ {
+ $page = $this->entities->page();
+ $page->html = 'Old content!
';
+ $page->save();
+
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ };
+
+ Theme::listen(ThemeEvents::PAGE_CONTENT_POST_RENDER, $callback);
+
+ $resp = $this->asEditor()->get($page->getUrl());
+ $resp->assertSee('Old content!
', false);
+
+ $this->assertCount(2, $args);
+ }
+
+ public function test_page_include_parse()
+ {
+ /** @var Page $page */
+ /** @var Page $otherPage */
+ $page = $this->entities->page();
+ $otherPage = Page::query()->where('id', '!=', $page->id)->first();
+ $otherPage->html = 'This is a really cool section
';
+ $page->html = "{{@{$otherPage->id}#bkmrk-cool}}
";
+ $page->save();
+ $otherPage->save();
+
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+
+ return 'Big & content replace surprise! ';
+ };
+
+ Theme::listen(ThemeEvents::PAGE_INCLUDE_PARSE, $callback);
+ $resp = $this->asEditor()->get($page->getUrl());
+ $this->withHtml($resp)->assertElementContains('.page-content strong', 'Big & content replace surprise!');
+
+ $this->assertCount(4, $args);
+ $this->assertEquals($otherPage->id . '#bkmrk-cool', $args[0]);
+ $this->assertEquals('This is a really cool section', $args[1]);
+ $this->assertTrue($args[2] instanceof Page);
+ $this->assertTrue($args[3] instanceof Page);
+ $this->assertEquals($page->id, $args[2]->id);
+ $this->assertEquals($otherPage->id, $args[3]->id);
+ }
+
+ public function test_routes_register_web_and_web_auth()
+ {
+ $functionsContent = <<<'END'
+get('/cat', fn () => 'cat')->name('say.cat');
+});
+Theme::listen(ThemeEvents::ROUTES_REGISTER_WEB_AUTH, function (Router $router) {
+ $router->get('/dog', fn () => 'dog')->name('say.dog');
+});
+END;
+
+ $this->usingThemeFolder(function () use ($functionsContent) {
+
+ $functionsFile = theme_path('functions.php');
+ file_put_contents($functionsFile, $functionsContent);
+
+ $app = $this->createApplication();
+ /** @var \Illuminate\Routing\Router $router */
+ $router = $app->get('router');
+
+ /** @var \Illuminate\Routing\Route $catRoute */
+ $catRoute = $router->getRoutes()->getRoutesByName()['say.cat'];
+ $this->assertEquals(['web'], $catRoute->middleware());
+
+ /** @var \Illuminate\Routing\Route $dogRoute */
+ $dogRoute = $router->getRoutes()->getRoutesByName()['say.dog'];
+ $this->assertEquals(['web', 'auth'], $dogRoute->middleware());
+ });
+ }
+
+ public function test_register_views_to_insert_views_before_and_after()
+ {
+ $this->usingThemeFolder(function (string $folder) {
+ $before = 'this-is-my-before-header-string';
+ $afterA = 'this-is-my-after-header-string-a';
+ $afterB = 'this-is-my-after-header-string-b';
+ $afterC = 'this-is-my-after-header-string-{{ 1+51 }}';
+
+ $functionsContent = <<<'CONTENT'
+renderBefore('layouts.parts.header', 'before', 4);
+ $themeViews->renderAfter('layouts.parts.header', 'after-a', 4);
+ $themeViews->renderAfter('layouts.parts.header', 'after-b', 1);
+ $themeViews->renderAfter('layouts.parts.header', 'after-c', 12);
+});
+CONTENT;
+
+ $viewDir = theme_path();
+ file_put_contents($viewDir . '/functions.php', $functionsContent);
+ file_put_contents($viewDir . '/before.blade.php', $before);
+ file_put_contents($viewDir . '/after-a.blade.php', $afterA);
+ file_put_contents($viewDir . '/after-b.blade.php', $afterB);
+ file_put_contents($viewDir . '/after-c.blade.php', $afterC);
+
+ $this->refreshApplication();
+ $this->artisan('view:clear');
+
+ $resp = $this->get('/login');
+ $resp->assertSee($before);
+ // Ensure ordering of the multiple after views
+ $resp->assertSee($afterB . "\n" . $afterA . "\nthis-is-my-after-header-string-52");
+ });
+
+ $this->artisan('view:clear');
+ }
+}
diff --git a/tests/Theme/LogicalThemeTest.php b/tests/Theme/LogicalThemeTest.php
new file mode 100644
index 000000000..feb1c7ea7
--- /dev/null
+++ b/tests/Theme/LogicalThemeTest.php
@@ -0,0 +1,105 @@
+usingThemeFolder(function ($themeFolder) {
+ $functionsFile = theme_path('functions.php');
+ app()->alias('cat', 'dog');
+ file_put_contents($functionsFile, "alias('cat', 'dog');});");
+ $this->runWithEnv(['APP_THEME' => $themeFolder], function () {
+ $this->assertEquals('cat', $this->app->getAlias('dog'));
+ });
+ });
+ }
+
+ public function test_theme_functions_loads_errors_are_caught_and_logged()
+ {
+ $this->usingThemeFolder(function ($themeFolder) {
+ $functionsFile = theme_path('functions.php');
+ file_put_contents($functionsFile, "expectException(ThemeException::class);
+ $this->expectExceptionMessageMatches('/Failed loading theme functions file at ".*?" with error: Class "BookStack\\\\Biscuits" not found/');
+
+ $this->runWithEnv(['APP_THEME' => $themeFolder], fn() => null);
+ });
+ }
+
+ public function test_add_social_driver()
+ {
+ Theme::addSocialDriver('catnet', [
+ 'client_id' => 'abc123',
+ 'client_secret' => 'def456',
+ ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
+
+ $this->assertEquals('catnet', config('services.catnet.name'));
+ $this->assertEquals('abc123', config('services.catnet.client_id'));
+ $this->assertEquals(url('/login/service/catnet/callback'), config('services.catnet.redirect'));
+
+ $loginResp = $this->get('/login');
+ $loginResp->assertSee('login/service/catnet');
+ }
+
+ public function test_add_social_driver_uses_name_in_config_if_given()
+ {
+ Theme::addSocialDriver('catnet', [
+ 'client_id' => 'abc123',
+ 'client_secret' => 'def456',
+ 'name' => 'Super Cat Name',
+ ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
+
+ $this->assertEquals('Super Cat Name', config('services.catnet.name'));
+ $loginResp = $this->get('/login');
+ $loginResp->assertSee('Super Cat Name');
+ }
+
+ public function test_add_social_driver_allows_a_configure_for_redirect_callback_to_be_passed()
+ {
+ Theme::addSocialDriver(
+ 'discord',
+ [
+ 'client_id' => 'abc123',
+ 'client_secret' => 'def456',
+ 'name' => 'Super Cat Name',
+ ],
+ 'SocialiteProviders\Discord\DiscordExtendSocialite@handle',
+ function ($driver) {
+ $driver->with(['donkey' => 'donut']);
+ }
+ );
+
+ $loginResp = $this->get('/login/service/discord');
+ $redirect = $loginResp->headers->get('location');
+ $this->assertStringContainsString('donkey=donut', $redirect);
+ }
+
+ public function test_register_command_allows_provided_command_to_be_usable_via_artisan()
+ {
+ Theme::registerCommand(new MyCustomCommand());
+
+ Artisan::call('bookstack:test-custom-command', []);
+ $output = Artisan::output();
+
+ $this->assertStringContainsString('Command ran!', $output);
+ }
+}
+
+class MyCustomCommand extends Command
+{
+ protected $signature = 'bookstack:test-custom-command';
+
+ public function handle()
+ {
+ $this->line('Command ran!');
+ }
+}
diff --git a/tests/Theme/ThemeModuleTest.php b/tests/Theme/ThemeModuleTest.php
new file mode 100644
index 000000000..d1c7225b6
--- /dev/null
+++ b/tests/Theme/ThemeModuleTest.php
@@ -0,0 +1,254 @@
+usingThemeFolder(function ($themeFolder) {
+ $a = theme_path('modules/a');
+ $b = theme_path('modules/b');
+ mkdir($a, 0777, true);
+ mkdir($b, 0777, true);
+
+ file_put_contents($a . '/bookstack-module.json', json_encode([
+ 'name' => 'Module A',
+ 'description' => 'This is module A',
+ 'version' => '1.0.0',
+ ]));
+ file_put_contents($b . '/bookstack-module.json', json_encode([
+ 'name' => 'Module B',
+ 'description' => 'This is module B',
+ 'version' => 'v0.5.0',
+ ]));
+
+ $this->refreshApplication();
+
+ $modules = Theme::getModules();
+ $this->assertCount(2, $modules);
+
+ $moduleA = $modules['a'];
+ $this->assertEquals('Module A', $moduleA->name);
+ $this->assertEquals('This is module A', $moduleA->description);
+ $this->assertEquals('1.0.0', $moduleA->version);
+ });
+ }
+
+ public function test_module_not_loaded_if_no_bookstack_module_json()
+ {
+ $this->usingThemeFolder(function ($themeFolder) {
+ $moduleDir = theme_path('/modules/a');
+ mkdir($moduleDir, 0777, true);
+ file_put_contents($moduleDir . '/module.json', '{}');
+ $this->refreshApplication();
+ $modules = Theme::getModules();
+ $this->assertCount(0, $modules);
+ });
+ }
+
+ public function test_language_text_overridable_via_module()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $translationPath = $moduleFolderPath . '/lang/en';
+ mkdir($translationPath, 0777, true);
+ file_put_contents($translationPath . '/entities.php', ' "SuperBeans"];');
+ $this->refreshApplication();
+
+ $this->asAdmin()->get('/books')->assertSee('SuperBeans');
+ });
+ }
+
+ public function test_language_files_merge_with_theme_files_with_theme_taking_precedence()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $moduleTranslationPath = $moduleFolderPath . '/lang/en';
+ mkdir($moduleTranslationPath, 0777, true);
+ file_put_contents($moduleTranslationPath . '/entities.php', ' "SuperBeans", "recently_viewed" => "ViewedBiscuits"];');
+
+ $themeTranslationPath = theme_path('lang/en');
+ mkdir($themeTranslationPath, 0777, true);
+ file_put_contents($themeTranslationPath . '/entities.php', ' "WonderBeans"];');
+ $this->refreshApplication();
+
+ $this->asAdmin()->get('/books')
+ ->assertSee('WonderBeans')
+ ->assertDontSee('SuperBeans')
+ ->assertSee('ViewedBiscuits');
+ });
+ }
+
+ public function test_view_files_overridable_from_module()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $viewsFolder = $moduleFolderPath . '/views/layouts/parts';
+ mkdir($viewsFolder, 0777, true);
+ file_put_contents($viewsFolder . '/header.blade.php', 'My custom header that says badgerriffic');
+ $this->refreshApplication();
+ $this->asAdmin()->get('/')->assertSee('badgerriffic');
+ });
+ }
+
+ public function test_theme_view_files_take_precedence_over_module_view_files()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $viewsFolder = $moduleFolderPath . '/views/layouts/parts';
+ mkdir($viewsFolder, 0777, true);
+ file_put_contents($viewsFolder . '/header.blade.php', 'My custom header that says badgerriffic');
+
+ $themeViewsFolder = theme_path('layouts/parts');
+ mkdir($themeViewsFolder, 0777, true);
+ file_put_contents($themeViewsFolder . '/header.blade.php', 'My theme header that says awesomeferrets');
+
+ $this->refreshApplication();
+ $this->asAdmin()->get('/')
+ ->assertDontSee('badgerriffic')
+ ->assertSee('awesomeferrets');
+ });
+ }
+
+ public function test_theme_and_modules_views_can_be_used_at_the_same_time()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $viewsFolder = $moduleFolderPath . '/views/layouts/parts';
+ mkdir($viewsFolder, 0777, true);
+ file_put_contents($viewsFolder . '/base-body-start.blade.php', 'My custom header that says badgerriffic');
+
+ $themeViewsFolder = theme_path('layouts/parts');
+ mkdir($themeViewsFolder, 0777, true);
+ file_put_contents($themeViewsFolder . '/base-body-end.blade.php', 'My theme header that says awesomeferrets');
+
+ $this->refreshApplication();
+ $this->asAdmin()->get('/')
+ ->assertSee('badgerriffic')
+ ->assertSee('awesomeferrets');
+ });
+ }
+
+ public function test_icons_can_be_overridden_from_module()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $iconsFolder = $moduleFolderPath . '/icons';
+ mkdir($iconsFolder, 0777, true);
+ file_put_contents($iconsFolder . '/books.svg', ' ');
+ $this->refreshApplication();
+
+ $this->asAdmin()->get('/')->assertSee('supericonpath', false);
+ });
+ }
+
+ public function test_theme_icons_take_precedence_over_module_icons()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $iconsFolder = $moduleFolderPath . '/icons';
+ mkdir($iconsFolder, 0777, true);
+ file_put_contents($iconsFolder . '/books.svg', ' ');
+ $this->refreshApplication();
+
+ $themeViewsFolder = theme_path('icons');
+ mkdir($themeViewsFolder, 0777, true);
+ file_put_contents($themeViewsFolder . '/books.svg', ' ');
+
+
+ $this->asAdmin()->get('/')
+ ->assertSee('wackyiconpath', false)
+ ->assertDontSee('supericonpath', false);
+ });
+ }
+
+ public function test_public_folder_can_be_provided_from_module()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $publicFolder = $moduleFolderPath . '/public';
+ mkdir($publicFolder, 0777, true);
+ $themeName = basename(dirname(dirname($moduleFolderPath)));
+ file_put_contents($publicFolder . '/test.txt', 'hellofrominsidethisfileimaghostwoooo!');
+ $this->refreshApplication();
+
+ $resp = $this->asAdmin()->get("/theme/{$themeName}/test.txt")->streamedContent();
+ $this->assertEquals('hellofrominsidethisfileimaghostwoooo!', $resp);
+ });
+ }
+
+ public function test_theme_public_files_take_precedence_over_modules()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ $publicFolder = $moduleFolderPath . '/public';
+ mkdir($publicFolder, 0777, true);
+ $themeName = basename(theme_path());
+ file_put_contents($publicFolder . '/test.txt', 'hellofrominsidethisfileimaghostwoooo!');
+
+ $themePublicFolder = theme_path('public');
+ mkdir($themePublicFolder, 0777, true);
+ file_put_contents($themePublicFolder . '/test.txt', 'imadifferentghostinsidethetheme,woooooo!');
+
+ $this->refreshApplication();
+
+ $resp = $this->asAdmin()->get("/theme/{$themeName}/test.txt")->streamedContent();
+ $this->assertEquals('imadifferentghostinsidethetheme,woooooo!', $resp);
+ });
+ }
+
+ public function test_logical_functions_file_loaded_from_module_and_it_runs_alongside_theme_functions()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ file_put_contents($moduleFolderPath . '/functions.php', "alias('cat', 'dog');});");
+
+ $themeFunctionsFile = theme_path('functions.php');
+ file_put_contents($themeFunctionsFile, "alias('beans', 'cheese');});");
+
+ $this->refreshApplication();
+
+ $this->assertEquals('cat', $this->app->getAlias('dog'));
+ $this->assertEquals('beans', $this->app->getAlias('cheese'));
+ });
+ }
+
+ public function test_module_can_use_theme_view_render_functions()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ file_put_contents($moduleFolderPath . '/functions.php', " \$views->renderBefore('layouts.parts.header', 'cat', 100));");
+ mkdir($moduleFolderPath . '/views', 0777, true);
+ file_put_contents($moduleFolderPath . '/views/cat.blade.php', 'mysupercatispouncy');
+
+ $this->refreshApplication();
+
+ $this->asAdmin()->get('/')->assertSee('mysupercatispouncy');
+ });
+ }
+
+ public function test_module_can_provide_head_content()
+ {
+ $this->usingModuleFolder(function (string $moduleFolderPath) {
+ mkdir($moduleFolderPath . '/head', 0777, true);
+ file_put_contents($moduleFolderPath . '/head/hello.html', ' ');
+
+ $this->refreshApplication();
+
+ $cspService = $this->app->make(CspService::class);
+ $nonce = $cspService->getNonce();
+
+ $resp = $this->asAdmin()->get('/');
+ $resp->assertSee(' ', false);
+ $resp->assertSee('', false);
+ });
+ }
+
+ protected function usingModuleFolder(callable $callback): void
+ {
+ $this->usingThemeFolder(function (string $themeFolder) use ($callback) {
+ $moduleFolderPath = theme_path('modules/test-module');
+ mkdir($moduleFolderPath, 0777, true);
+ file_put_contents($moduleFolderPath . '/bookstack-module.json', json_encode([
+ 'name' => 'Test Module',
+ 'description' => 'This is a test module',
+ 'version' => 'v1.0.0',
+ ]));
+ $callback($moduleFolderPath);
+ });
+ }
+}
diff --git a/tests/Theme/VisualThemeTest.php b/tests/Theme/VisualThemeTest.php
new file mode 100644
index 000000000..c06807d7f
--- /dev/null
+++ b/tests/Theme/VisualThemeTest.php
@@ -0,0 +1,132 @@
+usingThemeFolder(function () {
+ $translationPath = theme_path('/lang/en');
+ File::makeDirectory($translationPath, 0777, true);
+
+ $customTranslations = ' \'Sandwiches\'];
+ ';
+ file_put_contents($translationPath . '/entities.php', $customTranslations);
+
+ $homeRequest = $this->actingAs($this->users->viewer())->get('/');
+ $this->withHtml($homeRequest)->assertElementContains('header nav', 'Sandwiches');
+ });
+ }
+
+ public function test_custom_settings_category_page_can_be_added_via_view_file()
+ {
+ $content = 'My SuperCustomSettings';
+
+ $this->usingThemeFolder(function (string $folder) use ($content) {
+ $viewDir = theme_path('settings/categories');
+ mkdir($viewDir, 0777, true);
+ file_put_contents($viewDir . '/beans.blade.php', $content);
+
+ $this->asAdmin()->get('/settings/beans')->assertSee($content);
+ });
+ }
+
+ public function test_base_body_start_and_end_template_files_can_be_used()
+ {
+ $bodyStartStr = 'barry-fought-against-the-panther';
+ $bodyEndStr = 'barry-lost-his-fight-with-grace';
+
+ $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr) {
+ $viewDir = theme_path('layouts/parts');
+ mkdir($viewDir, 0777, true);
+ file_put_contents($viewDir . '/base-body-start.blade.php', $bodyStartStr);
+ file_put_contents($viewDir . '/base-body-end.blade.php', $bodyEndStr);
+
+ $resp = $this->asEditor()->get('/');
+ $resp->assertSee($bodyStartStr);
+ $resp->assertSee($bodyEndStr);
+ });
+ }
+
+ public function test_export_body_start_and_end_template_files_can_be_used()
+ {
+ $bodyStartStr = 'garry-fought-against-the-panther';
+ $bodyEndStr = 'garry-lost-his-fight-with-grace';
+ $page = $this->entities->page();
+
+ $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr, $page) {
+ $viewDir = theme_path('layouts/parts');
+ mkdir($viewDir, 0777, true);
+ file_put_contents($viewDir . '/export-body-start.blade.php', $bodyStartStr);
+ file_put_contents($viewDir . '/export-body-end.blade.php', $bodyEndStr);
+
+ $resp = $this->asEditor()->get($page->getUrl('/export/html'));
+ $resp->assertSee($bodyStartStr);
+ $resp->assertSee($bodyEndStr);
+ });
+ }
+
+ public function test_login_and_register_message_template_files_can_be_used()
+ {
+ $loginMessage = 'Welcome to this instance, login below you scallywag';
+ $registerMessage = 'You want to register? Enter the deets below you numpty';
+
+ $this->usingThemeFolder(function (string $folder) use ($loginMessage, $registerMessage) {
+ $viewDir = theme_path('auth/parts');
+ mkdir($viewDir, 0777, true);
+ file_put_contents($viewDir . '/login-message.blade.php', $loginMessage);
+ file_put_contents($viewDir . '/register-message.blade.php', $registerMessage);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $this->get('/login')->assertSee($loginMessage);
+ $this->get('/register')->assertSee($registerMessage);
+ });
+ }
+
+ public function test_header_links_start_template_file_can_be_used()
+ {
+ $content = 'This is added text in the header bar';
+
+ $this->usingThemeFolder(function (string $folder) use ($content) {
+ $viewDir = theme_path('layouts/parts');
+ mkdir($viewDir, 0777, true);
+ file_put_contents($viewDir . '/header-links-start.blade.php', $content);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $this->get('/login')->assertSee($content);
+ });
+ }
+
+ public function test_public_folder_contents_accessible_via_route()
+ {
+ $this->usingThemeFolder(function (string $themeFolderName) {
+ $publicDir = theme_path('public');
+ mkdir($publicDir, 0777, true);
+
+ $text = 'some-text ' . md5(random_bytes(5));
+ $css = "body { background-color: tomato !important; }";
+ file_put_contents("{$publicDir}/file.txt", $text);
+ file_put_contents("{$publicDir}/file.css", $css);
+ copy($this->files->testFilePath('test-image.png'), "{$publicDir}/image.png");
+
+ $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.txt");
+ $resp->assertStreamedContent($text);
+ $resp->assertHeader('Content-Type', 'text/plain; charset=utf-8');
+ $resp->assertHeader('Cache-Control', 'max-age=86400, private');
+
+ $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/image.png");
+ $resp->assertHeader('Content-Type', 'image/png');
+ $resp->assertHeader('Cache-Control', 'max-age=86400, private');
+
+ $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.css");
+ $resp->assertStreamedContent($css);
+ $resp->assertHeader('Content-Type', 'text/css; charset=utf-8');
+ $resp->assertHeader('Cache-Control', 'max-age=86400, private');
+ });
+ }
+}
diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php
deleted file mode 100644
index 841ff78ca..000000000
--- a/tests/ThemeTest.php
+++ /dev/null
@@ -1,521 +0,0 @@
-usingThemeFolder(function () {
- $translationPath = theme_path('/lang/en');
- File::makeDirectory($translationPath, 0777, true);
-
- $customTranslations = ' \'Sandwiches\'];
- ';
- file_put_contents($translationPath . '/entities.php', $customTranslations);
-
- $homeRequest = $this->actingAs($this->users->viewer())->get('/');
- $this->withHtml($homeRequest)->assertElementContains('header nav', 'Sandwiches');
- });
- }
-
- public function test_theme_functions_file_used_and_app_boot_event_runs()
- {
- $this->usingThemeFolder(function ($themeFolder) {
- $functionsFile = theme_path('functions.php');
- app()->alias('cat', 'dog');
- file_put_contents($functionsFile, "alias('cat', 'dog');});");
- $this->runWithEnv(['APP_THEME' => $themeFolder], function () {
- $this->assertEquals('cat', $this->app->getAlias('dog'));
- });
- });
- }
-
- public function test_theme_functions_loads_errors_are_caught_and_logged()
- {
- $this->usingThemeFolder(function ($themeFolder) {
- $functionsFile = theme_path('functions.php');
- file_put_contents($functionsFile, "expectException(ThemeException::class);
- $this->expectExceptionMessageMatches('/Failed loading theme functions file at ".*?" with error: Class "BookStack\\\\Biscuits" not found/');
-
- $this->runWithEnv(['APP_THEME' => $themeFolder], fn() => null);
- });
- }
-
- public function test_event_commonmark_environment_configure()
- {
- $callbackCalled = false;
- $callback = function ($environment) use (&$callbackCalled) {
- $this->assertInstanceOf(Environment::class, $environment);
- $callbackCalled = true;
-
- return $environment;
- };
- Theme::listen(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $callback);
-
- $page = $this->entities->page();
- $content = new PageContent($page);
- $content->setNewMarkdown('# test', $this->users->editor());
-
- $this->assertTrue($callbackCalled);
- }
-
- public function test_event_web_middleware_before()
- {
- $callbackCalled = false;
- $requestParam = null;
- $callback = function ($request) use (&$callbackCalled, &$requestParam) {
- $requestParam = $request;
- $callbackCalled = true;
- };
-
- Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback);
- $this->get('/login', ['Donkey' => 'cat']);
-
- $this->assertTrue($callbackCalled);
- $this->assertInstanceOf(Request::class, $requestParam);
- $this->assertEquals('cat', $requestParam->header('donkey'));
- }
-
- public function test_event_web_middleware_before_return_val_used_as_response()
- {
- $callback = function (Request $request) {
- return response('cat', 412);
- };
-
- Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback);
- $resp = $this->get('/login', ['Donkey' => 'cat']);
- $resp->assertSee('cat');
- $resp->assertStatus(412);
- }
-
- public function test_event_web_middleware_after()
- {
- $callbackCalled = false;
- $requestParam = null;
- $responseParam = null;
- $callback = function ($request, Response $response) use (&$callbackCalled, &$requestParam, &$responseParam) {
- $requestParam = $request;
- $responseParam = $response;
- $callbackCalled = true;
- $response->header('donkey', 'cat123');
- };
-
- Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback);
-
- $resp = $this->get('/login', ['Donkey' => 'cat']);
- $this->assertTrue($callbackCalled);
- $this->assertInstanceOf(Request::class, $requestParam);
- $this->assertInstanceOf(Response::class, $responseParam);
- $resp->assertHeader('donkey', 'cat123');
- }
-
- public function test_event_web_middleware_after_return_val_used_as_response()
- {
- $callback = function () {
- return response('cat456', 443);
- };
-
- Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback);
-
- $resp = $this->get('/login', ['Donkey' => 'cat']);
- $resp->assertSee('cat456');
- $resp->assertStatus(443);
- }
-
- public function test_event_auth_login_standard()
- {
- $args = [];
- $callback = function (...$eventArgs) use (&$args) {
- $args = $eventArgs;
- };
-
- Theme::listen(ThemeEvents::AUTH_LOGIN, $callback);
- $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']);
-
- $this->assertCount(2, $args);
- $this->assertEquals('standard', $args[0]);
- $this->assertInstanceOf(User::class, $args[1]);
- }
-
- public function test_event_auth_register_standard()
- {
- $args = [];
- $callback = function (...$eventArgs) use (&$args) {
- $args = $eventArgs;
- };
- Theme::listen(ThemeEvents::AUTH_REGISTER, $callback);
- $this->setSettings(['registration-enabled' => 'true']);
-
- $user = User::factory()->make();
- $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
-
- $this->assertCount(2, $args);
- $this->assertEquals('standard', $args[0]);
- $this->assertInstanceOf(User::class, $args[1]);
- }
-
- public function test_event_auth_pre_register()
- {
- $args = [];
- $callback = function (...$eventArgs) use (&$args) {
- $args = $eventArgs;
- };
- Theme::listen(ThemeEvents::AUTH_PRE_REGISTER, $callback);
- $this->setSettings(['registration-enabled' => 'true']);
-
- $user = User::factory()->make();
- $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
-
- $this->assertCount(2, $args);
- $this->assertEquals('standard', $args[0]);
- $this->assertEquals([
- 'email' => $user->email,
- 'name' => $user->name,
- 'password' => 'password',
- ], $args[1]);
- $this->assertDatabaseHas('users', ['email' => $user->email]);
- }
-
- public function test_event_auth_pre_register_with_false_return_blocks_registration()
- {
- $callback = function () {
- return false;
- };
- Theme::listen(ThemeEvents::AUTH_PRE_REGISTER, $callback);
- $this->setSettings(['registration-enabled' => 'true']);
-
- $user = User::factory()->make();
- $resp = $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
- $resp->assertRedirect('/login');
- $this->assertSessionError('User account could not be registered for the provided details');
- $this->assertDatabaseMissing('users', ['email' => $user->email]);
- }
-
- public function test_event_webhook_call_before()
- {
- $args = [];
- $callback = function (...$eventArgs) use (&$args) {
- $args = $eventArgs;
-
- return ['test' => 'hello!'];
- };
- Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, $callback);
-
- $responses = $this->mockHttpClient([new \GuzzleHttp\Psr7\Response(200, [], '')]);
-
- $webhook = new Webhook(['name' => 'Test webhook', 'endpoint' => 'https://example.com']);
- $webhook->save();
- $event = ActivityType::PAGE_UPDATE;
- $detail = Page::query()->first();
-
- dispatch((new DispatchWebhookJob($webhook, $event, $detail)));
-
- $this->assertCount(5, $args);
- $this->assertEquals($event, $args[0]);
- $this->assertEquals($webhook->id, $args[1]->id);
- $this->assertEquals($detail->id, $args[2]->id);
-
- $this->assertEquals(1, $responses->requestCount());
- $request = $responses->latestRequest();
- $reqData = json_decode($request->getBody(), true);
- $this->assertEquals('hello!', $reqData['test']);
- }
-
- public function test_event_activity_logged()
- {
- $book = $this->entities->book();
- $args = [];
- $callback = function (...$eventArgs) use (&$args) {
- $args = $eventArgs;
- };
-
- Theme::listen(ThemeEvents::ACTIVITY_LOGGED, $callback);
- $this->asEditor()->put($book->getUrl(), ['name' => 'My cool update book!']);
-
- $this->assertCount(2, $args);
- $this->assertEquals(ActivityType::BOOK_UPDATE, $args[0]);
- $this->assertTrue($args[1] instanceof Book);
- $this->assertEquals($book->id, $args[1]->id);
- }
-
- public function test_event_page_include_parse()
- {
- /** @var Page $page */
- /** @var Page $otherPage */
- $page = $this->entities->page();
- $otherPage = Page::query()->where('id', '!=', $page->id)->first();
- $otherPage->html = 'This is a really cool section
';
- $page->html = "{{@{$otherPage->id}#bkmrk-cool}}
";
- $page->save();
- $otherPage->save();
-
- $args = [];
- $callback = function (...$eventArgs) use (&$args) {
- $args = $eventArgs;
-
- return 'Big & content replace surprise! ';
- };
-
- Theme::listen(ThemeEvents::PAGE_INCLUDE_PARSE, $callback);
- $resp = $this->asEditor()->get($page->getUrl());
- $this->withHtml($resp)->assertElementContains('.page-content strong', 'Big & content replace surprise!');
-
- $this->assertCount(4, $args);
- $this->assertEquals($otherPage->id . '#bkmrk-cool', $args[0]);
- $this->assertEquals('This is a really cool section', $args[1]);
- $this->assertTrue($args[2] instanceof Page);
- $this->assertTrue($args[3] instanceof Page);
- $this->assertEquals($page->id, $args[2]->id);
- $this->assertEquals($otherPage->id, $args[3]->id);
- }
-
- public function test_event_routes_register_web_and_web_auth()
- {
- $functionsContent = <<<'END'
-get('/cat', fn () => 'cat')->name('say.cat');
-});
-Theme::listen(ThemeEvents::ROUTES_REGISTER_WEB_AUTH, function (Router $router) {
- $router->get('/dog', fn () => 'dog')->name('say.dog');
-});
-END;
-
- $this->usingThemeFolder(function () use ($functionsContent) {
-
- $functionsFile = theme_path('functions.php');
- file_put_contents($functionsFile, $functionsContent);
-
- $app = $this->createApplication();
- /** @var \Illuminate\Routing\Router $router */
- $router = $app->get('router');
-
- /** @var \Illuminate\Routing\Route $catRoute */
- $catRoute = $router->getRoutes()->getRoutesByName()['say.cat'];
- $this->assertEquals(['web'], $catRoute->middleware());
-
- /** @var \Illuminate\Routing\Route $dogRoute */
- $dogRoute = $router->getRoutes()->getRoutesByName()['say.dog'];
- $this->assertEquals(['web', 'auth'], $dogRoute->middleware());
- });
- }
-
- public function test_add_social_driver()
- {
- Theme::addSocialDriver('catnet', [
- 'client_id' => 'abc123',
- 'client_secret' => 'def456',
- ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
-
- $this->assertEquals('catnet', config('services.catnet.name'));
- $this->assertEquals('abc123', config('services.catnet.client_id'));
- $this->assertEquals(url('/login/service/catnet/callback'), config('services.catnet.redirect'));
-
- $loginResp = $this->get('/login');
- $loginResp->assertSee('login/service/catnet');
- }
-
- public function test_add_social_driver_uses_name_in_config_if_given()
- {
- Theme::addSocialDriver('catnet', [
- 'client_id' => 'abc123',
- 'client_secret' => 'def456',
- 'name' => 'Super Cat Name',
- ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
-
- $this->assertEquals('Super Cat Name', config('services.catnet.name'));
- $loginResp = $this->get('/login');
- $loginResp->assertSee('Super Cat Name');
- }
-
- public function test_add_social_driver_allows_a_configure_for_redirect_callback_to_be_passed()
- {
- Theme::addSocialDriver(
- 'discord',
- [
- 'client_id' => 'abc123',
- 'client_secret' => 'def456',
- 'name' => 'Super Cat Name',
- ],
- 'SocialiteProviders\Discord\DiscordExtendSocialite@handle',
- function ($driver) {
- $driver->with(['donkey' => 'donut']);
- }
- );
-
- $loginResp = $this->get('/login/service/discord');
- $redirect = $loginResp->headers->get('location');
- $this->assertStringContainsString('donkey=donut', $redirect);
- }
-
- public function test_register_command_allows_provided_command_to_be_usable_via_artisan()
- {
- Theme::registerCommand(new MyCustomCommand());
-
- Artisan::call('bookstack:test-custom-command', []);
- $output = Artisan::output();
-
- $this->assertStringContainsString('Command ran!', $output);
- }
-
- public function test_base_body_start_and_end_template_files_can_be_used()
- {
- $bodyStartStr = 'barry-fought-against-the-panther';
- $bodyEndStr = 'barry-lost-his-fight-with-grace';
-
- $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr) {
- $viewDir = theme_path('layouts/parts');
- mkdir($viewDir, 0777, true);
- file_put_contents($viewDir . '/base-body-start.blade.php', $bodyStartStr);
- file_put_contents($viewDir . '/base-body-end.blade.php', $bodyEndStr);
-
- $resp = $this->asEditor()->get('/');
- $resp->assertSee($bodyStartStr);
- $resp->assertSee($bodyEndStr);
- });
- }
-
- public function test_export_body_start_and_end_template_files_can_be_used()
- {
- $bodyStartStr = 'garry-fought-against-the-panther';
- $bodyEndStr = 'garry-lost-his-fight-with-grace';
- $page = $this->entities->page();
-
- $this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr, $page) {
- $viewDir = theme_path('layouts/parts');
- mkdir($viewDir, 0777, true);
- file_put_contents($viewDir . '/export-body-start.blade.php', $bodyStartStr);
- file_put_contents($viewDir . '/export-body-end.blade.php', $bodyEndStr);
-
- $resp = $this->asEditor()->get($page->getUrl('/export/html'));
- $resp->assertSee($bodyStartStr);
- $resp->assertSee($bodyEndStr);
- });
- }
-
- public function test_login_and_register_message_template_files_can_be_used()
- {
- $loginMessage = 'Welcome to this instance, login below you scallywag';
- $registerMessage = 'You want to register? Enter the deets below you numpty';
-
- $this->usingThemeFolder(function (string $folder) use ($loginMessage, $registerMessage) {
- $viewDir = theme_path('auth/parts');
- mkdir($viewDir, 0777, true);
- file_put_contents($viewDir . '/login-message.blade.php', $loginMessage);
- file_put_contents($viewDir . '/register-message.blade.php', $registerMessage);
- $this->setSettings(['registration-enabled' => 'true']);
-
- $this->get('/login')->assertSee($loginMessage);
- $this->get('/register')->assertSee($registerMessage);
- });
- }
-
- public function test_header_links_start_template_file_can_be_used()
- {
- $content = 'This is added text in the header bar';
-
- $this->usingThemeFolder(function (string $folder) use ($content) {
- $viewDir = theme_path('layouts/parts');
- mkdir($viewDir, 0777, true);
- file_put_contents($viewDir . '/header-links-start.blade.php', $content);
- $this->setSettings(['registration-enabled' => 'true']);
-
- $this->get('/login')->assertSee($content);
- });
- }
-
- public function test_custom_settings_category_page_can_be_added_via_view_file()
- {
- $content = 'My SuperCustomSettings';
-
- $this->usingThemeFolder(function (string $folder) use ($content) {
- $viewDir = theme_path('settings/categories');
- mkdir($viewDir, 0777, true);
- file_put_contents($viewDir . '/beans.blade.php', $content);
-
- $this->asAdmin()->get('/settings/beans')->assertSee($content);
- });
- }
-
- public function test_public_folder_contents_accessible_via_route()
- {
- $this->usingThemeFolder(function (string $themeFolderName) {
- $publicDir = theme_path('public');
- mkdir($publicDir, 0777, true);
-
- $text = 'some-text ' . md5(random_bytes(5));
- $css = "body { background-color: tomato !important; }";
- file_put_contents("{$publicDir}/file.txt", $text);
- file_put_contents("{$publicDir}/file.css", $css);
- copy($this->files->testFilePath('test-image.png'), "{$publicDir}/image.png");
-
- $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.txt");
- $resp->assertStreamedContent($text);
- $resp->assertHeader('Content-Type', 'text/plain; charset=utf-8');
- $resp->assertHeader('Cache-Control', 'max-age=86400, private');
-
- $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/image.png");
- $resp->assertHeader('Content-Type', 'image/png');
- $resp->assertHeader('Cache-Control', 'max-age=86400, private');
-
- $resp = $this->asAdmin()->get("/theme/{$themeFolderName}/file.css");
- $resp->assertStreamedContent($css);
- $resp->assertHeader('Content-Type', 'text/css; charset=utf-8');
- $resp->assertHeader('Cache-Control', 'max-age=86400, private');
- });
- }
-
- protected function usingThemeFolder(callable $callback)
- {
- // Create a folder and configure a theme
- $themeFolderName = 'testing_theme_' . str_shuffle(rtrim(base64_encode(time()), '='));
- config()->set('view.theme', $themeFolderName);
- $themeFolderPath = theme_path('');
-
- // Create theme folder and clean it up on application tear-down
- File::makeDirectory($themeFolderPath);
- $this->beforeApplicationDestroyed(fn() => File::deleteDirectory($themeFolderPath));
-
- // Run provided callback with theme env option set
- $this->runWithEnv(['APP_THEME' => $themeFolderName], function () use ($callback, $themeFolderName) {
- call_user_func($callback, $themeFolderName);
- });
- }
-}
-
-class MyCustomCommand extends Command
-{
- protected $signature = 'bookstack:test-custom-command';
-
- public function handle()
- {
- $this->line('Command ran!');
- }
-}
diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php
index 9ed68c8bd..2e9190dfd 100644
--- a/tests/Unit/ConfigTest.php
+++ b/tests/Unit/ConfigTest.php
@@ -122,6 +122,27 @@ class ConfigTest extends TestCase
});
}
+ public function test_app_url_changes_smtp_ehlo_host_on_mailer()
+ {
+ $getLocalDomain = function (): string {
+ /** @var EsmtpTransport $transport */
+ $transport = Mail::mailer('smtp')->getSymfonyTransport();
+ return $transport->getLocalDomain();
+ };
+
+ $this->runWithEnv(['APP_URL' => ''], function () use ($getLocalDomain) {
+ $this->assertEquals('[127.0.0.1]', $getLocalDomain());
+ });
+
+ $this->runWithEnv(['APP_URL' => 'https://example.com/cats/dogs'], function () use ($getLocalDomain) {
+ $this->assertEquals('example.com', $getLocalDomain());
+ });
+
+ $this->runWithEnv(['APP_URL' => 'http://beans.cat.example.com'], function () use ($getLocalDomain) {
+ $this->assertEquals('beans.cat.example.com', $getLocalDomain());
+ });
+ }
+
public function test_non_null_mail_encryption_options_enforce_smtp_scheme()
{
$this->checkEnvConfigResult('MAIL_ENCRYPTION', 'tls', 'mail.mailers.smtp.require_tls', true);