mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-11 03:59:35 +00:00
Merge upstream/master and resolve translation conflict
Merged new Conrad info provider and generic web provider translations from upstream while keeping Update Manager translations.
This commit is contained in:
commit
6b27f3aa14
22 changed files with 1787 additions and 377 deletions
|
|
@ -125,3 +125,25 @@ Classes for Datatables export
|
|||
.export-helper{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
* Table row highlighting tools
|
||||
***********************************************************/
|
||||
|
||||
.row-highlight {
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.20); /* Adds depth */
|
||||
position: relative;
|
||||
z-index: 1; /* Ensures the shadow overlaps other rows */
|
||||
border-left: 5px solid var(--bs-primary); /* Adds a vertical accent bar */
|
||||
}
|
||||
|
||||
@keyframes pulse-highlight {
|
||||
0% { outline: 2px solid transparent; }
|
||||
50% { outline: 2px solid var(--bs-primary); }
|
||||
100% { outline: 2px solid transparent; }
|
||||
}
|
||||
|
||||
.row-pulse {
|
||||
animation: pulse-highlight 1s ease-in-out;
|
||||
animation-iteration-count: 3;
|
||||
}
|
||||
|
|
|
|||
453
composer.lock
generated
453
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -96,6 +96,21 @@ The following providers are currently available and shipped with Part-DB:
|
|||
|
||||
(All trademarks are property of their respective owners. Part-DB is not affiliated with any of the companies.)
|
||||
|
||||
### Generic Web URL Provider
|
||||
The Generic Web URL Provider can extract part information from any webpage that contains structured data in the form of
|
||||
[Schema.org](https://schema.org/) format. Many e-commerce websites use this format to provide detailed product information
|
||||
for search engines and other services. Therefore it allows Part-DB to retrieve rudimentary part information (like name, image and price)
|
||||
from a wide range of websites without the need for a dedicated API integration.
|
||||
To use the Generic Web URL Provider, simply enable it in the information provider settings. No additional configuration
|
||||
is required. Afterwards you can enter any product URL in the search field, and Part-DB will attempt to extract the relevant part information
|
||||
from the webpage.
|
||||
|
||||
Please note that if this provider is enabled, Part-DB will make HTTP requests to external websites to fetch product data, which
|
||||
may have privacy and security implications.
|
||||
|
||||
Following env configuration options are available:
|
||||
* `PROVIDER_GENERIC_WEB_ENABLED`: Set this to `1` to enable the Generic Web URL Provider (optional, default: `0`)
|
||||
|
||||
### Octopart
|
||||
|
||||
The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and get information.
|
||||
|
|
@ -278,6 +293,16 @@ The following env configuration options are available:
|
|||
* `PROVIDER_BUERKLIN_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, default: `EUR`).
|
||||
* `PROVIDER_BUERKLIN_LANGUAGE`: The language you want to get the descriptions in. Possible values: `de` = German, `en` = English. (optional, default: `en`)
|
||||
|
||||
### Conrad
|
||||
|
||||
The conrad provider the [Conrad API](https://developer.conrad.com/) to search for parts and retried their information.
|
||||
To use it you have to request access to the API, however it seems currently your mail address needs to be allowlisted before you can register for an account.
|
||||
The conrad webpages uses the API key in the requests, so you might be able to extract a working API key by listening to browser requests.
|
||||
That method is not officially supported nor encouraged by Part-DB, and might break at any moment.
|
||||
|
||||
The following env configuration options are available:
|
||||
* `PROVIDER_CONRAD_API_KEY`: The API key you got from Conrad (mandatory)
|
||||
|
||||
### Custom provider
|
||||
|
||||
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use App\Form\InfoProviderSystem\PartSearchType;
|
|||
use App\Services\InfoProviderSystem\ExistingPartFinder;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\InfoProviderSystem\Providers\GenericWebProvider;
|
||||
use App\Settings\AppSettings;
|
||||
use App\Settings\InfoProviderSystem\InfoProviderGeneralSettings;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
|
@ -39,6 +40,7 @@ use Psr\Log\LoggerInterface;
|
|||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
|
@ -208,4 +210,58 @@ class InfoProviderController extends AbstractController
|
|||
'update_target' => $update_target
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/from_url', name: 'info_providers_from_url')]
|
||||
public function fromURL(Request $request, GenericWebProvider $provider): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
if (!$provider->isActive()) {
|
||||
$this->addFlash('error', "Generic Web Provider is not active. Please enable it in the provider settings.");
|
||||
return $this->redirectToRoute('info_providers_list');
|
||||
}
|
||||
|
||||
$formBuilder = $this->createFormBuilder();
|
||||
$formBuilder->add('url', UrlType::class, [
|
||||
'label' => 'info_providers.from_url.url.label',
|
||||
'required' => true,
|
||||
]);
|
||||
$formBuilder->add('submit', SubmitType::class, [
|
||||
'label' => 'info_providers.search.submit',
|
||||
]);
|
||||
|
||||
$form = $formBuilder->getForm();
|
||||
$form->handleRequest($request);
|
||||
|
||||
$partDetail = null;
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Try to retrieve the part detail from the given URL
|
||||
$url = $form->get('url')->getData();
|
||||
try {
|
||||
$searchResult = $this->infoRetriever->searchByKeyword(
|
||||
keyword: $url,
|
||||
providers: [$provider]
|
||||
);
|
||||
|
||||
if (count($searchResult) === 0) {
|
||||
$this->addFlash('warning', t('info_providers.from_url.no_part_found'));
|
||||
} else {
|
||||
$searchResult = $searchResult[0];
|
||||
//Redirect to the part creation page with the found part detail
|
||||
return $this->redirectToRoute('info_providers_create_part', [
|
||||
'providerKey' => $searchResult->provider_key,
|
||||
'providerId' => $searchResult->provider_id,
|
||||
]);
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.general_exception', ['%type%' => (new \ReflectionClass($e))->getShortName()]));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('info_providers/from_url/from_url.html.twig', [
|
||||
'form' => $form,
|
||||
'partDetail' => $partDetail,
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ final class PartController extends AbstractController
|
|||
'description_params' => $this->partInfoSettings->extractParamsFromDescription ? $parameterExtractor->extractParameters($part->getDescription()) : [],
|
||||
'comment_params' => $this->partInfoSettings->extractParamsFromNotes ? $parameterExtractor->extractParameters($part->getComment()) : [],
|
||||
'withdraw_add_helper' => $withdrawAddHelper,
|
||||
'highlightLotId' => $request->query->getInt('highlightLot', 0),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
32
src/Exceptions/ProviderIDNotSupportedException.php
Normal file
32
src/Exceptions/ProviderIDNotSupportedException.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class ProviderIDNotSupportedException extends \RuntimeException
|
||||
{
|
||||
public function fromProvider(string $providerKey, string $id): self
|
||||
{
|
||||
return new self(sprintf('The given ID %s is not supported by the provider %s.', $id, $providerKey,));
|
||||
}
|
||||
}
|
||||
|
|
@ -277,8 +277,11 @@ class BOMImporter
|
|||
// Fetch suppliers once for efficiency
|
||||
$suppliers = $this->entityManager->getRepository(\App\Entity\Parts\Supplier::class)->findAll();
|
||||
$supplierSPNKeys = [];
|
||||
$suppliersByName = []; // Map supplier names to supplier objects
|
||||
foreach ($suppliers as $supplier) {
|
||||
$supplierSPNKeys[] = $supplier->getName() . ' SPN';
|
||||
$supplierName = $supplier->getName();
|
||||
$supplierSPNKeys[] = $supplierName . ' SPN';
|
||||
$suppliersByName[$supplierName] = $supplier;
|
||||
}
|
||||
|
||||
foreach ($csv->getRecords() as $offset => $entry) {
|
||||
|
|
@ -356,6 +359,41 @@ class BOMImporter
|
|||
}
|
||||
}
|
||||
|
||||
// Try to link existing part based on supplier part number if no Part-DB ID is given
|
||||
if ($part === null) {
|
||||
// Check all available supplier SPN fields
|
||||
foreach ($suppliersByName as $supplierName => $supplier) {
|
||||
$supplier_spn = null;
|
||||
|
||||
if (isset($mapped_entry[$supplierName . ' SPN']) && !empty(trim($mapped_entry[$supplierName . ' SPN']))) {
|
||||
$supplier_spn = trim($mapped_entry[$supplierName . ' SPN']);
|
||||
}
|
||||
|
||||
if ($supplier_spn !== null) {
|
||||
// Query for orderdetails with matching supplier and SPN
|
||||
$orderdetail = $this->entityManager->getRepository(\App\Entity\PriceInformations\Orderdetail::class)
|
||||
->findOneBy([
|
||||
'supplier' => $supplier,
|
||||
'supplierpartnr' => $supplier_spn,
|
||||
]);
|
||||
|
||||
if ($orderdetail !== null && $orderdetail->getPart() !== null) {
|
||||
$part = $orderdetail->getPart();
|
||||
$name = $part->getName(); // Update name with actual part name
|
||||
|
||||
$this->logger->info('Linked BOM entry to existing part via supplier SPN', [
|
||||
'supplier' => $supplierName,
|
||||
'supplier_spn' => $supplier_spn,
|
||||
'part_id' => $part->getID(),
|
||||
'part_name' => $part->getName(),
|
||||
]);
|
||||
|
||||
break; // Stop searching once a match is found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create unique key for this entry (name + part ID)
|
||||
$entry_key = $name . '|' . ($part ? $part->getID() : 'null');
|
||||
|
||||
|
|
|
|||
320
src/Services/InfoProviderSystem/Providers/ConradProvider.php
Normal file
320
src/Services/InfoProviderSystem/Providers/ConradProvider.php
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Services\InfoProviderSystem\DTOs\FileDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||
use App\Settings\InfoProviderSystem\ConradSettings;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
readonly class ConradProvider implements InfoProviderInterface
|
||||
{
|
||||
|
||||
private const SEARCH_ENDPOINT = '/search/1/v3/facetSearch';
|
||||
public const DISTRIBUTOR_NAME = 'Conrad';
|
||||
|
||||
private HttpClientInterface $httpClient;
|
||||
|
||||
public function __construct( HttpClientInterface $httpClient, private ConradSettings $settings)
|
||||
{
|
||||
//We want everything in JSON
|
||||
$this->httpClient = $httpClient->withOptions([
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getProviderInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'Conrad',
|
||||
'description' => 'Retrieves part information from conrad.de',
|
||||
'url' => 'https://www.conrad.de/',
|
||||
'disabled_help' => 'Set API key in settings',
|
||||
'settings_class' => ConradSettings::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getProviderKey(): string
|
||||
{
|
||||
return 'conrad';
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return !empty($this->settings->apiKey);
|
||||
}
|
||||
|
||||
private function getProductUrl(string $productId): string
|
||||
{
|
||||
return 'https://' . $this->settings->shopID->getDomain() . '/' . $this->settings->shopID->getLanguage() . '/p/' . $productId;
|
||||
}
|
||||
|
||||
private function getFootprintFromTechnicalDetails(array $technicalDetails): ?string
|
||||
{
|
||||
foreach ($technicalDetails as $detail) {
|
||||
if ($detail['name'] === 'ATT_LOV_HOUSING_SEMICONDUCTORS') {
|
||||
return $detail['values'][0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function searchByKeyword(string $keyword): array
|
||||
{
|
||||
$url = $this->settings->shopID->getAPIRoot() . self::SEARCH_ENDPOINT . '/'
|
||||
. $this->settings->shopID->getDomainEnd() . '/' . $this->settings->shopID->getLanguage()
|
||||
. '/' . $this->settings->shopID->getCustomerType();
|
||||
|
||||
$response = $this->httpClient->request('POST', $url, [
|
||||
'query' => [
|
||||
'apikey' => $this->settings->apiKey,
|
||||
],
|
||||
'json' => [
|
||||
'query' => $keyword,
|
||||
'size' => 50,
|
||||
'sort' => [["field"=>"_score","order"=>"desc"]],
|
||||
],
|
||||
]);
|
||||
|
||||
$out = [];
|
||||
$results = $response->toArray();
|
||||
|
||||
foreach($results['hits'] as $result) {
|
||||
|
||||
$out[] = new SearchResultDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $result['productId'],
|
||||
name: $result['manufacturerId'] ?? $result['productId'],
|
||||
description: $result['title'] ?? '',
|
||||
manufacturer: $result['brand']['name'] ?? null,
|
||||
mpn: $result['manufacturerId'] ?? null,
|
||||
preview_image_url: $result['image'] ?? null,
|
||||
provider_url: $this->getProductUrl($result['productId']),
|
||||
footprint: $this->getFootprintFromTechnicalDetails($result['technicalDetails'] ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function getFootprintFromTechnicalAttributes(array $technicalDetails): ?string
|
||||
{
|
||||
foreach ($technicalDetails as $detail) {
|
||||
if ($detail['attributeID'] === 'ATT.LOV.HOUSING_SEMICONDUCTORS') {
|
||||
return $detail['values'][0]['value'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $technicalAttributes
|
||||
* @return array<ParameterDTO>
|
||||
*/
|
||||
private function technicalAttributesToParameters(array $technicalAttributes): array
|
||||
{
|
||||
return array_map(static function (array $p) {
|
||||
if (count($p['values']) === 1) { //Single value attribute
|
||||
if (array_key_exists('unit', $p['values'][0])) {
|
||||
return ParameterDTO::parseValueField( //With unit
|
||||
name: $p['attributeName'],
|
||||
value: $p['values'][0]['value'],
|
||||
unit: $p['values'][0]['unit']['name'],
|
||||
);
|
||||
}
|
||||
|
||||
return ParameterDTO::parseValueIncludingUnit(
|
||||
name: $p['attributeName'],
|
||||
value: $p['values'][0]['value'],
|
||||
);
|
||||
}
|
||||
|
||||
if (count($p['values']) === 2) { //Multi value attribute (e.g. min/max)
|
||||
$value = $p['values'][0]['value'] ?? null;
|
||||
$value2 = $p['values'][1]['value'] ?? null;
|
||||
$unit = $p['values'][0]['unit']['name'] ?? '';
|
||||
$unit2 = $p['values'][1]['unit']['name'] ?? '';
|
||||
if ($unit === $unit2 && is_numeric($value) && is_numeric($value2)) {
|
||||
if (array_key_exists('unit', $p['values'][0])) { //With unit
|
||||
return new ParameterDTO(
|
||||
name: $p['attributeName'],
|
||||
value_min: (float)$value,
|
||||
value_max: (float)$value2,
|
||||
unit: $unit,
|
||||
);
|
||||
}
|
||||
|
||||
return new ParameterDTO(
|
||||
name: $p['attributeName'],
|
||||
value_min: (float)$value,
|
||||
value_max: (float)$value2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// fallback implementation
|
||||
$values = implode(", ", array_map(fn($q) =>
|
||||
array_key_exists('unit', $q) ? $q['value']." ". ($q['unit']['name'] ?? $q['unit']) : $q['value']
|
||||
, $p['values']));
|
||||
return ParameterDTO::parseValueIncludingUnit(
|
||||
name: $p['attributeName'],
|
||||
value: $values,
|
||||
);
|
||||
}, $technicalAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $productMedia
|
||||
* @return array<FileDTO>
|
||||
*/
|
||||
public function productMediaToDatasheets(array $productMedia): array
|
||||
{
|
||||
$files = [];
|
||||
foreach ($productMedia['manuals'] as $manual) {
|
||||
//Filter out unwanted languages
|
||||
if (!empty($this->settings->attachmentLanguageFilter) && !in_array($manual['language'], $this->settings->attachmentLanguageFilter, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$files[] = new FileDTO($manual['fullUrl'], $manual['title'] . ' (' . $manual['language'] . ')');
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Queries prices for a given product ID. It makes a POST request to the Conrad API
|
||||
* @param string $productId
|
||||
* @return PurchaseInfoDTO
|
||||
*/
|
||||
private function queryPrices(string $productId): PurchaseInfoDTO
|
||||
{
|
||||
$priceQueryURL = $this->settings->shopID->getAPIRoot() . '/price-availability/4/'
|
||||
. $this->settings->shopID->getShopID() . '/facade';
|
||||
|
||||
$response = $this->httpClient->request('POST', $priceQueryURL, [
|
||||
'query' => [
|
||||
'apikey' => $this->settings->apiKey,
|
||||
'overrideCalculationSchema' => $this->settings->includeVAT ? 'GROSS' : 'NET'
|
||||
],
|
||||
'json' => [
|
||||
'ns:inputArticleItemList' => [
|
||||
"#namespaces" => [
|
||||
"ns" => "http://www.conrad.de/ccp/basit/service/article/priceandavailabilityservice/api"
|
||||
],
|
||||
'articles' => [
|
||||
[
|
||||
"articleID" => $productId,
|
||||
"calculatePrice" => true,
|
||||
"checkAvailability" => true,
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$result = $response->toArray();
|
||||
|
||||
$priceInfo = $result['priceAndAvailabilityFacadeResponse']['priceAndAvailability']['price'] ?? [];
|
||||
$price = $priceInfo['price'] ?? "0.0";
|
||||
$currency = $priceInfo['currency'] ?? "EUR";
|
||||
$includesVat = !$priceInfo['isGrossAmount'] || $priceInfo['isGrossAmount'] === "true";
|
||||
$minOrderAmount = $result['priceAndAvailabilityFacadeResponse']['priceAndAvailability']['availabilityStatus']['minimumOrderQuantity'] ?? 1;
|
||||
|
||||
$prices = [];
|
||||
foreach ($priceInfo['priceScale'] ?? [] as $priceScale) {
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: max($priceScale['scaleFrom'], $minOrderAmount),
|
||||
price: (string)$priceScale['pricePerUnit'],
|
||||
currency_iso_code: $currency,
|
||||
includes_tax: $includesVat
|
||||
);
|
||||
}
|
||||
if (empty($prices)) { //Fallback if no price scales are defined
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: $minOrderAmount,
|
||||
price: (string)$price,
|
||||
currency_iso_code: $currency,
|
||||
includes_tax: $includesVat
|
||||
);
|
||||
}
|
||||
|
||||
return new PurchaseInfoDTO(
|
||||
distributor_name: self::DISTRIBUTOR_NAME,
|
||||
order_number: $productId,
|
||||
prices: $prices,
|
||||
product_url: $this->getProductUrl($productId)
|
||||
);
|
||||
}
|
||||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
$productInfoURL = $this->settings->shopID->getAPIRoot() . '/product/1/service/' . $this->settings->shopID->getShopID()
|
||||
. '/product/' . $id;
|
||||
|
||||
$response = $this->httpClient->request('GET', $productInfoURL, [
|
||||
'query' => [
|
||||
'apikey' => $this->settings->apiKey,
|
||||
]
|
||||
]);
|
||||
|
||||
$data = $response->toArray();
|
||||
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $data['shortProductNumber'],
|
||||
name: $data['productFullInformation']['manufacturer']['name'] ?? $data['productFullInformation']['manufacturer']['id'] ?? $data['shortProductNumber'],
|
||||
description: $data['productShortInformation']['title'] ?? '',
|
||||
category: $data['productShortInformation']['articleGroupName'] ?? null,
|
||||
manufacturer: $data['brand']['displayName'] !== null ? preg_replace("/[\u{2122}\u{00ae}]/", "", $data['brand']['displayName']) : null, //Replace ™ and ® symbols
|
||||
mpn: $data['productFullInformation']['manufacturer']['id'] ?? null,
|
||||
preview_image_url: $data['productShortInformation']['mainImage']['imageUrl'] ?? null,
|
||||
provider_url: $this->getProductUrl($data['shortProductNumber']),
|
||||
footprint: $this->getFootprintFromTechnicalAttributes($data['productFullInformation']['technicalAttributes'] ?? []),
|
||||
notes: $data['productFullInformation']['description'] ?? null,
|
||||
datasheets: $this->productMediaToDatasheets($data['productMedia'] ?? []),
|
||||
parameters: $this->technicalAttributesToParameters($data['productFullInformation']['technicalAttributes'] ?? []),
|
||||
vendor_infos: [$this->queryPrices($data['shortProductNumber'])]
|
||||
);
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return [
|
||||
ProviderCapabilities::BASIC,
|
||||
ProviderCapabilities::PICTURE,
|
||||
ProviderCapabilities::DATASHEET,
|
||||
ProviderCapabilities::PRICE,
|
||||
];
|
||||
}
|
||||
}
|
||||
336
src/Services/InfoProviderSystem/Providers/GenericWebProvider.php
Normal file
336
src/Services/InfoProviderSystem/Providers/GenericWebProvider.php
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\InfoProviderSystem\Providers;
|
||||
|
||||
use App\Exceptions\ProviderIDNotSupportedException;
|
||||
use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||
use App\Settings\InfoProviderSystem\GenericWebProviderSettings;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Price;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class GenericWebProvider implements InfoProviderInterface
|
||||
{
|
||||
|
||||
public const DISTRIBUTOR_NAME = 'Website';
|
||||
|
||||
private readonly HttpClientInterface $httpClient;
|
||||
|
||||
public function __construct(HttpClientInterface $httpClient, private readonly GenericWebProviderSettings $settings)
|
||||
{
|
||||
$this->httpClient = $httpClient->withOptions(
|
||||
[
|
||||
'headers' => [
|
||||
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
|
||||
],
|
||||
'timeout' => 15,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getProviderInfo(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'Generic Web URL',
|
||||
'description' => 'Tries to extract a part from a given product webpage URL using common metadata standards like JSON-LD and OpenGraph.',
|
||||
//'url' => 'https://example.com',
|
||||
'disabled_help' => 'Enable in settings to use this provider',
|
||||
'settings_class' => GenericWebProviderSettings::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getProviderKey(): string
|
||||
{
|
||||
return 'generic_web';
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->settings->enabled;
|
||||
}
|
||||
|
||||
public function searchByKeyword(string $keyword): array
|
||||
{
|
||||
try {
|
||||
return [
|
||||
$this->getDetails($keyword)
|
||||
]; } catch (ProviderIDNotSupportedException $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function extractShopName(string $url): string
|
||||
{
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
if ($host === false || $host === null) {
|
||||
return self::DISTRIBUTOR_NAME;
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
|
||||
private function productJsonLdToPart(array $jsonLd, string $url, Crawler $dom): PartDetailDTO
|
||||
{
|
||||
$notes = $jsonLd['description'] ?? "";
|
||||
if (isset($jsonLd['disambiguatingDescription'])) {
|
||||
if (!empty($notes)) {
|
||||
$notes .= "\n\n";
|
||||
}
|
||||
$notes .= $jsonLd['disambiguatingDescription'];
|
||||
}
|
||||
|
||||
$vendor_infos = null;
|
||||
if (isset($jsonLd['offers'])) {
|
||||
|
||||
if (array_is_list($jsonLd['offers'])) {
|
||||
$offer = $jsonLd['offers'][0];
|
||||
} else {
|
||||
$offer = $jsonLd['offers'];
|
||||
}
|
||||
|
||||
//Make $jsonLd['url'] absolute if it's relative
|
||||
if (isset($jsonLd['url']) && parse_url($jsonLd['url'], PHP_URL_SCHEME) === null) {
|
||||
$parsedUrl = parse_url($url);
|
||||
$scheme = $parsedUrl['scheme'] ?? 'https';
|
||||
$host = $parsedUrl['host'] ?? '';
|
||||
$jsonLd['url'] = $scheme.'://'.$host.$jsonLd['url'];
|
||||
}
|
||||
|
||||
$prices = [];
|
||||
if (isset($offer['price'])) {
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: 1,
|
||||
price: (string) $offer['price'],
|
||||
currency_iso_code: $offer['priceCurrency'] ?? null
|
||||
);
|
||||
} else if (isset($offer['offers']) && array_is_list($offer['offers'])) {
|
||||
//Some sites nest offers
|
||||
foreach ($offer['offers'] as $subOffer) {
|
||||
if (isset($subOffer['price'])) {
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: 1,
|
||||
price: (string) $subOffer['price'],
|
||||
currency_iso_code: $subOffer['priceCurrency'] ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$vendor_infos = [new PurchaseInfoDTO(
|
||||
distributor_name: $this->extractShopName($url),
|
||||
order_number: (string) ($jsonLd['sku'] ?? $jsonLd['@id'] ?? $jsonLd['gtin'] ?? 'Unknown'),
|
||||
prices: $prices,
|
||||
product_url: $jsonLd['url'] ?? $url,
|
||||
)];
|
||||
}
|
||||
|
||||
$image = null;
|
||||
if (isset($jsonLd['image'])) {
|
||||
if (is_array($jsonLd['image'])) {
|
||||
if (array_is_list($jsonLd['image'])) {
|
||||
$image = $jsonLd['image'][0] ?? null;
|
||||
}
|
||||
} elseif (is_string($jsonLd['image'])) {
|
||||
$image = $jsonLd['image'];
|
||||
}
|
||||
}
|
||||
//If image is an object with @type ImageObject, extract the url
|
||||
if (is_array($image) && isset($image['@type']) && $image['@type'] === 'ImageObject') {
|
||||
$image = $image['contentUrl'] ?? $image['url'] ?? null;
|
||||
}
|
||||
|
||||
//Try to extract parameters from additionalProperty
|
||||
$parameters = [];
|
||||
if (isset($jsonLd['additionalProperty']) && array_is_list($jsonLd['additionalProperty'])) {
|
||||
foreach ($jsonLd['additionalProperty'] as $property) { //TODO: Handle minValue and maxValue
|
||||
if (isset ($property['unitText'])) {
|
||||
$parameters[] = ParameterDTO::parseValueField(
|
||||
name: $property['name'] ?? 'Unknown',
|
||||
value: $property['value'] ?? '',
|
||||
unit: $property['unitText']
|
||||
);
|
||||
} else {
|
||||
$parameters[] = ParameterDTO::parseValueIncludingUnit(
|
||||
name: $property['name'] ?? 'Unknown',
|
||||
value: $property['value'] ?? ''
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $url,
|
||||
name: $jsonLd ['name'] ?? 'Unknown Name',
|
||||
description: $this->getMetaContent($dom, 'og:description') ?? $this->getMetaContent($dom, 'description') ?? '',
|
||||
category: isset($jsonLd['category']) && is_string($jsonLd['category']) ? $jsonLd['category'] : null,
|
||||
manufacturer: $jsonLd['manufacturer']['name'] ?? $jsonLd['brand']['name'] ?? null,
|
||||
mpn: $jsonLd['mpn'] ?? null,
|
||||
preview_image_url: $image,
|
||||
provider_url: $url,
|
||||
notes: $notes,
|
||||
parameters: $parameters,
|
||||
vendor_infos: $vendor_infos,
|
||||
mass: isset($jsonLd['weight']['value']) ? (float)$jsonLd['weight']['value'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes JSON in a forgiving way, trying to fix common issues.
|
||||
* @param string $json
|
||||
* @return array
|
||||
* @throws \JsonException
|
||||
*/
|
||||
private function json_decode_forgiving(string $json): array
|
||||
{
|
||||
//Sanitize common issues
|
||||
$json = preg_replace("/[\r\n]+/", " ", $json);
|
||||
return json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
private function getMetaContent(Crawler $dom, string $name): ?string
|
||||
{
|
||||
$meta = $dom->filter('meta[property="'.$name.'"]');
|
||||
if ($meta->count() > 0) {
|
||||
return $meta->attr('content');
|
||||
}
|
||||
|
||||
//Try name attribute
|
||||
$meta = $dom->filter('meta[name="'.$name.'"]');
|
||||
if ($meta->count() > 0) {
|
||||
return $meta->attr('content');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDetails(string $id): PartDetailDTO
|
||||
{
|
||||
//Add scheme if missing
|
||||
if (!preg_match('/^https?:\/\//', $id)) {
|
||||
//Remove any leading slashes
|
||||
$id = ltrim($id, '/');
|
||||
|
||||
$id = 'https://'.$id;
|
||||
}
|
||||
|
||||
$url = $id;
|
||||
|
||||
//If this is not a valid URL with host, domain and path, throw an exception
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) === false ||
|
||||
parse_url($url, PHP_URL_HOST) === null ||
|
||||
parse_url($url, PHP_URL_PATH) === null) {
|
||||
throw new ProviderIDNotSupportedException("The given ID is not a valid URL: ".$id);
|
||||
}
|
||||
|
||||
//Try to get the webpage content
|
||||
$response = $this->httpClient->request('GET', $url);
|
||||
$content = $response->getContent();
|
||||
|
||||
$dom = new Crawler($content);
|
||||
|
||||
//Try to determine a canonical URL
|
||||
$canonicalURL = $url;
|
||||
if ($dom->filter('link[rel="canonical"]')->count() > 0) {
|
||||
$canonicalURL = $dom->filter('link[rel="canonical"]')->attr('href');
|
||||
} else if ($dom->filter('meta[property="og:url"]')->count() > 0) {
|
||||
$canonicalURL = $dom->filter('meta[property="og:url"]')->attr('content');
|
||||
}
|
||||
|
||||
//If the canonical URL is relative, make it absolute
|
||||
if (parse_url($canonicalURL, PHP_URL_SCHEME) === null) {
|
||||
$parsedUrl = parse_url($url);
|
||||
$scheme = $parsedUrl['scheme'] ?? 'https';
|
||||
$host = $parsedUrl['host'] ?? '';
|
||||
$canonicalURL = $scheme.'://'.$host.$canonicalURL;
|
||||
}
|
||||
|
||||
//Try to find json-ld data in the head
|
||||
$jsonLdNodes = $dom->filter('script[type="application/ld+json"]');
|
||||
foreach ($jsonLdNodes as $node) {
|
||||
$jsonLd = $this->json_decode_forgiving($node->textContent);
|
||||
//If the content of json-ld is an array, try to find a product inside
|
||||
if (!array_is_list($jsonLd)) {
|
||||
$jsonLd = [$jsonLd];
|
||||
}
|
||||
foreach ($jsonLd as $item) {
|
||||
if (isset($item['@type']) && $item['@type'] === 'Product') {
|
||||
return $this->productJsonLdToPart($item, $canonicalURL, $dom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If no JSON-LD data is found, try to extract basic data from meta tags
|
||||
$pageTitle = $dom->filter('title')->count() > 0 ? $dom->filter('title')->text() : 'Unknown';
|
||||
|
||||
$prices = [];
|
||||
if ($price = $this->getMetaContent($dom, 'product:price:amount')) {
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: 1,
|
||||
price: $price,
|
||||
currency_iso_code: $this->getMetaContent($dom, 'product:price:currency'),
|
||||
);
|
||||
} else {
|
||||
//Amazon fallback
|
||||
$amazonAmount = $dom->filter('input[type="hidden"][name*="amount"]');
|
||||
if ($amazonAmount->count() > 0) {
|
||||
$prices[] = new PriceDTO(
|
||||
minimum_discount_amount: 1,
|
||||
price: $amazonAmount->first()->attr('value'),
|
||||
currency_iso_code: $dom->filter('input[type="hidden"][name*="currencyCode"]')->first()->attr('value'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$vendor_infos = [new PurchaseInfoDTO(
|
||||
distributor_name: $this->extractShopName($canonicalURL),
|
||||
order_number: 'Unknown',
|
||||
prices: $prices,
|
||||
product_url: $canonicalURL,
|
||||
)];
|
||||
|
||||
return new PartDetailDTO(
|
||||
provider_key: $this->getProviderKey(),
|
||||
provider_id: $canonicalURL,
|
||||
name: $this->getMetaContent($dom, 'og:title') ?? $pageTitle,
|
||||
description: $this->getMetaContent($dom, 'og:description') ?? $this->getMetaContent($dom, 'description') ?? '',
|
||||
manufacturer: $this->getMetaContent($dom, 'product:brand'),
|
||||
preview_image_url: $this->getMetaContent($dom, 'og:image'),
|
||||
provider_url: $canonicalURL,
|
||||
vendor_infos: $vendor_infos,
|
||||
);
|
||||
}
|
||||
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return [
|
||||
ProviderCapabilities::BASIC,
|
||||
ProviderCapabilities::PICTURE,
|
||||
ProviderCapabilities::PRICE
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ final class BarcodeRedirector
|
|||
throw new EntityNotFoundException();
|
||||
}
|
||||
|
||||
return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]);
|
||||
return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID(), 'highlightLot' => $lot->getID()]);
|
||||
|
||||
case LabelSupportedElement::STORELOCATION:
|
||||
return $this->urlGenerator->generate('part_list_store_location', ['id' => $barcodeScan->target_id]);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ use App\Entity\UserSystem\User;
|
|||
use App\Helpers\Trees\TreeViewNode;
|
||||
use App\Services\Cache\UserCacheKeyGenerator;
|
||||
use App\Services\ElementTypeNameGenerator;
|
||||
use App\Services\InfoProviderSystem\Providers\GenericWebProvider;
|
||||
use App\Settings\InfoProviderSystem\GenericWebProviderSettings;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
|
@ -58,6 +60,7 @@ class ToolsTreeBuilder
|
|||
protected UserCacheKeyGenerator $keyGenerator,
|
||||
protected Security $security,
|
||||
private readonly ElementTypeNameGenerator $elementTypeNameGenerator,
|
||||
private readonly GenericWebProviderSettings $genericWebProviderSettings
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -147,6 +150,13 @@ class ToolsTreeBuilder
|
|||
$this->urlGenerator->generate('info_providers_search')
|
||||
))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down');
|
||||
|
||||
if ($this->genericWebProviderSettings->enabled) {
|
||||
$nodes[] = (new TreeViewNode(
|
||||
$this->translator->trans('info_providers.from_url.title'),
|
||||
$this->urlGenerator->generate('info_providers_from_url')
|
||||
))->setIcon('fa-treeview fa-fw fa-solid fa-book-atlas');
|
||||
}
|
||||
|
||||
$nodes[] = (new TreeViewNode(
|
||||
$this->translator->trans('info_providers.bulk_import.manage_jobs'),
|
||||
$this->urlGenerator->generate('bulk_info_provider_manage')
|
||||
|
|
|
|||
77
src/Settings/InfoProviderSystem/ConradSettings.php
Normal file
77
src/Settings/InfoProviderSystem/ConradSettings.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Settings\InfoProviderSystem;
|
||||
|
||||
use App\Form\Type\APIKeyType;
|
||||
use App\Settings\SettingsIcon;
|
||||
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
|
||||
use Jbtronics\SettingsBundle\ParameterTypes\ArrayType;
|
||||
use Jbtronics\SettingsBundle\ParameterTypes\StringType;
|
||||
use Jbtronics\SettingsBundle\Settings\Settings;
|
||||
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
|
||||
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CountryType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
|
||||
use Symfony\Component\Translation\TranslatableMessage as TM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[Settings(label: new TM("settings.ips.conrad"))]
|
||||
#[SettingsIcon("fa-plug")]
|
||||
class ConradSettings
|
||||
{
|
||||
use SettingsTrait;
|
||||
|
||||
#[SettingsParameter(label: new TM("settings.ips.element14.apiKey"),
|
||||
formType: APIKeyType::class,
|
||||
formOptions: ["help_html" => true], envVar: "PROVIDER_CONRAD_API_KEY", envVarMode: EnvVarMode::OVERWRITE)]
|
||||
public ?string $apiKey = null;
|
||||
|
||||
#[SettingsParameter(label: new TM("settings.ips.conrad.shopID"),
|
||||
description: new TM("settings.ips.conrad.shopID.description"),
|
||||
formType: EnumType::class,
|
||||
formOptions: ['class' => ConradShopIDs::class],
|
||||
)]
|
||||
public ConradShopIDs $shopID = ConradShopIDs::COM_B2B;
|
||||
|
||||
#[SettingsParameter(label: new TM("settings.ips.reichelt.include_vat"))]
|
||||
public bool $includeVAT = true;
|
||||
|
||||
/**
|
||||
* @var array|string[] Only attachments in these languages will be downloaded (ISO 639-1 codes)
|
||||
*/
|
||||
#[Assert\Unique()]
|
||||
#[Assert\All([new Assert\Language()])]
|
||||
#[SettingsParameter(type: ArrayType::class,
|
||||
label: new TM("settings.ips.conrad.attachment_language_filter"), description: new TM("settings.ips.conrad.attachment_language_filter.description"),
|
||||
options: ['type' => StringType::class],
|
||||
formType: LanguageType::class,
|
||||
formOptions: [
|
||||
'multiple' => true,
|
||||
'preferred_choices' => ['en', 'de', 'fr', 'it', 'cs', 'da', 'nl', 'hu', 'hr', 'sk', 'pl']
|
||||
],
|
||||
)]
|
||||
public array $attachmentLanguageFilter = ['en'];
|
||||
}
|
||||
167
src/Settings/InfoProviderSystem/ConradShopIDs.php
Normal file
167
src/Settings/InfoProviderSystem/ConradShopIDs.php
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Settings\InfoProviderSystem;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
enum ConradShopIDs: string implements TranslatableInterface
|
||||
{
|
||||
case COM_B2B = 'HP_COM_B2B';
|
||||
case DE_B2B = 'CQ_DE_B2B';
|
||||
case DE_B2C = 'CQ_DE_B2C';
|
||||
case AT_B2C = 'CQ_AT_B2C';
|
||||
case CH_B2C_DE = 'CQ_CH_B2C_DE';
|
||||
case CH_B2C_FR = 'CQ_CH_B2C_FR';
|
||||
case SE_B2B = 'HP_SE_B2B';
|
||||
case HU_B2C = 'CQ_HU_B2C';
|
||||
case CZ_B2B = 'HP_CZ_B2B';
|
||||
case SI_B2B = 'HP_SI_B2B';
|
||||
case SK_B2B = 'HP_SK_B2B';
|
||||
case BE_B2B = 'HP_BE_B2B';
|
||||
case PL_B2B = 'HP_PL_B2B';
|
||||
case NL_B2B = 'CQ_NL_B2B';
|
||||
case NL_B2C = 'CQ_NL_B2C';
|
||||
case DK_B2B = 'HP_DK_B2B';
|
||||
case IT_B2B = 'HP_IT_B2B';
|
||||
|
||||
case FR_B2B = 'HP_FR_B2B';
|
||||
case AT_B2B = 'CQ_AT_B2B';
|
||||
case HR_B2B = 'HP_HR_B2B';
|
||||
|
||||
|
||||
public function trans(TranslatorInterface $translator, ?string $locale = null): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DE_B2B => "conrad.de (B2B)",
|
||||
self::AT_B2C => "conrad.at (B2C)",
|
||||
self::CH_B2C_DE => "conrad.ch DE (B2C)",
|
||||
self::CH_B2C_FR => "conrad.ch FR (B2C)",
|
||||
self::SE_B2B => "conrad.se (B2B)",
|
||||
self::HU_B2C => "conrad.hu (B2C)",
|
||||
self::CZ_B2B => "conrad.cz (B2B)",
|
||||
self::SI_B2B => "conrad.si (B2B)",
|
||||
self::SK_B2B => "conrad.sk (B2B)",
|
||||
self::BE_B2B => "conrad.be (B2B)",
|
||||
self::DE_B2C => "conrad.de (B2C)",
|
||||
self::PL_B2B => "conrad.pl (B2B)",
|
||||
self::NL_B2B => "conrad.nl (B2B)",
|
||||
self::DK_B2B => "conradelektronik.dk (B2B)",
|
||||
self::IT_B2B => "conrad.it (B2B)",
|
||||
self::NL_B2C => "conrad.nl (B2C)",
|
||||
self::FR_B2B => "conrad.fr (B2B)",
|
||||
self::COM_B2B => "conrad.com (B2B)",
|
||||
self::AT_B2B => "conrad.at (B2B)",
|
||||
self::HR_B2B => "conrad.hr (B2B)",
|
||||
};
|
||||
}
|
||||
|
||||
public function getDomain(): string
|
||||
{
|
||||
if ($this === self::DK_B2B) {
|
||||
return 'conradelektronik.dk';
|
||||
}
|
||||
|
||||
return 'conrad.' . $this->getDomainEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the API root URL for this shop ID. e.g. https://api.conrad.de
|
||||
* @return string
|
||||
*/
|
||||
public function getAPIRoot(): string
|
||||
{
|
||||
return 'https://api.' . $this->getDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shop ID value used in the API requests. e.g. 'CQ_DE_B2B'
|
||||
* @return string
|
||||
*/
|
||||
public function getShopID(): string
|
||||
{
|
||||
if ($this === self::CH_B2C_FR || $this === self::CH_B2C_DE) {
|
||||
return 'CQ_CH_B2C';
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getDomainEnd(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DE_B2B, self::DE_B2C => 'de',
|
||||
self::AT_B2B, self::AT_B2C => 'at',
|
||||
self::CH_B2C_DE => 'ch', self::CH_B2C_FR => 'ch',
|
||||
self::SE_B2B => 'se',
|
||||
self::HU_B2C => 'hu',
|
||||
self::CZ_B2B => 'cz',
|
||||
self::SI_B2B => 'si',
|
||||
self::SK_B2B => 'sk',
|
||||
self::BE_B2B => 'be',
|
||||
self::PL_B2B => 'pl',
|
||||
self::NL_B2B, self::NL_B2C => 'nl',
|
||||
self::DK_B2B => 'dk',
|
||||
self::IT_B2B => 'it',
|
||||
self::FR_B2B => 'fr',
|
||||
self::COM_B2B => 'com',
|
||||
self::HR_B2B => 'hr',
|
||||
};
|
||||
}
|
||||
|
||||
public function getLanguage(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DE_B2B, self::DE_B2C, self::AT_B2B, self::AT_B2C => 'de',
|
||||
self::CH_B2C_DE => 'de', self::CH_B2C_FR => 'fr',
|
||||
self::SE_B2B => 'sv',
|
||||
self::HU_B2C => 'hu',
|
||||
self::CZ_B2B => 'cs',
|
||||
self::SI_B2B => 'sl',
|
||||
self::SK_B2B => 'sk',
|
||||
self::BE_B2B => 'nl',
|
||||
self::PL_B2B => 'pl',
|
||||
self::NL_B2B, self::NL_B2C => 'nl',
|
||||
self::DK_B2B => 'da',
|
||||
self::IT_B2B => 'it',
|
||||
self::FR_B2B => 'fr',
|
||||
self::COM_B2B => 'en',
|
||||
self::HR_B2B => 'hr',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the customer type for this shop ID. e.g. 'b2b' or 'b2c'
|
||||
* @return string 'b2b' or 'b2c'
|
||||
*/
|
||||
public function getCustomerType(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DE_B2B, self::AT_B2B, self::SE_B2B, self::CZ_B2B, self::SI_B2B,
|
||||
self::SK_B2B, self::BE_B2B, self::PL_B2B, self::NL_B2B, self::DK_B2B,
|
||||
self::IT_B2B, self::FR_B2B, self::COM_B2B, self::HR_B2B => 'b2b',
|
||||
self::DE_B2C, self::AT_B2C, self::CH_B2C_DE, self::CH_B2C_FR, self::HU_B2C, self::NL_B2C => 'b2c',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Settings\InfoProviderSystem;
|
||||
|
||||
use App\Settings\SettingsIcon;
|
||||
use Jbtronics\SettingsBundle\Metadata\EnvVarMode;
|
||||
use Jbtronics\SettingsBundle\Settings\Settings;
|
||||
use Jbtronics\SettingsBundle\Settings\SettingsParameter;
|
||||
use Jbtronics\SettingsBundle\Settings\SettingsTrait;
|
||||
use Symfony\Component\Translation\TranslatableMessage as TM;
|
||||
|
||||
#[Settings(name: "generic_web_provider", label: new TM("settings.ips.generic_web_provider"), description: new TM("settings.ips.generic_web_provider.description"))]
|
||||
#[SettingsIcon("fa-plug")]
|
||||
class GenericWebProviderSettings
|
||||
{
|
||||
use SettingsTrait;
|
||||
|
||||
#[SettingsParameter(label: new TM("settings.ips.lcsc.enabled"), description: new TM("settings.ips.generic_web_provider.enabled.help"),
|
||||
envVar: "bool:PROVIDER_GENERIC_WEB_ENABLED", envVarMode: EnvVarMode::OVERWRITE
|
||||
)]
|
||||
public bool $enabled = false;
|
||||
}
|
||||
|
|
@ -37,6 +37,9 @@ class InfoProviderSettings
|
|||
#[EmbeddedSettings]
|
||||
public ?InfoProviderGeneralSettings $general = null;
|
||||
|
||||
#[EmbeddedSettings]
|
||||
public ?GenericWebProviderSettings $genericWebProvider = null;
|
||||
|
||||
#[EmbeddedSettings]
|
||||
public ?DigikeySettings $digikey = null;
|
||||
|
||||
|
|
@ -66,4 +69,7 @@ class InfoProviderSettings
|
|||
|
||||
#[EmbeddedSettings]
|
||||
public ?BuerklinSettings $buerklin = null;
|
||||
|
||||
#[EmbeddedSettings]
|
||||
public ?ConradSettings $conrad = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</button>
|
||||
{% if is_granted("@tools.label_scanner") %}
|
||||
<a href="{{ path('scan_dialog') }}" class="navbar-toggler nav-link ms-3">
|
||||
<i class="fas fa-camera-retro fa-fw"></i>
|
||||
<i class="fas fa-camera-retro fa-fw"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -52,6 +52,14 @@
|
|||
{% trans %}info_providers.search.title{% endtrans %}
|
||||
</a>
|
||||
</li>
|
||||
{% if settings_instance('generic_web_provider').enabled %}
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ path('info_providers_from_url') }}">
|
||||
<i class="fa-fw fa-solid fa-book-atlas"></i>
|
||||
{% trans %}info_providers.from_url.title{% endtrans %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('@parts.import') %}
|
||||
|
|
@ -69,7 +77,7 @@
|
|||
{% if is_granted('@parts.read') %}
|
||||
{{ search.search_form("navbar") }}
|
||||
|
||||
{# {% include "_navbar_search.html.twig" %} #}
|
||||
{# {% include "_navbar_search.html.twig" %} #}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
|
|
|||
21
templates/info_providers/from_url/from_url.html.twig
Normal file
21
templates/info_providers/from_url/from_url.html.twig
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% import "info_providers/providers.macro.html.twig" as providers_macro %}
|
||||
{% import "helper.twig" as helper %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}info_providers.from_url.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-book-atlas"></i> {% trans %}info_providers.from_url.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
<p class="text-muted offset-3">{% trans %}info_providers.from_url.help{% endtrans %}</p>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.url) }}
|
||||
{{ form_row(form.submit) }}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
{% block card_content %}
|
||||
<div class="offset-sm-3">
|
||||
<h3>
|
||||
{% if info_provider_info.url %}
|
||||
{% if info_provider_info.url is defined %}
|
||||
<a href="{{ info_provider_info.url }}" class="link-external" target="_blank" rel="nofollow">{{ info_provider_info.name }}</a>
|
||||
{% else %}
|
||||
{{ info_provider_info.name }}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<tbody>
|
||||
{% for lot in part.partLots %}
|
||||
<tr>
|
||||
<tr {% if lot.id == highlightLotId %}class="table-primary row-highlight row-pulse"{% endif %}>
|
||||
<td>{{ lot.description }}</td>
|
||||
<td>
|
||||
{% if lot.storageLocation %}
|
||||
|
|
|
|||
|
|
@ -616,6 +616,181 @@ class BOMImporterTest extends WebTestCase
|
|||
$this->assertEquals('R1,R2', $bom_entries[0]->getMountnames());
|
||||
}
|
||||
|
||||
public function testStringToBOMEntriesKiCADSchematicWithSupplierSPN(): void
|
||||
{
|
||||
// Create test supplier
|
||||
$lcscSupplier = new Supplier();
|
||||
$lcscSupplier->setName('LCSC');
|
||||
$this->entityManager->persist($lcscSupplier);
|
||||
|
||||
// Create a test part with required fields
|
||||
$part = new Part();
|
||||
$part->setName('Test Resistor 10k 0805');
|
||||
$part->setCategory($this->getDefaultCategory($this->entityManager));
|
||||
$this->entityManager->persist($part);
|
||||
|
||||
// Create orderdetail linking the part to a supplier SPN
|
||||
$orderdetail = new \App\Entity\PriceInformations\Orderdetail();
|
||||
$orderdetail->setPart($part);
|
||||
$orderdetail->setSupplier($lcscSupplier);
|
||||
$orderdetail->setSupplierpartnr('C123456');
|
||||
$this->entityManager->persist($orderdetail);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Import CSV with LCSC SPN matching the orderdetail
|
||||
$input = <<<CSV
|
||||
"Reference","Value","LCSC SPN","Quantity"
|
||||
"R1,R2","10k","C123456","2"
|
||||
CSV;
|
||||
|
||||
$field_mapping = [
|
||||
'Reference' => 'Designator',
|
||||
'Value' => 'Value',
|
||||
'LCSC SPN' => 'LCSC SPN',
|
||||
'Quantity' => 'Quantity'
|
||||
];
|
||||
|
||||
$bom_entries = $this->service->stringToBOMEntries($input, [
|
||||
'type' => 'kicad_schematic',
|
||||
'field_mapping' => $field_mapping,
|
||||
'delimiter' => ','
|
||||
]);
|
||||
|
||||
$this->assertContainsOnlyInstancesOf(ProjectBOMEntry::class, $bom_entries);
|
||||
$this->assertCount(1, $bom_entries);
|
||||
|
||||
// Verify that the BOM entry is linked to the correct part via supplier SPN
|
||||
$this->assertSame($part, $bom_entries[0]->getPart());
|
||||
$this->assertEquals('Test Resistor 10k 0805', $bom_entries[0]->getName());
|
||||
$this->assertEquals('R1,R2', $bom_entries[0]->getMountnames());
|
||||
$this->assertEquals(2.0, $bom_entries[0]->getQuantity());
|
||||
$this->assertStringContainsString('LCSC SPN: C123456', $bom_entries[0]->getComment());
|
||||
$this->assertStringContainsString('Part-DB ID: ' . $part->getID(), $bom_entries[0]->getComment());
|
||||
|
||||
// Clean up
|
||||
$this->entityManager->remove($orderdetail);
|
||||
$this->entityManager->remove($part);
|
||||
$this->entityManager->remove($lcscSupplier);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function testStringToBOMEntriesKiCADSchematicWithMultipleSupplierSPNs(): void
|
||||
{
|
||||
// Create test suppliers
|
||||
$lcscSupplier = new Supplier();
|
||||
$lcscSupplier->setName('LCSC');
|
||||
$mouserSupplier = new Supplier();
|
||||
$mouserSupplier->setName('Mouser');
|
||||
$this->entityManager->persist($lcscSupplier);
|
||||
$this->entityManager->persist($mouserSupplier);
|
||||
|
||||
// Create first part linked via LCSC SPN
|
||||
$part1 = new Part();
|
||||
$part1->setName('Resistor 10k');
|
||||
$part1->setCategory($this->getDefaultCategory($this->entityManager));
|
||||
$this->entityManager->persist($part1);
|
||||
|
||||
$orderdetail1 = new \App\Entity\PriceInformations\Orderdetail();
|
||||
$orderdetail1->setPart($part1);
|
||||
$orderdetail1->setSupplier($lcscSupplier);
|
||||
$orderdetail1->setSupplierpartnr('C123456');
|
||||
$this->entityManager->persist($orderdetail1);
|
||||
|
||||
// Create second part linked via Mouser SPN
|
||||
$part2 = new Part();
|
||||
$part2->setName('Capacitor 100nF');
|
||||
$part2->setCategory($this->getDefaultCategory($this->entityManager));
|
||||
$this->entityManager->persist($part2);
|
||||
|
||||
$orderdetail2 = new \App\Entity\PriceInformations\Orderdetail();
|
||||
$orderdetail2->setPart($part2);
|
||||
$orderdetail2->setSupplier($mouserSupplier);
|
||||
$orderdetail2->setSupplierpartnr('789-CAP100NF');
|
||||
$this->entityManager->persist($orderdetail2);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Import CSV with both LCSC and Mouser SPNs
|
||||
$input = <<<CSV
|
||||
"Reference","Value","LCSC SPN","Mouser SPN","Quantity"
|
||||
"R1","10k","C123456","","1"
|
||||
"C1","100nF","","789-CAP100NF","1"
|
||||
CSV;
|
||||
|
||||
$field_mapping = [
|
||||
'Reference' => 'Designator',
|
||||
'Value' => 'Value',
|
||||
'LCSC SPN' => 'LCSC SPN',
|
||||
'Mouser SPN' => 'Mouser SPN',
|
||||
'Quantity' => 'Quantity'
|
||||
];
|
||||
|
||||
$bom_entries = $this->service->stringToBOMEntries($input, [
|
||||
'type' => 'kicad_schematic',
|
||||
'field_mapping' => $field_mapping,
|
||||
'delimiter' => ','
|
||||
]);
|
||||
|
||||
$this->assertCount(2, $bom_entries);
|
||||
|
||||
// Verify first entry linked via LCSC SPN
|
||||
$this->assertSame($part1, $bom_entries[0]->getPart());
|
||||
$this->assertEquals('Resistor 10k', $bom_entries[0]->getName());
|
||||
|
||||
// Verify second entry linked via Mouser SPN
|
||||
$this->assertSame($part2, $bom_entries[1]->getPart());
|
||||
$this->assertEquals('Capacitor 100nF', $bom_entries[1]->getName());
|
||||
|
||||
// Clean up
|
||||
$this->entityManager->remove($orderdetail1);
|
||||
$this->entityManager->remove($orderdetail2);
|
||||
$this->entityManager->remove($part1);
|
||||
$this->entityManager->remove($part2);
|
||||
$this->entityManager->remove($lcscSupplier);
|
||||
$this->entityManager->remove($mouserSupplier);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function testStringToBOMEntriesKiCADSchematicWithNonMatchingSPN(): void
|
||||
{
|
||||
// Create test supplier
|
||||
$lcscSupplier = new Supplier();
|
||||
$lcscSupplier->setName('LCSC');
|
||||
$this->entityManager->persist($lcscSupplier);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Import CSV with LCSC SPN that doesn't match any orderdetail
|
||||
$input = <<<CSV
|
||||
"Reference","Value","LCSC SPN","Quantity"
|
||||
"R1","10k","C999999","1"
|
||||
CSV;
|
||||
|
||||
$field_mapping = [
|
||||
'Reference' => 'Designator',
|
||||
'Value' => 'Value',
|
||||
'LCSC SPN' => 'LCSC SPN',
|
||||
'Quantity' => 'Quantity'
|
||||
];
|
||||
|
||||
$bom_entries = $this->service->stringToBOMEntries($input, [
|
||||
'type' => 'kicad_schematic',
|
||||
'field_mapping' => $field_mapping,
|
||||
'delimiter' => ','
|
||||
]);
|
||||
|
||||
$this->assertCount(1, $bom_entries);
|
||||
|
||||
// Verify that no part is linked (SPN not found)
|
||||
$this->assertNull($bom_entries[0]->getPart());
|
||||
$this->assertEquals('10k', $bom_entries[0]->getName()); // Should use Value as name
|
||||
$this->assertStringContainsString('LCSC SPN: C999999', $bom_entries[0]->getComment());
|
||||
|
||||
// Clean up
|
||||
$this->entityManager->remove($lcscSupplier);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
private function getDefaultCategory(EntityManagerInterface $entityManager)
|
||||
{
|
||||
// Get the first available category or create a default one
|
||||
|
|
|
|||
|
|
@ -9928,13 +9928,13 @@ Element 1 -> Element 1.2]]></target>
|
|||
<unit id="NdZ1t7a" name="project.builds.number_of_builds_possible">
|
||||
<segment state="translated">
|
||||
<source>project.builds.number_of_builds_possible</source>
|
||||
<target>You have enough stocked to build <b>%max_builds%</b> builds of this [project].</target>
|
||||
<target><![CDATA[You have enough stocked to build <b>%max_builds%</b> builds of this [project].]]></target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="iuSpPbg" name="project.builds.check_project_status">
|
||||
<segment state="translated">
|
||||
<source>project.builds.check_project_status</source>
|
||||
<target>The current [project] status is <b>"%project_status%"</b>. You should check if you really want to build the [project] with this status!</target>
|
||||
<target><![CDATA[The current [project] status is <b>"%project_status%"</b>. You should check if you really want to build the [project] with this status!]]></target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Y7vSSxi" name="project.builds.following_bom_entries_miss_instock_n">
|
||||
|
|
@ -15018,5 +15018,77 @@ Buerklin-API Authentication server:
|
|||
<target>WARNING: This will overwrite your current database with the backup data. This action cannot be undone! Make sure you have a current backup before proceeding.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="kHKChQB" name="settings.ips.conrad">
|
||||
<segment>
|
||||
<source>settings.ips.conrad</source>
|
||||
<target>Conrad</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="gwZXJ0F" name="settings.ips.conrad.shopID">
|
||||
<segment>
|
||||
<source>settings.ips.conrad.shopID</source>
|
||||
<target>Shop ID</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="honqnBf" name="settings.ips.conrad.shopID.description">
|
||||
<segment>
|
||||
<source>settings.ips.conrad.shopID.description</source>
|
||||
<target>The version of the conrad store you wanna get results from. This determines language, prices and currency of the results. If both a B2B and a B2C version if available, you should choose the B2C version if you want prices including VAT. </target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="EVcQbEK" name="settings.ips.conrad.attachment_language_filter">
|
||||
<segment>
|
||||
<source>settings.ips.conrad.attachment_language_filter</source>
|
||||
<target>Language filter for attachments</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="MWPmQf2" name="settings.ips.conrad.attachment_language_filter.description">
|
||||
<segment>
|
||||
<source>settings.ips.conrad.attachment_language_filter.description</source>
|
||||
<target>Only includes attachments in the selected languages in the results.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="PNmvQ.U" name="settings.ips.generic_web_provider">
|
||||
<segment>
|
||||
<source>settings.ips.generic_web_provider</source>
|
||||
<target>Generic Web URL Provider</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Vk3BoE_" name="settings.ips.generic_web_provider.description">
|
||||
<segment>
|
||||
<source>settings.ips.generic_web_provider.description</source>
|
||||
<target>This info provider allows to retrieve basic part information from many shop page URLs.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="Pu8juaH" name="settings.ips.generic_web_provider.enabled.help">
|
||||
<segment>
|
||||
<source>settings.ips.generic_web_provider.enabled.help</source>
|
||||
<target>When the provider is enabled, users can make requests to arbitary websites on behalf of the Part-DB server. Only enable this, if you are aware of the potential consequences.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="IvIOYcn" name="info_providers.from_url.title">
|
||||
<segment>
|
||||
<source>info_providers.from_url.title</source>
|
||||
<target>Create [part] from URL</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="QLL7vDC" name="info_providers.from_url.url.label">
|
||||
<segment>
|
||||
<source>info_providers.from_url.url.label</source>
|
||||
<target>URL</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="JTbTQLl" name="info_providers.from_url.no_part_found">
|
||||
<segment>
|
||||
<source>info_providers.from_url.no_part_found</source>
|
||||
<target>No part found from the given URL. Are you sure this is a valid shop URL?</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="xoSvJk0" name="info_providers.from_url.help">
|
||||
<segment>
|
||||
<source>info_providers.from_url.help</source>
|
||||
<target>Creates a part based on the given URL. It tries to delegate it to an existing info provider if possible, otherwise it will be tried to extract rudimentary data from the webpage's metadata.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
|
|||
278
yarn.lock
278
yarn.lock
|
|
@ -55,34 +55,34 @@
|
|||
resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.19.4.tgz#7a0802e7c64dcc3584d5085e23a290a64ade4319"
|
||||
integrity sha512-/qE8BETNFbul4WrrUyBYgaaKcgFPk0Px9FDKADnr3HlIkXquRpcFHTxXK16jdwXb33yrcXaAVSQZRfUUSSnxVA==
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7"
|
||||
integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c"
|
||||
integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.1.1"
|
||||
|
||||
"@babel/compat-data@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c"
|
||||
integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==
|
||||
"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d"
|
||||
integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==
|
||||
|
||||
"@babel/core@^7.19.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f"
|
||||
integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322"
|
||||
integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.28.6"
|
||||
"@babel/generator" "^7.28.6"
|
||||
"@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.28.6"
|
||||
"@babel/parser" "^7.29.0"
|
||||
"@babel/template" "^7.28.6"
|
||||
"@babel/traverse" "^7.28.6"
|
||||
"@babel/types" "^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"
|
||||
|
|
@ -90,13 +90,13 @@
|
|||
json5 "^2.2.3"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/generator@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1"
|
||||
integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==
|
||||
"@babel/generator@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.0.tgz#4cba5a76b3c71d8be31761b03329d5dc7768447f"
|
||||
integrity sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.28.6"
|
||||
"@babel/types" "^7.28.6"
|
||||
"@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"
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
regexpu-core "^6.3.1"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/helper-define-polyfill-provider@^0.6.5", "@babel/helper-define-polyfill-provider@^0.6.6":
|
||||
"@babel/helper-define-polyfill-provider@^0.6.6":
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz#714dfe33d8bd710f556df59953720f6eeb6c1a14"
|
||||
integrity sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==
|
||||
|
|
@ -173,7 +173,7 @@
|
|||
"@babel/traverse" "^7.28.6"
|
||||
"@babel/types" "^7.28.6"
|
||||
|
||||
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3", "@babel/helper-module-transforms@^7.28.6":
|
||||
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e"
|
||||
integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==
|
||||
|
|
@ -252,12 +252,12 @@
|
|||
"@babel/template" "^7.28.6"
|
||||
"@babel/types" "^7.28.6"
|
||||
|
||||
"@babel/parser@^7.18.9", "@babel/parser@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd"
|
||||
integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==
|
||||
"@babel/parser@^7.18.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6"
|
||||
integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==
|
||||
dependencies:
|
||||
"@babel/types" "^7.28.6"
|
||||
"@babel/types" "^7.29.0"
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5":
|
||||
version "7.28.5"
|
||||
|
|
@ -332,14 +332,14 @@
|
|||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/plugin-transform-async-generator-functions@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz#80cb86d3eaa2102e18ae90dd05ab87bdcad3877d"
|
||||
integrity sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==
|
||||
"@babel/plugin-transform-async-generator-functions@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f"
|
||||
integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
"@babel/helper-remap-async-to-generator" "^7.27.1"
|
||||
"@babel/traverse" "^7.28.6"
|
||||
"@babel/traverse" "^7.29.0"
|
||||
|
||||
"@babel/plugin-transform-async-to-generator@^7.28.6":
|
||||
version "7.28.6"
|
||||
|
|
@ -423,10 +423,10 @@
|
|||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz#e0c59ba54f1655dd682f2edf5f101b5910a8f6f3"
|
||||
integrity sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz#8014b8a6cfd0e7b92762724443bf0d2400f26df1"
|
||||
integrity sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==
|
||||
dependencies:
|
||||
"@babel/helper-create-regexp-features-plugin" "^7.28.5"
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
|
|
@ -521,15 +521,15 @@
|
|||
"@babel/helper-module-transforms" "^7.28.6"
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
|
||||
"@babel/plugin-transform-modules-systemjs@^7.28.5":
|
||||
version "7.28.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2"
|
||||
integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==
|
||||
"@babel/plugin-transform-modules-systemjs@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz#e458a95a17807c415924106a3ff188a3b8dee964"
|
||||
integrity sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==
|
||||
dependencies:
|
||||
"@babel/helper-module-transforms" "^7.28.3"
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
"@babel/helper-module-transforms" "^7.28.6"
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
"@babel/traverse" "^7.28.5"
|
||||
"@babel/traverse" "^7.29.0"
|
||||
|
||||
"@babel/plugin-transform-modules-umd@^7.27.1":
|
||||
version "7.27.1"
|
||||
|
|
@ -539,13 +539,13 @@
|
|||
"@babel/helper-module-transforms" "^7.27.1"
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1"
|
||||
integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==
|
||||
"@babel/plugin-transform-named-capturing-groups-regex@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a"
|
||||
integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==
|
||||
dependencies:
|
||||
"@babel/helper-create-regexp-features-plugin" "^7.27.1"
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
"@babel/helper-create-regexp-features-plugin" "^7.28.5"
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
|
||||
"@babel/plugin-transform-new-target@^7.27.1":
|
||||
version "7.27.1"
|
||||
|
|
@ -633,10 +633,10 @@
|
|||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.27.1"
|
||||
|
||||
"@babel/plugin-transform-regenerator@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz#6ca2ed5b76cff87980f96eaacfc2ce833e8e7a1b"
|
||||
integrity sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==
|
||||
"@babel/plugin-transform-regenerator@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b"
|
||||
integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
|
||||
|
|
@ -723,11 +723,11 @@
|
|||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
|
||||
"@babel/preset-env@^7.19.4":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.6.tgz#b4586bb59d8c61be6c58997f4912e7ea6bd17178"
|
||||
integrity sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.29.0.tgz#c55db400c515a303662faaefd2d87e796efa08d0"
|
||||
integrity sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==
|
||||
dependencies:
|
||||
"@babel/compat-data" "^7.28.6"
|
||||
"@babel/compat-data" "^7.29.0"
|
||||
"@babel/helper-compilation-targets" "^7.28.6"
|
||||
"@babel/helper-plugin-utils" "^7.28.6"
|
||||
"@babel/helper-validator-option" "^7.27.1"
|
||||
|
|
@ -741,7 +741,7 @@
|
|||
"@babel/plugin-syntax-import-attributes" "^7.28.6"
|
||||
"@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
|
||||
"@babel/plugin-transform-arrow-functions" "^7.27.1"
|
||||
"@babel/plugin-transform-async-generator-functions" "^7.28.6"
|
||||
"@babel/plugin-transform-async-generator-functions" "^7.29.0"
|
||||
"@babel/plugin-transform-async-to-generator" "^7.28.6"
|
||||
"@babel/plugin-transform-block-scoped-functions" "^7.27.1"
|
||||
"@babel/plugin-transform-block-scoping" "^7.28.6"
|
||||
|
|
@ -752,7 +752,7 @@
|
|||
"@babel/plugin-transform-destructuring" "^7.28.5"
|
||||
"@babel/plugin-transform-dotall-regex" "^7.28.6"
|
||||
"@babel/plugin-transform-duplicate-keys" "^7.27.1"
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.28.6"
|
||||
"@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.29.0"
|
||||
"@babel/plugin-transform-dynamic-import" "^7.27.1"
|
||||
"@babel/plugin-transform-explicit-resource-management" "^7.28.6"
|
||||
"@babel/plugin-transform-exponentiation-operator" "^7.28.6"
|
||||
|
|
@ -765,9 +765,9 @@
|
|||
"@babel/plugin-transform-member-expression-literals" "^7.27.1"
|
||||
"@babel/plugin-transform-modules-amd" "^7.27.1"
|
||||
"@babel/plugin-transform-modules-commonjs" "^7.28.6"
|
||||
"@babel/plugin-transform-modules-systemjs" "^7.28.5"
|
||||
"@babel/plugin-transform-modules-systemjs" "^7.29.0"
|
||||
"@babel/plugin-transform-modules-umd" "^7.27.1"
|
||||
"@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1"
|
||||
"@babel/plugin-transform-named-capturing-groups-regex" "^7.29.0"
|
||||
"@babel/plugin-transform-new-target" "^7.27.1"
|
||||
"@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6"
|
||||
"@babel/plugin-transform-numeric-separator" "^7.28.6"
|
||||
|
|
@ -779,7 +779,7 @@
|
|||
"@babel/plugin-transform-private-methods" "^7.28.6"
|
||||
"@babel/plugin-transform-private-property-in-object" "^7.28.6"
|
||||
"@babel/plugin-transform-property-literals" "^7.27.1"
|
||||
"@babel/plugin-transform-regenerator" "^7.28.6"
|
||||
"@babel/plugin-transform-regenerator" "^7.29.0"
|
||||
"@babel/plugin-transform-regexp-modifiers" "^7.28.6"
|
||||
"@babel/plugin-transform-reserved-words" "^7.27.1"
|
||||
"@babel/plugin-transform-shorthand-properties" "^7.27.1"
|
||||
|
|
@ -792,10 +792,10 @@
|
|||
"@babel/plugin-transform-unicode-regex" "^7.27.1"
|
||||
"@babel/plugin-transform-unicode-sets-regex" "^7.28.6"
|
||||
"@babel/preset-modules" "0.1.6-no-external-plugins"
|
||||
babel-plugin-polyfill-corejs2 "^0.4.14"
|
||||
babel-plugin-polyfill-corejs3 "^0.13.0"
|
||||
babel-plugin-polyfill-regenerator "^0.6.5"
|
||||
core-js-compat "^3.43.0"
|
||||
babel-plugin-polyfill-corejs2 "^0.4.15"
|
||||
babel-plugin-polyfill-corejs3 "^0.14.0"
|
||||
babel-plugin-polyfill-regenerator "^0.6.6"
|
||||
core-js-compat "^3.48.0"
|
||||
semver "^6.3.1"
|
||||
|
||||
"@babel/preset-modules@0.1.6-no-external-plugins":
|
||||
|
|
@ -816,23 +816,23 @@
|
|||
"@babel/parser" "^7.28.6"
|
||||
"@babel/types" "^7.28.6"
|
||||
|
||||
"@babel/traverse@^7.18.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e"
|
||||
integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==
|
||||
"@babel/traverse@^7.18.9", "@babel/traverse@^7.27.1", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a"
|
||||
integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.28.6"
|
||||
"@babel/generator" "^7.28.6"
|
||||
"@babel/code-frame" "^7.29.0"
|
||||
"@babel/generator" "^7.29.0"
|
||||
"@babel/helper-globals" "^7.28.0"
|
||||
"@babel/parser" "^7.28.6"
|
||||
"@babel/parser" "^7.29.0"
|
||||
"@babel/template" "^7.28.6"
|
||||
"@babel/types" "^7.28.6"
|
||||
"@babel/types" "^7.29.0"
|
||||
debug "^4.3.1"
|
||||
|
||||
"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.4.4":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df"
|
||||
integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==
|
||||
"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.4.4":
|
||||
version "7.29.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7"
|
||||
integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.27.1"
|
||||
"@babel/helper-validator-identifier" "^7.28.5"
|
||||
|
|
@ -1850,9 +1850,9 @@
|
|||
integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==
|
||||
|
||||
"@hotwired/turbo@^8.0.1":
|
||||
version "8.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-8.0.21.tgz#a3e80c01d70048200f64bbe3582b84f9bfac034e"
|
||||
integrity sha512-fJTv3JnzFHeDxBb23esZSOhT4r142xf5o3lKMFMvzPC6AllkqbBKk5Yb31UZhtIsKQCwmO/pUQrtTUlYl5CHAQ==
|
||||
version "8.0.23"
|
||||
resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-8.0.23.tgz#a6eebc9ab4a5faadae265a4cbec8cfcb5731e77c"
|
||||
integrity sha512-GZ7cijxEZ6Ig71u7rD6LHaRv/wcE/hNsc+nEfiWOkLNqUgLOwo5MNGWOy5ZV9ZUDSiQx1no7YxjTNnT4O6//cQ==
|
||||
|
||||
"@isaacs/balanced-match@^4.0.1":
|
||||
version "4.0.1"
|
||||
|
|
@ -2169,9 +2169,9 @@
|
|||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||
|
||||
"@types/node@*":
|
||||
version "25.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.10.tgz#4864459c3c9459376b8b75fd051315071c8213e7"
|
||||
integrity sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==
|
||||
version "25.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.1.0.tgz#95cc584f1f478301efc86de4f1867e5875e83571"
|
||||
integrity sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==
|
||||
dependencies:
|
||||
undici-types "~7.16.0"
|
||||
|
||||
|
|
@ -2575,7 +2575,7 @@ available-typed-arrays@^1.0.7:
|
|||
dependencies:
|
||||
find-up "^5.0.0"
|
||||
|
||||
babel-plugin-polyfill-corejs2@^0.4.14:
|
||||
babel-plugin-polyfill-corejs2@^0.4.15:
|
||||
version "0.4.15"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz#808fa349686eea4741807cfaaa2aa3aa57ce120a"
|
||||
integrity sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==
|
||||
|
|
@ -2584,15 +2584,15 @@ babel-plugin-polyfill-corejs2@^0.4.14:
|
|||
"@babel/helper-define-polyfill-provider" "^0.6.6"
|
||||
semver "^6.3.1"
|
||||
|
||||
babel-plugin-polyfill-corejs3@^0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164"
|
||||
integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==
|
||||
babel-plugin-polyfill-corejs3@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz#65b06cda48d6e447e1e926681f5a247c6ae2b9cf"
|
||||
integrity sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==
|
||||
dependencies:
|
||||
"@babel/helper-define-polyfill-provider" "^0.6.5"
|
||||
core-js-compat "^3.43.0"
|
||||
"@babel/helper-define-polyfill-provider" "^0.6.6"
|
||||
core-js-compat "^3.48.0"
|
||||
|
||||
babel-plugin-polyfill-regenerator@^0.6.5:
|
||||
babel-plugin-polyfill-regenerator@^0.6.6:
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz#69f5dd263cab933c42fe5ea05e83443b374bd4bf"
|
||||
integrity sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==
|
||||
|
|
@ -2627,9 +2627,9 @@ base64-js@^1.1.2, base64-js@^1.3.0:
|
|||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
baseline-browser-mapping@^2.9.0:
|
||||
version "2.9.18"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz#c8281693035a9261b10d662a5379650a6c2d1ff7"
|
||||
integrity sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==
|
||||
version "2.9.19"
|
||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488"
|
||||
integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==
|
||||
|
||||
big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
|
|
@ -2865,9 +2865,9 @@ chrome-trace-event@^1.0.2:
|
|||
integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
|
||||
|
||||
ci-info@^4.2.0:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa"
|
||||
integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c"
|
||||
integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==
|
||||
|
||||
ckeditor5@47.4.0, ckeditor5@^47.0.0:
|
||||
version "47.4.0"
|
||||
|
|
@ -3101,7 +3101,7 @@ convert-source-map@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
core-js-compat@^3.43.0:
|
||||
core-js-compat@^3.48.0:
|
||||
version "3.48.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6"
|
||||
integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==
|
||||
|
|
@ -3165,18 +3165,18 @@ css-loader@^5.2.7:
|
|||
semver "^7.3.5"
|
||||
|
||||
css-loader@^7.1.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.2.tgz#64671541c6efe06b0e22e750503106bdd86880f8"
|
||||
integrity sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.3.tgz#c0de715ceabe39b8531a85fcaf6734a430c4d99a"
|
||||
integrity sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA==
|
||||
dependencies:
|
||||
icss-utils "^5.1.0"
|
||||
postcss "^8.4.33"
|
||||
postcss "^8.4.40"
|
||||
postcss-modules-extract-imports "^3.1.0"
|
||||
postcss-modules-local-by-default "^4.0.5"
|
||||
postcss-modules-scope "^3.2.0"
|
||||
postcss-modules-values "^4.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
semver "^7.5.4"
|
||||
semver "^7.6.3"
|
||||
|
||||
css-minimizer-webpack-plugin@^7.0.0:
|
||||
version "7.0.4"
|
||||
|
|
@ -3379,11 +3379,11 @@ data-view-byte-offset@^1.0.1:
|
|||
is-data-view "^1.0.1"
|
||||
|
||||
datatables.net-bs5@^2, datatables.net-bs5@^2.0.0:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-bs5/-/datatables.net-bs5-2.3.6.tgz#88e9b015cb3d260f3e874f0f9ad16dc566b997da"
|
||||
integrity sha512-oUNGjZrpNC2fY3l/6V4ijTC9kyVKU4Raons+RFmq2J7590rPn0c+5WAYKBx0evgW/CW7WfhStGBrU7+WJig6Og==
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-bs5/-/datatables.net-bs5-2.3.7.tgz#ddef957ee23b03c2d4bc1d48735b39c6182e5d53"
|
||||
integrity sha512-RiCEMpMXDBeMDwjSrMpmcXDU6mibRMuOn7Wk7k3SlOfLEY3FQHO7S2m+K7teXYeaNlCLyjJMU+6BUUwlBCpLFw==
|
||||
dependencies:
|
||||
datatables.net "2.3.6"
|
||||
datatables.net "2.3.7"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net-buttons-bs5@^3.0.0:
|
||||
|
|
@ -3438,18 +3438,18 @@ datatables.net-fixedheader@4.0.5:
|
|||
jquery ">=1.7"
|
||||
|
||||
datatables.net-responsive-bs5@^3.0.0:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-responsive-bs5/-/datatables.net-responsive-bs5-3.0.7.tgz#aa9961d096a7443f59a871d55bf8a19e37a9e60e"
|
||||
integrity sha512-M5VgAXMF7sa64GxFxVfyhiomYpvH/CRXhwoB+l13LaoDU6qtb6noOupFMtG7AVECrDar6UaKe38Frfqz3Pi0Kg==
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-responsive-bs5/-/datatables.net-responsive-bs5-3.0.8.tgz#666e9dfbd14f330630660374edca5d645c3697d5"
|
||||
integrity sha512-f0YTxv/HKWKXkOdutwDe3MmRM3AWf4Lxw7FjrgVc3H5+62emUnHep6cA9VwUcAAMywNqMYVndaKPyhAoeKUCyQ==
|
||||
dependencies:
|
||||
datatables.net-bs5 "^2"
|
||||
datatables.net-responsive "3.0.7"
|
||||
datatables.net-responsive "3.0.8"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net-responsive@3.0.7:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-responsive/-/datatables.net-responsive-3.0.7.tgz#7b57574bcfba105dc0827b77ec75b72b63e461fb"
|
||||
integrity sha512-MngWU41M1LDDMjKFJ3rAHc4Zb3QhOysDTh+TfKE1ycrh5dpnKa1vobw2MKMMbvbx4q05OXZY9jtLSPIkaJRsuw==
|
||||
datatables.net-responsive@3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-responsive/-/datatables.net-responsive-3.0.8.tgz#c41d706c98442122e61a8fb9b02a8b2995cd487d"
|
||||
integrity sha512-htslaX9g/9HFrJeyFQKEe/XJWpawPxpvy+M6vc/NkKQIrKhbxSoPc3phPqmlnZth6b9hgawqWDT0e0lwf5p+KA==
|
||||
dependencies:
|
||||
datatables.net "^2"
|
||||
jquery ">=1.7"
|
||||
|
|
@ -3471,10 +3471,10 @@ datatables.net-select@3.1.3:
|
|||
datatables.net "^2"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net@2.3.6, datatables.net@^2, datatables.net@^2.0.0:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.3.6.tgz#a11be57a2b50d7231cae2980a8ff1df3c18b7b17"
|
||||
integrity sha512-xQ/dCxrjfxM0XY70wSIzakkTZ6ghERwlLmAPyCnu8Sk5cyt9YvOVyOsFNOa/BZ/lM63Q3i2YSSvp/o7GXZGsbg==
|
||||
datatables.net@2.3.7, datatables.net@^2, datatables.net@^2.0.0:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.3.7.tgz#3cd34f6f5d1f40a46b5a20a4ba32604bdbcd6738"
|
||||
integrity sha512-AvsjG/Nkp6OxeyBKYZauemuzQCPogE1kOtKwG4sYjvdqGCSLiGaJagQwXv4YxG+ts5vaJr6qKGG9ec3g6vTo3w==
|
||||
dependencies:
|
||||
jquery ">=1.7"
|
||||
|
||||
|
|
@ -3671,9 +3671,9 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1:
|
|||
gopd "^1.2.0"
|
||||
|
||||
electron-to-chromium@^1.5.263:
|
||||
version "1.5.278"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz#807a5e321f012a41bfd64e653f35993c9af95493"
|
||||
integrity sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==
|
||||
version "1.5.283"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz#51d492c37c2d845a0dccb113fe594880c8616de8"
|
||||
integrity sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
|
|
@ -4146,9 +4146,9 @@ get-symbol-description@^1.1.0:
|
|||
get-intrinsic "^1.2.6"
|
||||
|
||||
get-tsconfig@^4.4.0:
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.0.tgz#fcdd991e6d22ab9a600f00e91c318707a5d9a0d7"
|
||||
integrity sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==
|
||||
version "4.13.1"
|
||||
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.13.1.tgz#ff96c0d98967df211c1ebad41f375ccf516c43fa"
|
||||
integrity sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==
|
||||
dependencies:
|
||||
resolve-pkg-maps "^1.0.0"
|
||||
|
||||
|
|
@ -4957,9 +4957,9 @@ jszip@^3.2.0:
|
|||
setimmediate "^1.0.5"
|
||||
|
||||
katex@^0.16.0:
|
||||
version "0.16.27"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.27.tgz#4ecf6f620e0ca1c1a5de722e85fcdcec49086a48"
|
||||
integrity sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==
|
||||
version "0.16.28"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.28.tgz#64068425b5a29b41b136aae0d51cbb2c71d64c39"
|
||||
integrity sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==
|
||||
dependencies:
|
||||
commander "^8.3.0"
|
||||
|
||||
|
|
@ -6503,7 +6503,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
|
|||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.2.14, postcss@^8.2.15, postcss@^8.4.12, postcss@^8.4.33, postcss@^8.4.40:
|
||||
postcss@^8.2.14, postcss@^8.2.15, postcss@^8.4.12, postcss@^8.4.40:
|
||||
version "8.5.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
|
|
@ -6513,9 +6513,9 @@ postcss@^8.2.14, postcss@^8.2.15, postcss@^8.4.12, postcss@^8.4.33, postcss@^8.4
|
|||
source-map-js "^1.2.1"
|
||||
|
||||
preact@^10.13.2:
|
||||
version "10.28.2"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.28.2.tgz#4b668383afa4b4a2546bbe4bd1747e02e2360138"
|
||||
integrity sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==
|
||||
version "10.28.3"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.28.3.tgz#3c2171526b3e29628ad1a6c56a9e3ca867bbdee8"
|
||||
integrity sha512-tCmoRkPQLpBeWzpmbhryairGnhW9tKV6c6gr/w+RhoRoKEJwsjzipwp//1oCpGPOchvSLaAPlpcJi9MwMmoPyA==
|
||||
|
||||
pretty-error@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
|
@ -6952,7 +6952,7 @@ semver@^6.0.0, semver@^6.3.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.5.4:
|
||||
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.6.3:
|
||||
version "7.7.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946"
|
||||
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
|
||||
|
|
@ -7499,9 +7499,9 @@ tslib@^2.8.0:
|
|||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
type-fest@^5.2.0:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.4.1.tgz#aa9eaadcdc0acb0b5bd52e54f966ee3e38e125d2"
|
||||
integrity sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.4.3.tgz#b4c7e028da129098911ee2162a0c30df8a1be904"
|
||||
integrity sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA==
|
||||
dependencies:
|
||||
tagged-tag "^1.0.0"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue