From 3ff042606ca44f0fbbaa94315421a6f0534f06fa Mon Sep 17 00:00:00 2001 From: Stanislav Ilin Date: Sun, 22 Feb 2026 19:32:45 +0500 Subject: [PATCH] Add initial website configuration --- website/.gitignore | 4 + website/docs/.vitepress/config.ts | 177 ++ website/docs/.vitepress/theme/custom.css | 100 + website/docs/.vitepress/theme/index.ts | 4 + website/docs/examples.md | 67 + website/docs/getting-started/installation.md | 62 + website/docs/getting-started/quick-start.md | 87 + website/docs/getting-started/why-talker.md | 69 + website/docs/guides/crashlytics.md | 197 ++ website/docs/guides/custom-error-messages.md | 161 ++ website/docs/guides/custom-logs.md | 178 ++ website/docs/guides/routing.md | 133 + website/docs/index.md | 222 ++ website/docs/integrations/bloc.md | 98 + website/docs/integrations/chopper.md | 114 + website/docs/integrations/dio.md | 100 + website/docs/integrations/grpc.md | 95 + website/docs/integrations/http.md | 107 + website/docs/integrations/riverpod.md | 96 + website/docs/packages/talker-flutter.md | 242 ++ website/docs/packages/talker-logger.md | 141 + website/docs/packages/talker.md | 265 ++ website/docs/ru/examples.md | 67 + .../docs/ru/getting-started/installation.md | 62 + .../docs/ru/getting-started/quick-start.md | 88 + website/docs/ru/getting-started/why-talker.md | 69 + website/docs/ru/guides/crashlytics.md | 189 ++ .../docs/ru/guides/custom-error-messages.md | 158 ++ website/docs/ru/guides/custom-logs.md | 172 ++ website/docs/ru/guides/routing.md | 129 + website/docs/ru/index.md | 222 ++ website/docs/ru/integrations/bloc.md | 91 + website/docs/ru/integrations/chopper.md | 111 + website/docs/ru/integrations/dio.md | 94 + website/docs/ru/integrations/grpc.md | 95 + website/docs/ru/integrations/http.md | 106 + website/docs/ru/integrations/riverpod.md | 91 + website/docs/ru/packages/talker-flutter.md | 190 ++ website/docs/ru/packages/talker-logger.md | 121 + website/docs/ru/packages/talker.md | 253 ++ website/package-lock.json | 2518 +++++++++++++++++ website/package.json | 14 + website/public/logo.png | Bin 0 -> 1677 bytes 43 files changed, 7559 insertions(+) create mode 100644 website/.gitignore create mode 100644 website/docs/.vitepress/config.ts create mode 100644 website/docs/.vitepress/theme/custom.css create mode 100644 website/docs/.vitepress/theme/index.ts create mode 100644 website/docs/examples.md create mode 100644 website/docs/getting-started/installation.md create mode 100644 website/docs/getting-started/quick-start.md create mode 100644 website/docs/getting-started/why-talker.md create mode 100644 website/docs/guides/crashlytics.md create mode 100644 website/docs/guides/custom-error-messages.md create mode 100644 website/docs/guides/custom-logs.md create mode 100644 website/docs/guides/routing.md create mode 100644 website/docs/index.md create mode 100644 website/docs/integrations/bloc.md create mode 100644 website/docs/integrations/chopper.md create mode 100644 website/docs/integrations/dio.md create mode 100644 website/docs/integrations/grpc.md create mode 100644 website/docs/integrations/http.md create mode 100644 website/docs/integrations/riverpod.md create mode 100644 website/docs/packages/talker-flutter.md create mode 100644 website/docs/packages/talker-logger.md create mode 100644 website/docs/packages/talker.md create mode 100644 website/docs/ru/examples.md create mode 100644 website/docs/ru/getting-started/installation.md create mode 100644 website/docs/ru/getting-started/quick-start.md create mode 100644 website/docs/ru/getting-started/why-talker.md create mode 100644 website/docs/ru/guides/crashlytics.md create mode 100644 website/docs/ru/guides/custom-error-messages.md create mode 100644 website/docs/ru/guides/custom-logs.md create mode 100644 website/docs/ru/guides/routing.md create mode 100644 website/docs/ru/index.md create mode 100644 website/docs/ru/integrations/bloc.md create mode 100644 website/docs/ru/integrations/chopper.md create mode 100644 website/docs/ru/integrations/dio.md create mode 100644 website/docs/ru/integrations/grpc.md create mode 100644 website/docs/ru/integrations/http.md create mode 100644 website/docs/ru/integrations/riverpod.md create mode 100644 website/docs/ru/packages/talker-flutter.md create mode 100644 website/docs/ru/packages/talker-logger.md create mode 100644 website/docs/ru/packages/talker.md create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/public/logo.png diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 000000000..ddfcee14d --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +docs/.vitepress/dist/ +docs/.vitepress/cache/ +.DS_Store diff --git a/website/docs/.vitepress/config.ts b/website/docs/.vitepress/config.ts new file mode 100644 index 000000000..1811bf88a --- /dev/null +++ b/website/docs/.vitepress/config.ts @@ -0,0 +1,177 @@ +import { defineConfig } from 'vitepress' + +export default defineConfig({ + title: 'Talker', + description: 'Advanced error handler and logger for Dart and Flutter apps', + + head: [ + ['link', { rel: 'icon', href: '/logo.png' }], + ], + + locales: { + root: { + label: 'English', + lang: 'en', + themeConfig: { + nav: [ + { text: 'Guide', link: '/getting-started/installation' }, + { text: 'Packages', link: '/packages/talker' }, + { text: 'Integrations', link: '/integrations/dio' }, + { + text: 'Links', + items: [ + { text: 'pub.dev', link: 'https://pub.dev/packages/talker' }, + { text: 'GitHub', link: 'https://github.com/Frezyx/talker' }, + { text: 'Web Demo', link: 'https://frezyx.github.io/talker' }, + ], + }, + ], + sidebar: { + '/': [ + { + text: 'Getting Started', + collapsed: false, + items: [ + { text: 'Installation', link: '/getting-started/installation' }, + { text: 'Quick Start', link: '/getting-started/quick-start' }, + { text: 'Why Talker?', link: '/getting-started/why-talker' }, + ], + }, + { + text: 'Core Packages', + collapsed: false, + items: [ + { text: 'talker', link: '/packages/talker' }, + { text: 'talker_logger', link: '/packages/talker-logger' }, + { text: 'talker_flutter', link: '/packages/talker-flutter' }, + ], + }, + { + text: 'Integrations', + collapsed: false, + items: [ + { text: 'Dio Logger', link: '/integrations/dio' }, + { text: 'HTTP Logger', link: '/integrations/http' }, + { text: 'BLoC Logger', link: '/integrations/bloc' }, + { text: 'Riverpod Logger', link: '/integrations/riverpod' }, + { text: 'Chopper Logger', link: '/integrations/chopper' }, + { text: 'gRPC Logger', link: '/integrations/grpc' }, + ], + }, + { + text: 'Guides', + collapsed: false, + items: [ + { text: 'Custom Log Types', link: '/guides/custom-logs' }, + { text: 'Crashlytics Integration', link: '/guides/crashlytics' }, + { text: 'Route Logging', link: '/guides/routing' }, + { text: 'Custom Error Messages', link: '/guides/custom-error-messages' }, + ], + }, + { + text: 'Examples', + link: '/examples', + }, + ], + }, + }, + }, + ru: { + label: 'Русский', + lang: 'ru', + link: '/ru/', + themeConfig: { + nav: [ + { text: 'Руководство', link: '/ru/getting-started/installation' }, + { text: 'Пакеты', link: '/ru/packages/talker' }, + { text: 'Интеграции', link: '/ru/integrations/dio' }, + { + text: 'Ссылки', + items: [ + { text: 'pub.dev', link: 'https://pub.dev/packages/talker' }, + { text: 'GitHub', link: 'https://github.com/Frezyx/talker' }, + { text: 'Web Demo', link: 'https://frezyx.github.io/talker' }, + ], + }, + ], + sidebar: { + '/ru/': [ + { + text: 'Начало работы', + collapsed: false, + items: [ + { text: 'Установка', link: '/ru/getting-started/installation' }, + { text: 'Быстрый старт', link: '/ru/getting-started/quick-start' }, + { text: 'Почему Talker?', link: '/ru/getting-started/why-talker' }, + ], + }, + { + text: 'Основные пакеты', + collapsed: false, + items: [ + { text: 'talker', link: '/ru/packages/talker' }, + { text: 'talker_logger', link: '/ru/packages/talker-logger' }, + { text: 'talker_flutter', link: '/ru/packages/talker-flutter' }, + ], + }, + { + text: 'Интеграции', + collapsed: false, + items: [ + { text: 'Dio Logger', link: '/ru/integrations/dio' }, + { text: 'HTTP Logger', link: '/ru/integrations/http' }, + { text: 'BLoC Logger', link: '/ru/integrations/bloc' }, + { text: 'Riverpod Logger', link: '/ru/integrations/riverpod' }, + { text: 'Chopper Logger', link: '/ru/integrations/chopper' }, + { text: 'gRPC Logger', link: '/ru/integrations/grpc' }, + ], + }, + { + text: 'Руководства', + collapsed: false, + items: [ + { text: 'Пользовательские логи', link: '/ru/guides/custom-logs' }, + { text: 'Интеграция с Crashlytics', link: '/ru/guides/crashlytics' }, + { text: 'Логирование маршрутов', link: '/ru/guides/routing' }, + { text: 'Кастомные сообщения об ошибках', link: '/ru/guides/custom-error-messages' }, + ], + }, + { + text: 'Примеры', + link: '/ru/examples', + }, + ], + }, + outline: { label: 'Содержание' }, + docFooter: { prev: 'Предыдущая', next: 'Следующая' }, + darkModeSwitchLabel: 'Тема', + sidebarMenuLabel: 'Меню', + returnToTopLabel: 'Наверх', + langMenuLabel: 'Язык', + }, + }, + }, + + themeConfig: { + logo: '/logo.png', + siteTitle: 'Talker', + + socialLinks: [ + { icon: 'github', link: 'https://github.com/Frezyx/talker' }, + ], + + search: { + provider: 'local', + }, + + editLink: { + pattern: 'https://github.com/Frezyx/talker/edit/master/website/docs/:path', + text: 'Edit this page on GitHub', + }, + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2022-present Stanislav Ilin', + }, + }, +}) diff --git a/website/docs/.vitepress/theme/custom.css b/website/docs/.vitepress/theme/custom.css new file mode 100644 index 000000000..e8262c589 --- /dev/null +++ b/website/docs/.vitepress/theme/custom.css @@ -0,0 +1,100 @@ +/** + * Talker Documentation - Purple Theme + * + * Colors based on Material Purple (#7C4DFF) + * @see https://vitepress.dev/reference/default-theme-config#custom-css + */ + +:root { + /* Brand / Primary Purple */ + --vp-c-brand-1: #7C4DFF; + --vp-c-brand-2: #9C6FFF; + --vp-c-brand-3: #651FFF; + --vp-c-brand-soft: rgba(124, 77, 255, 0.14); + + /* Tip color - same as brand */ + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + /* Button colors */ + --vp-button-brand-border: transparent; + --vp-button-brand-text: #fff; + --vp-button-brand-bg: var(--vp-c-brand-1); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: #fff; + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: #fff; + --vp-button-brand-active-bg: var(--vp-c-brand-3); + + /* Home hero name gradient */ + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #7C4DFF 30%, + #B388FF + ); + + /* Home hero image glow */ + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #7C4DFF 50%, + #B388FF 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +.dark { + --vp-c-brand-1: #9C6FFF; + --vp-c-brand-2: #B388FF; + --vp-c-brand-3: #7C4DFF; + --vp-c-brand-soft: rgba(156, 111, 255, 0.16); + + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #B388FF 30%, + #7C4DFF + ); +} + +/* Sidebar active link */ +.VPSidebar .is-active > .link > .text { + color: var(--vp-c-brand-1) !important; +} + +/* Custom badge for package versions */ +.package-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + background-color: var(--vp-c-brand-soft); + color: var(--vp-c-brand-1); +} + +/* DartPad embed styling */ +.dartpad-embed { + width: 100%; + border: 1px solid var(--vp-c-divider); + border-radius: 8px; + overflow: hidden; + margin: 16px 0; +} + +.dartpad-embed iframe { + width: 100%; + height: 400px; + border: none; +} + +/* Feature cards on home page */ +.VPFeatures .VPFeature { + border-color: var(--vp-c-brand-soft) !important; +} + +.VPFeatures .VPFeature:hover { + border-color: var(--vp-c-brand-1) !important; +} diff --git a/website/docs/.vitepress/theme/index.ts b/website/docs/.vitepress/theme/index.ts new file mode 100644 index 000000000..42fe9a936 --- /dev/null +++ b/website/docs/.vitepress/theme/index.ts @@ -0,0 +1,4 @@ +import DefaultTheme from 'vitepress/theme' +import './custom.css' + +export default DefaultTheme diff --git a/website/docs/examples.md b/website/docs/examples.md new file mode 100644 index 000000000..0922753df --- /dev/null +++ b/website/docs/examples.md @@ -0,0 +1,67 @@ +# Examples + +## Interactive DartPad Examples + +### Basic Logging + +Try Talker's logging levels in your browser: + +
+ +
+ +### Error Handling + +See how Talker handles exceptions with stack traces: + +
+ +
+ +### Custom Log Types + +Create your own log types: + +
+ +
+ +## Shop App Example + +A full production-like Flutter application demonstrating Talker integration: + +- Talker setup with DI +- HTTP logging with Dio +- BLoC state management logging +- TalkerScreen and TalkerMonitor +- Error handling with TalkerWrapper +- Route logging + +📂 [View Shop App on GitHub](https://github.com/Frezyx/talker/tree/master/examples/shop_app_example) + +## Web Demo + +Try the fully interactive Flutter web demo with `TalkerScreen` and `TalkerMonitor`: + +🌐 [Open Web Demo](https://frezyx.github.io/talker) + + diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md new file mode 100644 index 000000000..95b9006ca --- /dev/null +++ b/website/docs/getting-started/installation.md @@ -0,0 +1,62 @@ +# Installation + +## Choose Your Package + +Talker is a modular ecosystem. Install only what you need: + +### For Dart-only projects + +```yaml +dependencies: + talker: ^5.1.13 +``` + +### For Flutter projects + +```yaml +dependencies: + talker_flutter: ^5.1.13 +``` + +::: tip +`talker_flutter` already includes `talker` as a dependency — you don't need to add both. +::: + +### Integration packages + +Add any of these alongside your main package: + +```yaml +dependencies: + # HTTP logging for Dio + talker_dio_logger: ^5.1.13 + + # HTTP logging for http package + talker_http_logger: ^5.1.13 + + # BLoC state management logging + talker_bloc_logger: ^5.1.13 + + # Riverpod state management logging + talker_riverpod_logger: ^5.1.13 + + # HTTP logging for Chopper + talker_chopper_logger: ^5.1.13 + + # gRPC logging + talker_grpc_logger: ^5.1.13 +``` + +## Run pub get + +After adding the dependencies, run: + +```bash +flutter pub get +# or for Dart-only projects +dart pub get +``` + +## Next Steps + +Now that you have Talker installed, head over to the [Quick Start](/getting-started/quick-start) guide to start logging! diff --git a/website/docs/getting-started/quick-start.md b/website/docs/getting-started/quick-start.md new file mode 100644 index 000000000..9e2e69b33 --- /dev/null +++ b/website/docs/getting-started/quick-start.md @@ -0,0 +1,87 @@ +# Quick Start + +## Basic Usage (Dart) + +Create a Talker instance and start logging: + +```dart +import 'package:talker/talker.dart'; + +final talker = Talker(); + +void main() { + // Log messages with different levels + talker.info('App started'); + talker.debug('Loading configuration...'); + talker.warning('Cache is almost full'); + talker.good('User profile loaded!'); + talker.verbose('Detailed trace info'); + talker.critical('Database connection lost!'); + + // Handle exceptions + try { + throw Exception('Something went wrong'); + } catch (e, st) { + talker.handle(e, st, 'Failed to process data'); + } +} +``` + +## Flutter Usage + +For Flutter apps, use `TalkerFlutter.init()` for optimal platform-specific logging: + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +void main() { + // Handle exceptions and errors + try { + // your code... + } catch (e, st) { + talker.handle(e, st, 'Exception in ...'); + } + + // Log your app info + talker.info('App is started'); + talker.critical('Houston, we have a problem!'); + talker.error('The service is not available'); +} +``` + +::: tip Why TalkerFlutter.init()? +Most Flutter logging packages either cut messages in the console or can't display colored messages on iOS. `TalkerFlutter.init()` uses the optimal logging method for each platform: +- **Web** — uses `print()` +- **iOS / macOS** — uses `dart:developer.log` +- **Android / Windows / Linux** — uses `debugPrint` +::: + +## View Logs In-App + +Use `TalkerScreen` to browse logs directly in your app: + +```dart +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), +); +``` + +## Add HTTP Logging + +If you use Dio, add logging with one line: + +```dart +final dio = Dio(); +dio.interceptors.add(TalkerDioLogger(talker: talker)); +``` + +## What's Next? + +- Learn about [Talker core features](/packages/talker) — custom logs, colors, titles, observer +- Explore [Flutter widgets](/packages/talker-flutter) — TalkerScreen, TalkerMonitor, TalkerWrapper +- Set up [integrations](/integrations/dio) — Dio, BLoC, Riverpod, and more +- Read [guides](/guides/custom-logs) — custom log types, Crashlytics integration diff --git a/website/docs/getting-started/why-talker.md b/website/docs/getting-started/why-talker.md new file mode 100644 index 000000000..a691b9dcb --- /dev/null +++ b/website/docs/getting-started/why-talker.md @@ -0,0 +1,69 @@ +# Why Talker? + +## The Problem + +When developing Dart and Flutter applications, you need to: + +- **Understand where errors occur** — as quickly as possible +- **Log application actions** — HTTP requests, state changes, navigation +- **Handle exceptions gracefully** — without crashing the app +- **Share error reports** — with your team or crash reporting services +- **Debug on real devices** — when there's no console available + +Most existing logging solutions only cover part of this. You end up with scattered tools, inconsistent formats, and no unified view. + +## The Solution + +Talker provides a **single, unified system** for all logging and error handling in your application: + +### Unified Logging + +One API for everything — from simple debug messages to HTTP requests and state management events. All logs share the same format, history, and output. + +### Rich Error Handling + +Automatically distinguish between `Exception` and `Error` types, capture `StackTrace`, and route everything through a unified handler with `TalkerObserver`. + +### Flutter-First UI + +Built-in `TalkerScreen` lets you view, filter, search, and share all logs directly in your app — no console needed. `TalkerMonitor` gives you a quick status overview. + +### Modular Integrations + +Pick only what you need. Each integration (Dio, BLoC, Riverpod, etc.) is a separate lightweight package that plugs into the same Talker instance. + +### Full Customization + +Every aspect is customizable — colors, titles, log formats, filters. Create your own log types with full control over appearance and behavior. + +## Key Features + +| Feature | Talker | +|---------|--------| +| Log levels (info, debug, warning, error, critical, verbose, good) | ✅ | +| Colored console output | ✅ | +| Custom log types | ✅ | +| Exception/Error handling with StackTrace | ✅ | +| Log history | ✅ | +| In-app log viewer (TalkerScreen) | ✅ | +| In-app status monitor (TalkerMonitor) | ✅ | +| Error alerts UI (TalkerWrapper) | ✅ | +| Route/Navigation logging | ✅ | +| Dio HTTP logging | ✅ | +| http package logging | ✅ | +| BLoC state management logging | ✅ | +| Riverpod state management logging | ✅ | +| Chopper HTTP logging | ✅ | +| gRPC logging | ✅ | +| Observer for external services (Crashlytics, Sentry) | ✅ | +| Platform-optimized output (iOS, Android, Web) | ✅ | +| 100% test coverage | ✅ | +| Works with any state management | ✅ | + +## Compatibility + +- **Dart** >= 2.15 +- **Flutter** >= 3.0 +- **Platforms**: Android, iOS, Web, macOS, Windows, Linux +- **State management**: Works with any — BLoC, Riverpod, Provider, GetX, MobX, etc. +- **Crash reporting**: Firebase Crashlytics, Sentry, or any custom solution diff --git a/website/docs/guides/crashlytics.md b/website/docs/guides/crashlytics.md new file mode 100644 index 000000000..957e6d4f2 --- /dev/null +++ b/website/docs/guides/crashlytics.md @@ -0,0 +1,197 @@ +# Crashlytics & Sentry Integration + +Use `TalkerObserver` to send errors, exceptions, and logs from Talker to external crash reporting and analytics services. + +## How TalkerObserver Works + +`TalkerObserver` listens to all Talker events and provides callbacks for each type: + +```dart +abstract class TalkerObserver { + void onError(TalkerError err) {} + void onException(TalkerException exception) {} + void onLog(TalkerDataInterface log) {} +} +``` + +- **onError** — called when `talker.handle()` catches an `Error` +- **onException** — called when `talker.handle()` catches an `Exception` +- **onLog** — called for every log event (info, debug, HTTP, custom, etc.) + +## Firebase Crashlytics + +```dart +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:talker/talker.dart'; + +class CrashlyticsTalkerObserver extends TalkerObserver { + CrashlyticsTalkerObserver(this.crashlytics); + + final FirebaseCrashlytics crashlytics; + + @override + void onError(TalkerError err) { + crashlytics.recordError( + err.error, + err.stackTrace, + reason: err.message, + fatal: err.logLevel == LogLevel.critical, + ); + } + + @override + void onException(TalkerException exception) { + crashlytics.recordError( + exception.exception, + exception.stackTrace, + reason: exception.message, + ); + } + + @override + void onLog(TalkerDataInterface log) { + // Optionally log breadcrumbs for non-error events + crashlytics.log(log.generateTextMessage()); + } +} +``` + +### Setup + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + final crashlytics = FirebaseCrashlytics.instance; + + final talker = TalkerFlutter.init( + observer: CrashlyticsTalkerObserver(crashlytics), + ); + + // Now all talker.handle() calls will automatically report to Crashlytics + try { + throw Exception('Payment failed'); + } catch (e, st) { + talker.handle(e, st, 'Payment processing error'); + // ^ This will log to console, save to history, AND send to Crashlytics + } +} +``` + +## Sentry + +```dart +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:talker/talker.dart'; + +class SentryTalkerObserver extends TalkerObserver { + @override + void onError(TalkerError err) { + Sentry.captureException( + err.error, + stackTrace: err.stackTrace, + ); + } + + @override + void onException(TalkerException exception) { + Sentry.captureException( + exception.exception, + stackTrace: exception.stackTrace, + ); + } + + @override + void onLog(TalkerDataInterface log) { + Sentry.addBreadcrumb( + Breadcrumb( + message: log.generateTextMessage(), + level: _mapLevel(log), + timestamp: DateTime.now(), + ), + ); + } + + SentryLevel _mapLevel(TalkerDataInterface log) { + if (log is TalkerError || log is TalkerException) { + return SentryLevel.error; + } + return SentryLevel.info; + } +} +``` + +### Setup + +```dart +void main() async { + await SentryFlutter.init( + (options) { + options.dsn = 'YOUR_SENTRY_DSN'; + }, + appRunner: () { + final talker = TalkerFlutter.init( + observer: SentryTalkerObserver(), + ); + runApp(MyApp(talker: talker)); + }, + ); +} +``` + +## Custom Analytics Service + +The same pattern works for any analytics or monitoring service: + +```dart +class AnalyticsTalkerObserver extends TalkerObserver { + AnalyticsTalkerObserver(this.analytics); + + final MyAnalyticsService analytics; + + @override + void onError(TalkerError err) { + analytics.trackError( + name: 'app_error', + error: err.error.toString(), + stackTrace: err.stackTrace?.toString(), + ); + } + + @override + void onException(TalkerException exception) { + analytics.trackError( + name: 'app_exception', + error: exception.exception.toString(), + stackTrace: exception.stackTrace?.toString(), + ); + } + + @override + void onLog(TalkerDataInterface log) { + analytics.trackEvent( + name: 'app_log', + params: {'message': log.message, 'title': log.title}, + ); + } +} +``` + +## Best Practices + +1. **Don't block the main thread** — crash reporting SDKs handle this internally, but be mindful with `onLog` callbacks if you add heavy processing. + +2. **Use log levels wisely** — send only `error` and `critical` logs to Crashlytics, use `onLog` for breadcrumbs. + +3. **Combine with TalkerWrapper** — show errors to users via SnackBars while simultaneously reporting to your crash service: + +```dart +TalkerWrapper( + talker: talker, // Same instance with CrashlyticsTalkerObserver + options: TalkerWrapperOptions(enableErrorAlerts: true), + child: MyApp(), +) +``` + +4. **Test your observer** — write unit tests to verify that the observer is called correctly for each event type. diff --git a/website/docs/guides/custom-error-messages.md b/website/docs/guides/custom-error-messages.md new file mode 100644 index 000000000..f684a477c --- /dev/null +++ b/website/docs/guides/custom-error-messages.md @@ -0,0 +1,161 @@ +# Custom Error Messages + +Show user-friendly error alerts and custom messages in your Flutter app using Talker's Flutter widgets. + +## TalkerWrapper + +The simplest way to show error alerts. Wrap your app (or a subtree) with `TalkerWrapper`: + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +TalkerWrapper( + talker: talker, + options: const TalkerWrapperOptions( + enableErrorAlerts: true, + enableExceptionAlerts: true, + ), + child: MaterialApp( + home: HomeScreen(), + ), +) +``` + +Now whenever `talker.handle()` catches an exception or error, a SnackBar will automatically appear at the bottom of the screen. + +## TalkerListener + +For more control over what happens on each event, use `TalkerListener`: + +```dart +TalkerListener( + talker: talker, + listener: (data) { + if (data is TalkerException) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: ${data.displayMessage}'), + backgroundColor: Colors.red, + action: SnackBarAction( + label: 'Details', + textColor: Colors.white, + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => TalkerScreen(talker: talker), + ), + ); + }, + ), + ), + ); + } + }, + child: MyApp(), +) +``` + +## TalkerBuilder + +Build UI reactively based on log events: + +```dart +TalkerBuilder( + talker: talker, + builder: (context, data) { + final errors = data.whereType().toList(); + final exceptions = data.whereType().toList(); + + if (errors.isEmpty && exceptions.isEmpty) { + return const SizedBox.shrink(); + } + + return Container( + padding: const EdgeInsets.all(8), + color: Colors.red.shade100, + child: Text( + '${errors.length + exceptions.length} errors occurred', + style: const TextStyle(color: Colors.red), + ), + ); + }, +) +``` + +## Custom Dialog on Error + +Combine `TalkerListener` with a dialog for critical errors: + +```dart +TalkerListener( + talker: talker, + listener: (data) { + // Show dialog only for critical errors + if (data is TalkerError && data.logLevel == LogLevel.critical) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Critical Error'), + content: Text(data.displayMessage), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => TalkerScreen(talker: talker), + ), + ); + }, + child: const Text('View Logs'), + ), + ], + ), + ); + } + }, + child: MyApp(), +) +``` + +## Combining with Crashlytics + +Use `TalkerWrapper` for user-visible alerts AND `TalkerObserver` for crash reporting simultaneously: + +```dart +final talker = TalkerFlutter.init( + observer: CrashlyticsTalkerObserver(FirebaseCrashlytics.instance), +); + +TalkerWrapper( + talker: talker, + options: const TalkerWrapperOptions( + enableErrorAlerts: true, + enableExceptionAlerts: true, + ), + child: MaterialApp(home: HomeScreen()), +) +``` + +Now every error is: +1. Logged to the console +2. Saved in history +3. Shown to the user as a SnackBar +4. Sent to Crashlytics + +## Best Practices + +1. **Don't show technical details to users** — use `displayMessage` or create friendly messages rather than showing raw exception text. + +2. **Use TalkerWrapper for development** — it's a quick way to see errors during development. Disable or replace with custom UI for production. + +3. **Offer "View Logs" action** — let power users or testers open `TalkerScreen` from error alerts to investigate issues. + +4. **Rate-limit alerts** — if errors fire rapidly, use a debounce mechanism to avoid flooding the user with SnackBars. diff --git a/website/docs/guides/custom-logs.md b/website/docs/guides/custom-logs.md new file mode 100644 index 000000000..414795508 --- /dev/null +++ b/website/docs/guides/custom-logs.md @@ -0,0 +1,178 @@ +# Custom Log Types + +Talker allows you to create your own log types with full control over appearance, output format, and categorization. + +## Why Custom Logs? + +The built-in log types (`info`, `debug`, `warning`, etc.) cover most cases. But when you need domain-specific logs — like analytics events, business rule triggers, or custom HTTP formats — you can define your own. + +## Creating a Custom Log + +Extend the `TalkerLog` class: + +```dart +import 'package:talker/talker.dart'; + +class AnalyticsLog extends TalkerLog { + AnalyticsLog(String event, this.params) : super(event); + + final Map params; + + /// Unique key for this log type (used in settings, filters, etc.) + @override + String get key => 'analytics'; + + /// Title shown in console and TalkerScreen + @override + String get title => 'ANALYTICS'; + + /// Console color + @override + AnsiPen get pen => AnsiPen()..magenta(); + + /// Customize the full message output + @override + String generateTextMessage({TimeFormat? timeFormat}) { + return '${super.generateTextMessage(timeFormat: timeFormat)}\n' + 'Event: $message\n' + 'Params: $params'; + } +} +``` + +## Using Custom Logs + +Use `logCustom()` to send your custom log through Talker: + +```dart +final talker = Talker(); + +talker.logCustom( + AnalyticsLog('user_login', {'method': 'google', 'first_time': true}), +); +``` + +This will: +- Print a colored message to console with the title `ANALYTICS` +- Save the log in Talker's history +- Show the log in `TalkerScreen` +- Broadcast it via `talker.stream` +- Trigger `TalkerObserver.onLog()` + +## Registering Custom Keys + +To use custom colors and titles via `TalkerSettings`, register your custom key: + +```dart +final talker = Talker( + settings: TalkerSettings( + // Register custom colors + colors: { + 'analytics': AnsiPen()..magenta(), + }, + // Register custom titles + titles: { + 'analytics': 'ANALYTICS', + }, + ), +); +``` + +::: tip +Settings-level colors and titles take priority over the log's own `pen` and `title` getters, allowing you to override them without changing the log class. +::: + +## Real-World Examples + +### Business Event Log + +```dart +class BusinessEventLog extends TalkerLog { + BusinessEventLog(String eventName, {this.revenue, this.userId}) + : super(eventName); + + final double? revenue; + final String? userId; + + @override + String get key => 'business_event'; + + @override + String get title => 'BUSINESS'; + + @override + AnsiPen get pen => AnsiPen()..xterm(220); // Gold + + @override + String generateTextMessage({TimeFormat? timeFormat}) { + var msg = super.generateTextMessage(timeFormat: timeFormat); + if (revenue != null) msg += '\nRevenue: \$${revenue!.toStringAsFixed(2)}'; + if (userId != null) msg += '\nUser: $userId'; + return msg; + } +} + +talker.logCustom( + BusinessEventLog('purchase_completed', revenue: 29.99, userId: 'usr_123'), +); +``` + +### Database Query Log + +```dart +class DbQueryLog extends TalkerLog { + DbQueryLog(String query, this.duration) : super(query); + + final Duration duration; + + @override + String get key => 'db_query'; + + @override + String get title => 'DB'; + + @override + AnsiPen get pen => duration.inMilliseconds > 1000 + ? (AnsiPen()..red()) // Slow query + : (AnsiPen()..green()); // Normal query + + @override + String generateTextMessage({TimeFormat? timeFormat}) { + return '${super.generateTextMessage(timeFormat: timeFormat)}\n' + 'Query: $message\n' + 'Duration: ${duration.inMilliseconds}ms'; + } +} + +talker.logCustom( + DbQueryLog('SELECT * FROM users WHERE id = ?', Duration(milliseconds: 45)), +); +``` + +## Filtering Custom Logs + +Custom logs can be filtered by type and title just like built-in logs: + +```dart +final talker = Talker( + filter: BaseTalkerFilter( + titles: ['ANALYTICS', 'DB'], + ), +); +``` + +## Viewing in TalkerScreen + +Custom logs appear in `TalkerScreen` automatically. Their color in the Flutter UI can be customized via `TalkerScreenTheme`: + +```dart +TalkerScreen( + talker: talker, + theme: const TalkerScreenTheme( + logColors: { + 'analytics': Color(0xFFE040FB), + 'db_query': Color(0xFF4CAF50), + }, + ), +) +``` diff --git a/website/docs/guides/routing.md b/website/docs/guides/routing.md new file mode 100644 index 000000000..077a2bb40 --- /dev/null +++ b/website/docs/guides/routing.md @@ -0,0 +1,133 @@ +# Route Logging + +Log navigation events in your Flutter app with `TalkerRouteObserver`. Works with Navigator, go_router, auto_route, and any NavigatorObserver-compatible router. + +## Setup + +### Navigator 1.0 + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +MaterialApp( + navigatorObservers: [ + TalkerRouteObserver(talker), + ], + home: HomeScreen(), +) +``` + +### go_router + +```dart +import 'package:go_router/go_router.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +final router = GoRouter( + observers: [TalkerRouteObserver(talker)], + routes: [ + GoRoute(path: '/', builder: (_, __) => HomeScreen()), + GoRoute(path: '/profile', builder: (_, __) => ProfileScreen()), + ], +); + +MaterialApp.router(routerConfig: router) +``` + +### auto_route v7+ + +```dart +import 'package:auto_route/auto_route.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +MaterialApp.router( + routerConfig: _appRouter.config( + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), +) +``` + +### auto_route (legacy) + +```dart +MaterialApp.router( + routerDelegate: AutoRouterDelegate( + appRouter, + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), + routeInformationParser: appRouter.defaultRouteParser(), +) +``` + +## What Gets Logged + +`TalkerRouteObserver` logs the following events: + +| Event | Description | Example | +|-------|-------------|---------| +| **Push** | New route pushed onto the stack | `Route pushed: /profile` | +| **Pop** | Route popped from the stack | `Route popped: /profile` | +| **Replace** | Route replaced | `Route replaced: /login → /home` | +| **Remove** | Route removed | `Route removed: /splash` | + +All navigation logs appear in the console, TalkerScreen, and history with the default route color (purple). + +## Viewing in TalkerScreen + +Navigate to `TalkerScreen` to see all route events alongside other logs: + +```dart +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), +); +``` + +Route logs are color-coded and easily distinguishable from other log types. You can filter them in TalkerScreen by selecting the "Route" filter. + +## Customization + +### Custom Route Color + +Change the color of route logs in the console: + +```dart +final talker = Talker( + settings: TalkerSettings( + colors: { + TalkerLogType.route.key: AnsiPen()..cyan(), + }, + ), +); +``` + +### Custom Route Title + +```dart +final talker = Talker( + settings: TalkerSettings( + titles: { + TalkerLogType.route.key: 'NAV', + }, + ), +); +``` + +## Best Practices + +1. **Use the same Talker instance** across your entire app — route logs, HTTP logs, BLoC events, and error reports all in one place. + +2. **Combine with TalkerScreen** — during development, add a debug button or shake gesture to open `TalkerScreen` and see the full navigation history. + +3. **Use with Crashlytics** — route logs make excellent breadcrumbs for crash reports. When combined with `TalkerObserver`, every navigation event is sent as a breadcrumb to Crashlytics or Sentry. diff --git a/website/docs/index.md b/website/docs/index.md new file mode 100644 index 000000000..9268de7f6 --- /dev/null +++ b/website/docs/index.md @@ -0,0 +1,222 @@ +--- +layout: home + +hero: + name: Talker + text: Advanced Error Handler & Logger + tagline: Log your app actions, catch and handle exceptions, show alerts and share log reports for Dart & Flutter + image: + src: /logo.png + alt: Talker + actions: + - theme: brand + text: Get Started + link: /getting-started/installation + - theme: alt + text: View on GitHub + link: https://github.com/Frezyx/talker + - theme: alt + text: Web Demo + link: https://frezyx.github.io/talker + +features: + - icon: 📝 + title: Powerful Logging + details: Log with multiple levels — info, debug, warning, error, critical, verbose, and good. Full color customization and custom log types support. + - icon: 🛡️ + title: Error Handling + details: Catch and handle Exceptions and Errors with StackTrace. Send error data to Crashlytics, Sentry, or your own analytics service via TalkerObserver. + - icon: 📱 + title: Flutter UI + details: Built-in TalkerScreen to view logs in-app, TalkerMonitor for quick status overview, TalkerWrapper for error alerts, and TalkerRouteObserver for navigation logging. + - icon: 🔌 + title: Rich Integrations + details: Out-of-the-box support for Dio, http, BLoC, Riverpod, Chopper, and gRPC. Each with full customization, filtering, and color options. + - icon: 🎨 + title: Fully Customizable + details: Custom colors, titles, formatters, and filters for every log type. Create your own log types with full control over appearance and behavior. + - icon: ✅ + title: Production Ready + details: 100% test coverage, MIT licensed, compatible with any state management, works on all platforms — Android, iOS, Web, macOS, Windows, Linux. +--- + + + +
+ +## Try Talker Now + +Play with Talker directly in your browser — no setup needed: + +
+ +
+ +
+ +
+ +## Packages Ecosystem + +Talker is designed for any level of customization — pick what you need: + +
+ +
+ +### [talker](https://pub.dev/packages/talker) + +Main Dart package for logging and error handling. The core of the ecosystem. + +
+ +
+ +### [talker_flutter](https://pub.dev/packages/talker_flutter) + +Flutter extensions — colored logs, TalkerScreen, TalkerMonitor, route observer, and more. + +
+ +
+ +### [talker_logger](https://pub.dev/packages/talker_logger) + +Customizable pretty logger for Dart/Flutter apps. Can be used standalone. + +
+ +
+ +### [talker_dio_logger](https://pub.dev/packages/talker_dio_logger) + +Beautiful HTTP request/response logging for Dio http client. + +
+ +
+ +### [talker_http_logger](https://pub.dev/packages/talker_http_logger) + +HTTP logging for the standard http package with http_interceptor. + +
+ +
+ +### [talker_bloc_logger](https://pub.dev/packages/talker_bloc_logger) + +State management logging for BLoC — events, transitions, state changes. + +
+ +
+ +### [talker_riverpod_logger](https://pub.dev/packages/talker_riverpod_logger) + +Provider lifecycle logging for Riverpod — add, update, dispose, fail. + +
+ +
+ +### [talker_chopper_logger](https://pub.dev/packages/talker_chopper_logger) + +HTTP logging for Chopper client with curl command support. + +
+ +
+ +### [talker_grpc_logger](https://pub.dev/packages/talker_grpc_logger) + +gRPC request/response logging with token obfuscation. + +
+ +
+ +
diff --git a/website/docs/integrations/bloc.md b/website/docs/integrations/bloc.md new file mode 100644 index 000000000..5db8cb3ec --- /dev/null +++ b/website/docs/integrations/bloc.md @@ -0,0 +1,98 @@ +# BLoC Logger + +Pub + +Lightweight and customizable [BLoC](https://pub.dev/packages/bloc) state management logger built on Talker. + +## Installation + +```yaml +dependencies: + talker_bloc_logger: ^5.1.13 +``` + +## Basic Usage + +Set `TalkerBlocObserver` as the global BLoC observer: + +```dart +import 'package:talker_bloc_logger/talker_bloc_logger.dart'; + +Bloc.observer = TalkerBlocObserver(); +``` + +That's it! All BLoC events, transitions, state changes, creations, and closings will be logged. + +## Using with Talker + +Pass your existing Talker instance: + +```dart +import 'package:talker_bloc_logger/talker_bloc_logger.dart'; +import 'package:talker/talker.dart'; + +final talker = Talker(); +Bloc.observer = TalkerBlocObserver(talker: talker); +``` + +## Customization + +### Toggle Event Types + +```dart +Bloc.observer = TalkerBlocObserver( + settings: TalkerBlocLoggerSettings( + enabled: true, + printEvents: true, + printTransitions: true, + printChanges: true, + printCreations: true, + printClosings: true, + ), +); +``` + +### Truncate State/Event Data + +By default, full data is printed. You can truncate it: + +```dart +Bloc.observer = TalkerBlocObserver( + settings: TalkerBlocLoggerSettings( + printEventFullData: false, + printStateFullData: false, + ), +); +``` + +### Filter Logs + +Log only specific BLoCs: + +```dart +Bloc.observer = TalkerBlocObserver( + settings: TalkerBlocLoggerSettings( + // Only log AuthBloc transitions + transitionFilter: (bloc, transition) => + bloc.runtimeType.toString() == 'AuthBloc', + // Only log AuthBloc events + eventFilter: (bloc, event) => + bloc.runtimeType.toString() == 'AuthBloc', + ), +); +``` + +## Settings Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `enabled` | `bool` | `true` | Enable/disable logging | +| `printEvents` | `bool` | `true` | Log BLoC events | +| `printTransitions` | `bool` | `true` | Log state transitions | +| `printChanges` | `bool` | `true` | Log state changes | +| `printCreations` | `bool` | `true` | Log BLoC creations | +| `printClosings` | `bool` | `true` | Log BLoC closings | +| `printEventFullData` | `bool` | `true` | Print full event data | +| `printStateFullData` | `bool` | `true` | Print full state data | +| `transitionFilter` | `Function?` | `null` | Filter transitions to log | +| `eventFilter` | `Function?` | `null` | Filter events to log | diff --git a/website/docs/integrations/chopper.md b/website/docs/integrations/chopper.md new file mode 100644 index 000000000..8b61ce30f --- /dev/null +++ b/website/docs/integrations/chopper.md @@ -0,0 +1,114 @@ +# Chopper Logger + +Pub + +Lightweight and customizable HTTP logger for [Chopper](https://pub.dev/packages/chopper) built on Talker. + +## Installation + +```yaml +dependencies: + talker_chopper_logger: ^5.1.13 +``` + +## Basic Usage + +Add `TalkerChopperLogger` as a Chopper interceptor: + +```dart +import 'package:talker_chopper_logger/talker_chopper_logger.dart'; + +final chopper = ChopperClient( + baseUrl: Uri.parse('https://api.example.com'), + interceptors: [ + TalkerChopperLogger( + settings: const TalkerChopperLoggerSettings(), + ), + ], +); +``` + +## Using with Talker + +```dart +final talker = Talker(); +final chopper = ChopperClient( + interceptors: [ + TalkerChopperLogger(talker: talker), + ], +); +``` + +## Customization + +### Toggle Request/Response Logging + +```dart +TalkerChopperLoggerSettings( + printRequestData: true, + printRequestHeaders: true, + printResponseData: true, + printResponseHeaders: true, + printResponseMessage: true, +) +``` + +### Print curl Command + +```dart +TalkerChopperLoggerSettings( + printRequestCurl: true, +) +``` + +### Hide Sensitive Headers + +```dart +TalkerChopperLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + hiddenHeaders: { + 'authorization', + 'cookie', + 'x-api-key', + }, +) +``` + +### Custom Colors + +```dart +TalkerChopperLoggerSettings( + requestPen: AnsiPen()..cyan(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +### Filtering + +```dart +TalkerChopperLoggerSettings( + requestFilter: (request) => + !request.url.path.contains('/health'), + responseFilter: (response) => + response.statusCode != 304, +) +``` + +## Settings Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `printRequestData` | `bool` | `true` | Print request body | +| `printResponseData` | `bool` | `true` | Print response body | +| `printRequestHeaders` | `bool` | `false` | Print request headers | +| `printResponseHeaders` | `bool` | `false` | Print response headers | +| `printResponseMessage` | `bool` | `true` | Print response status message | +| `printRequestCurl` | `bool` | `false` | Print curl command for requests | +| `hiddenHeaders` | `Set` | `{}` | Headers to hide from output | +| `requestPen` | `AnsiPen?` | pink | Console color for requests | +| `responsePen` | `AnsiPen?` | green | Console color for responses | +| `errorPen` | `AnsiPen?` | red | Console color for errors | +| `requestFilter` | `Function?` | `null` | Filter which requests to log | +| `responseFilter` | `Function?` | `null` | Filter which responses to log | diff --git a/website/docs/integrations/dio.md b/website/docs/integrations/dio.md new file mode 100644 index 000000000..dc2c20a6d --- /dev/null +++ b/website/docs/integrations/dio.md @@ -0,0 +1,100 @@ +# Dio Logger + +Pub + +Lightweight and customizable HTTP logger for [Dio](https://pub.dev/packages/dio) built on Talker. + +## Installation + +```yaml +dependencies: + talker_dio_logger: ^5.1.13 +``` + +## Basic Usage + +Add `TalkerDioLogger` to your Dio interceptors: + +```dart +import 'package:talker_dio_logger/talker_dio_logger.dart'; + +final dio = Dio(); +dio.interceptors.add( + TalkerDioLogger( + settings: const TalkerDioLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + printResponseMessage: true, + ), + ), +); +``` + +## Using with Talker + +Pass your existing Talker instance to unify all logs: + +```dart +final talker = Talker(); +final dio = Dio(); +dio.interceptors.add(TalkerDioLogger(talker: talker)); +``` + +Now all HTTP logs will appear in `TalkerScreen`, history, and stream alongside your other logs. + +## Customization + +### Toggle Request/Response Logging + +```dart +TalkerDioLoggerSettings( + // Enable response body logging + printResponseData: true, + // Disable request body logging + printRequestData: false, + // Include response headers + printResponseHeaders: true, + // Exclude request headers + printRequestHeaders: false, +) +``` + +### Custom Colors + +```dart +TalkerDioLoggerSettings( + requestPen: AnsiPen()..blue(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +### Filtering + +Only log specific requests/responses: + +```dart +TalkerDioLoggerSettings( + // Skip requests to /secure endpoints + requestFilter: (RequestOptions options) => + !options.path.contains('/secure'), + // Skip 301 redirects + responseFilter: (response) => + response.statusCode != 301, +) +``` + +## Settings Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `printRequestData` | `bool` | `true` | Print request body | +| `printResponseData` | `bool` | `true` | Print response body | +| `printRequestHeaders` | `bool` | `false` | Print request headers | +| `printResponseHeaders` | `bool` | `false` | Print response headers | +| `printResponseMessage` | `bool` | `true` | Print response status message | +| `requestPen` | `AnsiPen?` | pink | Console color for requests | +| `responsePen` | `AnsiPen?` | green | Console color for responses | +| `errorPen` | `AnsiPen?` | red | Console color for errors | +| `requestFilter` | `Function?` | `null` | Filter which requests to log | +| `responseFilter` | `Function?` | `null` | Filter which responses to log | diff --git a/website/docs/integrations/grpc.md b/website/docs/integrations/grpc.md new file mode 100644 index 000000000..d7e93f243 --- /dev/null +++ b/website/docs/integrations/grpc.md @@ -0,0 +1,95 @@ +# gRPC Logger + +Pub + +Lightweight and customizable [gRPC](https://pub.dev/packages/grpc) logger built on Talker. + +## Installation + +```yaml +dependencies: + talker_grpc_logger: ^5.1.13 +``` + +## Basic Usage + +Add `TalkerGrpcLogger` as a gRPC client interceptor: + +```dart +import 'package:talker_grpc_logger/talker_grpc_logger.dart'; + +final channel = ClientChannel( + 'localhost', + port: 50051, + options: ChannelOptions( + credentials: const ChannelCredentials.insecure(), + ), +); + +final client = YourServiceClient( + channel, + interceptors: [TalkerGrpcLogger()], +); +``` + +## Using with Talker + +```dart +final talker = Talker(); +final client = YourServiceClient( + channel, + interceptors: [ + TalkerGrpcLogger(talker: talker), + ], +); +``` + +## Customization + +### Token Obfuscation + +gRPC metadata often contains sensitive tokens. Talker can obfuscate them: + +```dart +TalkerGrpcLogger( + settings: TalkerGrpcLoggerSettings( + // Token will show as "eyJh***" instead of the full value + printRequestHeaders: true, + hideTokens: true, + ), +) +``` + +### Toggle Request/Response Logging + +```dart +TalkerGrpcLoggerSettings( + printRequestData: true, + printResponseData: true, + printRequestHeaders: true, + printResponseHeaders: true, +) +``` + +### Custom Colors + +```dart +TalkerGrpcLoggerSettings( + requestPen: AnsiPen()..cyan(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +## Settings Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `printRequestData` | `bool` | `true` | Print request message data | +| `printResponseData` | `bool` | `true` | Print response message data | +| `printRequestHeaders` | `bool` | `false` | Print request metadata | +| `printResponseHeaders` | `bool` | `false` | Print response trailers | +| `hideTokens` | `bool` | `false` | Obfuscate tokens in metadata | +| `requestPen` | `AnsiPen?` | pink | Console color for requests | +| `responsePen` | `AnsiPen?` | green | Console color for responses | +| `errorPen` | `AnsiPen?` | red | Console color for errors | diff --git a/website/docs/integrations/http.md b/website/docs/integrations/http.md new file mode 100644 index 000000000..fe7fa5c90 --- /dev/null +++ b/website/docs/integrations/http.md @@ -0,0 +1,107 @@ +# HTTP Logger + +Pub + +Lightweight HTTP logger for the [http_interceptor](https://pub.dev/packages/http_interceptor) package built on Talker. + +## Installation + +```yaml +dependencies: + talker_http_logger: ^5.1.13 +``` + +## Basic Usage + +Add `TalkerHttpLogger` to your `InterceptedClient`: + +```dart +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:talker_http_logger/talker_http_logger.dart'; + +void main() async { + final client = InterceptedClient.build(interceptors: [ + TalkerHttpLogger( + settings: const TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + printResponseMessage: true, + ), + ), + ]); + + await client.get("https://google.com".toUri()); +} +``` + +## Using with Talker + +```dart +final talker = Talker(); +final client = InterceptedClient.build( + interceptors: [ + TalkerHttpLogger(talker: talker), + ], +); +``` + +## Customization + +### Print curl Command + +```dart +TalkerHttpLoggerSettings( + printRequestCurl: true, +) +``` + +### Hide Sensitive Headers + +```dart +TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + hiddenHeaders: { + 'authorization', + 'cookie', + }, +) +``` + +### Custom Colors + +```dart +TalkerHttpLoggerSettings( + requestPen: AnsiPen()..blue(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +### Filtering + +```dart +TalkerHttpLoggerSettings( + requestFilter: (Request request) => + !request.url.path.contains('/secure'), + responseFilter: (Response response) => + response.statusCode != 301, +) +``` + +## Settings Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `printRequestData` | `bool` | `true` | Print request body | +| `printResponseData` | `bool` | `true` | Print response body | +| `printRequestHeaders` | `bool` | `false` | Print request headers | +| `printResponseHeaders` | `bool` | `false` | Print response headers | +| `printResponseMessage` | `bool` | `true` | Print response status message | +| `printRequestCurl` | `bool` | `false` | Print curl command for requests | +| `hiddenHeaders` | `Set` | `{}` | Headers to hide from output | +| `requestPen` | `AnsiPen?` | pink | Console color for requests | +| `responsePen` | `AnsiPen?` | green | Console color for responses | +| `errorPen` | `AnsiPen?` | red | Console color for errors | +| `requestFilter` | `Function?` | `null` | Filter which requests to log | +| `responseFilter` | `Function?` | `null` | Filter which responses to log | diff --git a/website/docs/integrations/riverpod.md b/website/docs/integrations/riverpod.md new file mode 100644 index 000000000..4ab88a6dc --- /dev/null +++ b/website/docs/integrations/riverpod.md @@ -0,0 +1,96 @@ +# Riverpod Logger + +Pub + +Lightweight and customizable [Riverpod](https://pub.dev/packages/riverpod) state management logger built on Talker. + +## Installation + +```yaml +dependencies: + talker_riverpod_logger: ^5.1.13 +``` + +## Basic Usage + +Add `TalkerRiverpodObserver` to your `ProviderScope`: + +```dart +import 'package:talker_riverpod_logger/talker_riverpod_logger.dart'; + +runApp( + ProviderScope( + observers: [TalkerRiverpodObserver()], + child: MyApp(), + ), +); +``` + +All provider lifecycle events — add, update, dispose, and fail — will be logged automatically. + +## Using with Talker + +Pass your existing Talker instance: + +```dart +final talker = Talker(); + +runApp( + ProviderScope( + observers: [TalkerRiverpodObserver(talker: talker)], + child: MyApp(), + ), +); +``` + +## Customization + +### Toggle Event Types + +```dart +TalkerRiverpodObserver( + settings: TalkerRiverpodLoggerSettings( + enabled: true, + printProviderAdded: true, + printProviderUpdated: true, + printProviderDisposed: true, + printProviderFailed: true, + ), +) +``` + +### Truncate Data + +```dart +TalkerRiverpodObserver( + settings: TalkerRiverpodLoggerSettings( + printStateFullData: false, + ), +) +``` + +### Filter Logs + +Only log specific providers: + +```dart +TalkerRiverpodObserver( + settings: TalkerRiverpodLoggerSettings( + // Only log specific providers + providerFilter: (provider) => + provider.name?.contains('auth') ?? false, + ), +) +``` + +## Settings Reference + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `enabled` | `bool` | `true` | Enable/disable logging | +| `printProviderAdded` | `bool` | `true` | Log provider additions | +| `printProviderUpdated` | `bool` | `true` | Log provider updates | +| `printProviderDisposed` | `bool` | `true` | Log provider disposals | +| `printProviderFailed` | `bool` | `true` | Log provider failures | +| `printStateFullData` | `bool` | `true` | Print full state data | +| `providerFilter` | `Function?` | `null` | Filter which providers to log | diff --git a/website/docs/packages/talker-flutter.md b/website/docs/packages/talker-flutter.md new file mode 100644 index 000000000..a8657f475 --- /dev/null +++ b/website/docs/packages/talker-flutter.md @@ -0,0 +1,242 @@ +# talker_flutter + +Pub + +Flutter extensions for Talker — colored logs, in-app log viewer, error alerts, route observer, and more. + +## Installation + +```yaml +dependencies: + talker_flutter: ^5.1.13 +``` + +## Setup + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); +``` + +::: tip Why TalkerFlutter.init()? +This method uses the optimal logging output for each platform: +- **Web** — `print()` +- **iOS / macOS** — `dart:developer.log` (preserves colored output) +- **Android / Windows / Linux** — `debugPrint` (avoids message truncation) +::: + +You get the same `Talker` instance as `Talker()` but with platform-optimized logging. + +## TalkerScreen + +A full-featured log viewer widget. View, filter, search, and share all logs directly in your app. + +```dart +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), +); +``` + +### TalkerScreen Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `talker` | `Talker` | _required_ | Talker instance to display logs from | +| `theme` | `TalkerScreenTheme` | `TalkerScreenTheme()` | Visual theme configuration | +| `appBarTitle` | `String` | `'Talker'` | Custom title for the app bar | +| `appBarLeading` | `Widget?` | `null` | Custom leading widget for the app bar | +| `itemsBuilder` | `TalkerDataBuilder?` | `null` | Custom builder for log item cards | +| `customSettings` | `List` | `[]` | Additional settings groups | +| `isLogsExpanded` | `bool` | `true` | Whether logs are initially expanded | +| `isLogOrderReversed` | `bool` | `true` | Whether latest logs appear on top | + +### Custom Theme Colors + +Customize colors for any log type in TalkerScreen: + +```dart +TalkerScreen( + talker: talker, + theme: const TalkerScreenTheme( + logColors: { + // Override default log type colors + TalkerLogType.httpResponse.key: Color(0xFF26FF3C), + TalkerLogType.error.key: Colors.redAccent, + TalkerLogType.info.key: Color.fromARGB(255, 0, 255, 247), + + // Custom log keys + 'custom_log_key': Colors.green, + }, + ), +) +``` + +### TalkerScreenTheme + +```dart +TalkerScreenTheme( + backgroundColor: Colors.grey[800]!, + cardColor: Colors.grey[700]!, + textColor: Colors.white, + logColors: { + // Your log colors... + }, +) +``` + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `backgroundColor` | `Color` | Dark gray | Screen background color | +| `cardColor` | `Color` | Gray | Log card background color | +| `textColor` | `Color` | White | Log text color | +| `logColors` | `Map` | _(defaults)_ | Custom colors for log types | + +## TalkerView + +Same as `TalkerScreen` but without `Scaffold` — for embedding in your own layouts: + +```dart +Scaffold( + appBar: AppBar(title: Text('My Custom Screen')), + body: TalkerView( + talker: talker, + theme: const TalkerScreenTheme(), + ), +) +``` + +## TalkerMonitor + +A quick status overview showing counts of HTTP requests, exceptions, errors, warnings, etc. + +`TalkerMonitor` is accessible from the `TalkerScreen` settings page. It provides a filtered summary of your application's health at a glance. + +## TalkerWrapper + +Show error alerts and status messages automatically in your UI: + +```dart +TalkerWrapper( + talker: talker, + options: const TalkerWrapperOptions( + enableErrorAlerts: true, + ), + child: MyApp(), +) +``` + +### TalkerWrapperOptions + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `enableErrorAlerts` | `bool` | `true` | Show SnackBars for errors | +| `enableExceptionAlerts` | `bool` | `true` | Show SnackBars for exceptions | + +## TalkerListener + +Listen to Talker events in the widget tree: + +```dart +TalkerListener( + talker: talker, + listener: (data) { + if (data is TalkerException || data is TalkerError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(data.displayMessage)), + ); + } + }, + child: MyApp(), +) +``` + +## TalkerBuilder + +Build UI reactively based on Talker logs: + +```dart +TalkerBuilder( + talker: talker, + builder: (context, data) { + return ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + return ListTile(title: Text(data[index].message)); + }, + ); + }, +) +``` + +## TalkerRouteObserver + +Log navigation events. Works with any routing package. + +### Navigator + +```dart +MaterialApp( + navigatorObservers: [ + TalkerRouteObserver(talker), + ], +) +``` + +### go_router + +```dart +GoRouter( + observers: [TalkerRouteObserver(talker)], +) +``` + +### auto_route v7+ + +```dart +MaterialApp.router( + routerConfig: _appRouter.config( + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), +) +``` + +### auto_route (legacy) + +```dart +MaterialApp.router( + routerDelegate: AutoRouterDelegate( + appRouter, + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), +) +``` + +See the full [Route Logging](/guides/routing) guide for more details. + +## runTalkerZonedGuarded + +Catch all uncaught errors in your app zone: + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +void main() { + final talker = TalkerFlutter.init(); + + runTalkerZonedGuarded( + talker, + () => runApp(MyApp()), + ); +} +``` + +## API Reference + +Full API documentation is available on [pub.dev](https://pub.dev/documentation/talker_flutter/latest/). diff --git a/website/docs/packages/talker-logger.md b/website/docs/packages/talker-logger.md new file mode 100644 index 000000000..4d91b7f36 --- /dev/null +++ b/website/docs/packages/talker-logger.md @@ -0,0 +1,141 @@ +# talker_logger + +Pub + +Customizable pretty logger for Dart/Flutter apps. Can be used as a standalone package or as part of the Talker ecosystem. + +## Installation + +```yaml +dependencies: + talker_logger: ^5.1.13 +``` + +## Basic Usage + +```dart +import 'package:talker_logger/talker_logger.dart'; + +final logger = TalkerLogger(); + +logger.debug('debug'); +logger.info('info'); +logger.warning('warning'); +logger.error('error'); +logger.critical('critical'); +logger.good('good'); +logger.verbose('verbose'); +``` + +## Custom Log Level + +Use the `log()` method to specify level and color: + +```dart +logger.log('custom message', level: LogLevel.info); +logger.log('colored log', pen: AnsiPen()..xterm(49)); +``` + +## Log Levels + +| Level | Description | +|-------|-------------| +| `LogLevel.critical` | Critical errors that require immediate attention | +| `LogLevel.error` | Runtime errors | +| `LogLevel.warning` | Potential issues | +| `LogLevel.info` | Informational messages | +| `LogLevel.debug` | Debug information | +| `LogLevel.verbose` | Detailed tracing information | +| `LogLevel.good` | Positive status messages | + +## Filtering + +Filter logs by setting the minimum log level: + +```dart +final logger = TalkerLogger( + settings: const TalkerLoggerSettings( + level: LogLevel.critical, + ), +); + +// This will print +logger.critical('critical error'); + +// This will NOT print (level is below critical) +logger.info('info message'); +``` + +## Custom Formatting + +### Built-in Formatter Customization + +```dart +final logger = TalkerLogger( + settings: TalkerLoggerSettings( + colors: { + LogLevel.critical: AnsiPen()..yellow(), + LogLevel.error: AnsiPen()..yellow(), + LogLevel.info: AnsiPen()..yellow(), + }, + maxLineWidth: 20, + lineSymbol: '#', + enableColors: true, + ), +); +``` + +### Custom Formatter + +Implement your own `LoggerFormatter`: + +```dart +class ColoredLoggerFormatter implements LoggerFormatter { + @override + String fmt(LogDetails details, TalkerLoggerSettings settings) { + final msg = details.message?.toString() ?? ''; + final coloredMsg = msg + .split('\n') + .map((e) => details.pen.write(e)) + .toList() + .join('\n'); + return coloredMsg; + } +} + +final logger = TalkerLogger( + formatter: ColoredLoggerFormatter(), +); + +logger.debug('debug'); +logger.info('info'); +logger.warning('warning'); +logger.error('error'); +``` + +## TalkerLoggerSettings + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `level` | `LogLevel` | `LogLevel.verbose` | Minimum log level to output | +| `enableColors` | `bool` | `true` | Whether to use ANSI colors in output | +| `maxLineWidth` | `int` | `110` | Maximum width of log line decorations | +| `lineSymbol` | `String` | `'─'` | Character used for line decorations | +| `colors` | `Map` | _(defaults)_ | Custom colors for each log level | + +## Custom Output + +You can redirect where logs are printed: + +```dart +final logger = TalkerLogger( + output: (message) { + // Send to file, remote server, etc. + myCustomOutput(message); + }, +); +``` + +## API Reference + +Full API documentation is available on [pub.dev](https://pub.dev/documentation/talker_logger/latest/). diff --git a/website/docs/packages/talker.md b/website/docs/packages/talker.md new file mode 100644 index 000000000..24be247ab --- /dev/null +++ b/website/docs/packages/talker.md @@ -0,0 +1,265 @@ +# talker + +Pub + +The core Dart package for logging and error handling. Works in any Dart project — server-side, CLI, or Flutter. + +## Installation + +```yaml +dependencies: + talker: ^5.1.13 +``` + +## Basic Logging + +```dart +import 'package:talker/talker.dart'; + +final talker = Talker(); + +// Log messages with different levels +talker.info('App started'); +talker.debug('Loading configuration...'); +talker.warning('Cache is almost full'); +talker.error('Failed to save file'); +talker.critical('Database connection lost!'); +talker.verbose('Detailed trace information'); +talker.good('Operation completed successfully!'); +``` + +## Error Handling + +Handle exceptions and errors with full `StackTrace` support: + +```dart +try { + throw Exception('Something went wrong'); +} catch (e, st) { + talker.handle(e, st, 'Exception in data processing'); +} +``` + +The `handle()` method automatically distinguishes between `Exception` and `Error` types. + +## Advanced Logging + +Use the `log()` method for maximum control: + +```dart +talker.log( + 'Server error', + logLevel: LogLevel.critical, + exception: Exception('Connection timeout'), + stackTrace: stackTrace, + pen: AnsiPen()..red(), +); +``` + +## TalkerSettings + +Configure Talker behavior through `TalkerSettings`: + +```dart +final talker = Talker( + settings: TalkerSettings( + /// Enable/disable all talker processes + enabled: true, + /// Enable/disable saving logs in history + useHistory: true, + /// Maximum number of history items + maxHistoryItems: 1000, + /// Enable/disable console output + useConsoleLogs: true, + /// Time format for log timestamps + timeFormat: TimeFormat.timeAndSeconds, + ), +); +``` + +### Settings Fields + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `enabled` | `bool` | `true` | Master switch for all Talker operations | +| `useHistory` | `bool` | `true` | Whether to save logs in history | +| `maxHistoryItems` | `int` | `1000` | Maximum number of stored history items | +| `useConsoleLogs` | `bool` | `true` | Whether to print logs to console | +| `timeFormat` | `TimeFormat` | `timeAndSeconds` | Timestamp format for logs | +| `titles` | `Map` | _(defaults)_ | Custom titles for log types | +| `colors` | `Map` | _(defaults)_ | Custom colors for log types | + +## Custom Log Colors + +Set your own color for any type of log: + +```dart +final talker = Talker( + settings: TalkerSettings( + colors: { + TalkerLogType.httpResponse.key: AnsiPen()..red(), + TalkerLogType.error.key: AnsiPen()..green(), + TalkerLogType.info.key: AnsiPen()..blue(), + + // Custom log keys + 'custom_log_key': AnsiPen()..yellow(), + }, + ), +); +``` + +### Default Color Scheme + +| Log Type | Color | +|----------|-------| +| `critical` | Red | +| `error` | Red | +| `exception` | Red | +| `warning` | Yellow | +| `info` | Blue | +| `debug` | Gray | +| `verbose` | Gray | +| `httpRequest` | Pink (xterm 219) | +| `httpResponse` | Green (xterm 46) | +| `httpError` | Red | +| `route` | Purple (xterm 135) | + +## Custom Log Titles + +Override the default titles for any log type: + +```dart +final talker = Talker( + settings: TalkerSettings( + titles: { + TalkerLogType.exception.key: 'Whatever you want', + TalkerLogType.error.key: 'E', + TalkerLogType.info.key: 'i', + + // Custom log keys + 'custom_log_key': 'My Custom Title', + }, + ), +); +``` + +## Custom Log Types + +Create your own log type by extending `TalkerLog`: + +```dart +class HttpLog extends TalkerLog { + HttpLog(String super.message); + + static const logKey = 'http_log'; + static final logTitle = 'HTTP'; + static final logPen = AnsiPen()..cyan(); + + @override + String get title => logTitle; + + @override + String get key => logKey; + + @override + AnsiPen get pen => logPen; +} + +// Usage +talker.logCustom(HttpLog('GET /api/users — 200 OK')); +``` + +See the full guide on [Custom Log Types](/guides/custom-logs). + +## TalkerObserver + +Observe all Talker events from the outside — perfect for sending data to external services: + +```dart +class MyTalkerObserver extends TalkerObserver { + @override + void onError(TalkerError err) { + // Send to Crashlytics, Sentry, etc. + super.onError(err); + } + + @override + void onException(TalkerException exception) { + // Send to error tracking service + super.onException(exception); + } + + @override + void onLog(TalkerDataInterface log) { + // Send to Grafana, analytics backend, etc. + super.onLog(log); + } +} + +final talker = Talker(observer: MyTalkerObserver()); +``` + +See the [Crashlytics Integration](/guides/crashlytics) guide for a full example. + +## Stream + +Listen to all Talker events via a broadcast stream: + +```dart +talker.stream.listen((data) { + print('New event: ${data.message}'); +}); +``` + +## History + +Access the full log history: + +```dart +// Get all history +final logs = talker.history; + +// Clear history +talker.cleanHistory(); +``` + +## Filtering + +Use `TalkerFilter` to select specific logs: + +```dart +final talker = Talker( + filter: BaseTalkerFilter( + titles: ['error', 'exception'], + types: [TalkerError, TalkerException], + ), +); +``` + +## Enable / Disable + +Control Talker at runtime: + +```dart +// Stop all logging and error handling +talker.disable(); + +// Resume operations +talker.enable(); +``` + +## Runtime Configuration + +Reconfigure Talker after creation: + +```dart +talker.configure( + settings: TalkerSettings(enabled: true), + logger: TalkerLogger(), + observer: MyTalkerObserver(), +); +``` + +## API Reference + +Full API documentation is available on [pub.dev](https://pub.dev/documentation/talker/latest/). diff --git a/website/docs/ru/examples.md b/website/docs/ru/examples.md new file mode 100644 index 000000000..42d91fa80 --- /dev/null +++ b/website/docs/ru/examples.md @@ -0,0 +1,67 @@ +# Примеры + +## Интерактивные примеры в DartPad + +### Базовое логирование + +Попробуйте уровни логирования Talker в браузере: + +
+ +
+ +### Обработка ошибок + +Посмотрите, как Talker обрабатывает исключения с stack trace: + +
+ +
+ +### Пользовательские типы логов + +Создавайте собственные типы логов: + +
+ +
+ +## Пример Shop App + +Полноценное Flutter-приложение, демонстрирующее интеграцию Talker: + +- Настройка Talker с DI +- HTTP-логирование с Dio +- Логирование BLoC state management +- TalkerScreen и TalkerMonitor +- Обработка ошибок с TalkerWrapper +- Логирование маршрутов + +📂 [Посмотреть Shop App на GitHub](https://github.com/Frezyx/talker/tree/master/examples/shop_app_example) + +## Веб-демо + +Попробуйте полностью интерактивную Flutter веб-демку с `TalkerScreen` и `TalkerMonitor`: + +🌐 [Открыть веб-демо](https://frezyx.github.io/talker) + + diff --git a/website/docs/ru/getting-started/installation.md b/website/docs/ru/getting-started/installation.md new file mode 100644 index 000000000..70ddd6813 --- /dev/null +++ b/website/docs/ru/getting-started/installation.md @@ -0,0 +1,62 @@ +# Установка + +## Выберите пакет + +Talker — это модульная экосистема. Устанавливайте только то, что вам нужно: + +### Для Dart-проектов + +```yaml +dependencies: + talker: ^5.1.13 +``` + +### Для Flutter-проектов + +```yaml +dependencies: + talker_flutter: ^5.1.13 +``` + +::: tip +`talker_flutter` уже включает `talker` как зависимость — не нужно добавлять оба пакета. +::: + +### Пакеты интеграций + +Добавьте любой из них рядом с основным пакетом: + +```yaml +dependencies: + # HTTP-логирование для Dio + talker_dio_logger: ^5.1.13 + + # HTTP-логирование для пакета http + talker_http_logger: ^5.1.13 + + # Логирование BLoC state management + talker_bloc_logger: ^5.1.13 + + # Логирование Riverpod state management + talker_riverpod_logger: ^5.1.13 + + # HTTP-логирование для Chopper + talker_chopper_logger: ^5.1.13 + + # gRPC-логирование + talker_grpc_logger: ^5.1.13 +``` + +## Запустите pub get + +После добавления зависимостей выполните: + +```bash +flutter pub get +# или для Dart-проектов +dart pub get +``` + +## Далее + +Теперь, когда Talker установлен, переходите к [Быстрому старту](/ru/getting-started/quick-start)! diff --git a/website/docs/ru/getting-started/quick-start.md b/website/docs/ru/getting-started/quick-start.md new file mode 100644 index 000000000..01c78542f --- /dev/null +++ b/website/docs/ru/getting-started/quick-start.md @@ -0,0 +1,88 @@ +# Быстрый старт + +## Базовое использование (Dart) + +Создайте экземпляр Talker и начните логирование: + +```dart +import 'package:talker/talker.dart'; + +final talker = Talker(); + +void main() { + // Логи с разными уровнями + talker.info('Приложение запущено'); + talker.debug('Загрузка конфигурации...'); + talker.warning('Кэш почти заполнен'); + talker.error('Не удалось сохранить файл'); + talker.critical('Соединение с базой данных потеряно!'); + talker.verbose('Детальная информация трассировки'); + talker.good('Операция выполнена успешно!'); + + // Обработка исключений + try { + throw Exception('Что-то пошло не так'); + } catch (e, st) { + talker.handle(e, st, 'Ошибка обработки данных'); + } +} +``` + +## Использование во Flutter + +Для Flutter-приложений используйте `TalkerFlutter.init()` для оптимальной платформенной настройки: + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +void main() { + // Обработка исключений и ошибок + try { + // ваш код... + } catch (e, st) { + talker.handle(e, st, 'Исключение в ...'); + } + + // Логирование информации приложения + talker.info('Приложение запущено'); + talker.critical('Хьюстон, у нас проблема!'); + talker.error('Сервис недоступен'); +} +``` + +::: tip Почему TalkerFlutter.init()? +Большинство Flutter-пакетов для логирования либо обрезают сообщения в консоли, либо не могут отображать цветные сообщения на iOS. `TalkerFlutter.init()` использует оптимальный метод для каждой платформы: +- **Web** — `print()` +- **iOS / macOS** — `dart:developer.log` +- **Android / Windows / Linux** — `debugPrint` +::: + +## Просмотр логов в приложении + +Используйте `TalkerScreen` для просмотра логов прямо в приложении: + +```dart +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), +); +``` + +## HTTP-логирование + +Если вы используете Dio, добавьте логирование одной строкой: + +```dart +final dio = Dio(); +dio.interceptors.add(TalkerDioLogger(talker: talker)); +``` + +## Что дальше? + +- Узнайте о [возможностях Talker](/ru/packages/talker) — кастомные логи, цвета, заголовки, observer +- Изучите [Flutter-виджеты](/ru/packages/talker-flutter) — TalkerScreen, TalkerMonitor, TalkerWrapper +- Настройте [интеграции](/ru/integrations/dio) — Dio, BLoC, Riverpod и другие +- Прочитайте [руководства](/ru/guides/custom-logs) — пользовательские типы логов, интеграция с Crashlytics diff --git a/website/docs/ru/getting-started/why-talker.md b/website/docs/ru/getting-started/why-talker.md new file mode 100644 index 000000000..d6a7aa997 --- /dev/null +++ b/website/docs/ru/getting-started/why-talker.md @@ -0,0 +1,69 @@ +# Почему Talker? + +## Проблема + +При разработке Dart и Flutter приложений вам нужно: + +- **Понимать, где возникают ошибки** — как можно быстрее +- **Логировать действия приложения** — HTTP-запросы, изменения состояний, навигацию +- **Обрабатывать исключения корректно** — без крашей приложения +- **Делиться отчётами об ошибках** — с командой или сервисами крэш-репортинга +- **Отлаживать на реальных устройствах** — когда консоль недоступна + +Большинство существующих решений для логирования покрывают лишь часть этих задач. В итоге вы получаете разрозненные инструменты, несогласованные форматы и отсутствие единого представления. + +## Решение + +Talker предоставляет **единую систему** для логирования и обработки ошибок в вашем приложении: + +### Единое логирование + +Один API для всего — от простых отладочных сообщений до HTTP-запросов и событий state management. Все логи имеют единый формат, историю и вывод. + +### Богатая обработка ошибок + +Автоматическое различение типов `Exception` и `Error`, захват `StackTrace` и маршрутизация всего через единый обработчик с `TalkerObserver`. + +### Flutter-first UI + +Встроенный `TalkerScreen` позволяет просматривать, фильтровать, искать и делиться логами прямо в приложении — консоль не нужна. `TalkerMonitor` даёт быстрый обзор состояния. + +### Модульные интеграции + +Выбирайте только то, что нужно. Каждая интеграция (Dio, BLoC, Riverpod и т.д.) — это отдельный лёгкий пакет, подключаемый к тому же экземпляру Talker. + +### Полная кастомизация + +Настраивается всё — цвета, заголовки, форматы логов, фильтры. Создавайте свои типы логов с полным контролем. + +## Ключевые возможности + +| Возможность | Talker | +|-------------|--------| +| Уровни логов (info, debug, warning, error, critical, verbose, good) | ✅ | +| Цветной вывод в консоль | ✅ | +| Пользовательские типы логов | ✅ | +| Обработка Exception/Error со StackTrace | ✅ | +| История логов | ✅ | +| Просмотр логов в приложении (TalkerScreen) | ✅ | +| Монитор статуса (TalkerMonitor) | ✅ | +| UI-алерты об ошибках (TalkerWrapper) | ✅ | +| Логирование навигации | ✅ | +| HTTP-логирование для Dio | ✅ | +| HTTP-логирование для http | ✅ | +| Логирование BLoC | ✅ | +| Логирование Riverpod | ✅ | +| HTTP-логирование для Chopper | ✅ | +| gRPC-логирование | ✅ | +| Observer для внешних сервисов (Crashlytics, Sentry) | ✅ | +| Оптимизированный вывод для платформ (iOS, Android, Web) | ✅ | +| 100% покрытие тестами | ✅ | +| Работает с любым state management | ✅ | + +## Совместимость + +- **Dart** >= 2.15 +- **Flutter** >= 3.0 +- **Платформы**: Android, iOS, Web, macOS, Windows, Linux +- **State management**: Работает с любым — BLoC, Riverpod, Provider, GetX, MobX и т.д. +- **Крэш-репортинг**: Firebase Crashlytics, Sentry или любое кастомное решение diff --git a/website/docs/ru/guides/crashlytics.md b/website/docs/ru/guides/crashlytics.md new file mode 100644 index 000000000..f87c2cf4e --- /dev/null +++ b/website/docs/ru/guides/crashlytics.md @@ -0,0 +1,189 @@ +# Интеграция с Crashlytics и Sentry + +Используйте `TalkerObserver` для отправки ошибок, исключений и логов из Talker во внешние сервисы крэш-репортинга и аналитики. + +## Как работает TalkerObserver + +`TalkerObserver` слушает все события Talker и предоставляет колбэки для каждого типа: + +```dart +abstract class TalkerObserver { + void onError(TalkerError err) {} + void onException(TalkerException exception) {} + void onLog(TalkerDataInterface log) {} +} +``` + +- **onError** — вызывается когда `talker.handle()` перехватывает `Error` +- **onException** — вызывается когда `talker.handle()` перехватывает `Exception` +- **onLog** — вызывается для каждого события лога + +## Firebase Crashlytics + +```dart +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:talker/talker.dart'; + +class CrashlyticsTalkerObserver extends TalkerObserver { + CrashlyticsTalkerObserver(this.crashlytics); + + final FirebaseCrashlytics crashlytics; + + @override + void onError(TalkerError err) { + crashlytics.recordError( + err.error, + err.stackTrace, + reason: err.message, + fatal: err.logLevel == LogLevel.critical, + ); + } + + @override + void onException(TalkerException exception) { + crashlytics.recordError( + exception.exception, + exception.stackTrace, + reason: exception.message, + ); + } + + @override + void onLog(TalkerDataInterface log) { + // Breadcrumbs для не-ошибочных событий + crashlytics.log(log.generateTextMessage()); + } +} +``` + +### Настройка + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + final crashlytics = FirebaseCrashlytics.instance; + + final talker = TalkerFlutter.init( + observer: CrashlyticsTalkerObserver(crashlytics), + ); + + // Теперь все talker.handle() автоматически отправляются в Crashlytics + try { + throw Exception('Ошибка оплаты'); + } catch (e, st) { + talker.handle(e, st, 'Ошибка обработки платежа'); + // ^ Логирование в консоль, сохранение в историю И отправка в Crashlytics + } +} +``` + +## Sentry + +```dart +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:talker/talker.dart'; + +class SentryTalkerObserver extends TalkerObserver { + @override + void onError(TalkerError err) { + Sentry.captureException( + err.error, + stackTrace: err.stackTrace, + ); + } + + @override + void onException(TalkerException exception) { + Sentry.captureException( + exception.exception, + stackTrace: exception.stackTrace, + ); + } + + @override + void onLog(TalkerDataInterface log) { + Sentry.addBreadcrumb( + Breadcrumb( + message: log.generateTextMessage(), + level: _mapLevel(log), + timestamp: DateTime.now(), + ), + ); + } + + SentryLevel _mapLevel(TalkerDataInterface log) { + if (log is TalkerError || log is TalkerException) { + return SentryLevel.error; + } + return SentryLevel.info; + } +} +``` + +### Настройка + +```dart +void main() async { + await SentryFlutter.init( + (options) { + options.dsn = 'YOUR_SENTRY_DSN'; + }, + appRunner: () { + final talker = TalkerFlutter.init( + observer: SentryTalkerObserver(), + ); + runApp(MyApp(talker: talker)); + }, + ); +} +``` + +## Кастомный аналитический сервис + +Тот же паттерн работает для любого сервиса: + +```dart +class AnalyticsTalkerObserver extends TalkerObserver { + AnalyticsTalkerObserver(this.analytics); + + final MyAnalyticsService analytics; + + @override + void onError(TalkerError err) { + analytics.trackError( + name: 'app_error', + error: err.error.toString(), + stackTrace: err.stackTrace?.toString(), + ); + } + + @override + void onException(TalkerException exception) { + analytics.trackError( + name: 'app_exception', + error: exception.exception.toString(), + stackTrace: exception.stackTrace?.toString(), + ); + } + + @override + void onLog(TalkerDataInterface log) { + analytics.trackEvent( + name: 'app_log', + params: {'message': log.message, 'title': log.title}, + ); + } +} +``` + +## Лучшие практики + +1. **Не блокируйте основной поток** — SDK крэш-репортинга обрабатывают это внутренне, но будьте внимательны с тяжёлой обработкой в `onLog`. + +2. **Используйте уровни логов разумно** — отправляйте только `error` и `critical` в Crashlytics, используйте `onLog` для breadcrumbs. + +3. **Комбинируйте с TalkerWrapper** — показывайте ошибки пользователям через SnackBar, одновременно отправляя в крэш-сервис. + +4. **Тестируйте observer** — пишите юнит-тесты, чтобы убедиться, что observer вызывается корректно. diff --git a/website/docs/ru/guides/custom-error-messages.md b/website/docs/ru/guides/custom-error-messages.md new file mode 100644 index 000000000..642d8db38 --- /dev/null +++ b/website/docs/ru/guides/custom-error-messages.md @@ -0,0 +1,158 @@ +# Кастомные сообщения об ошибках + +Показывайте user-friendly алерты и кастомные сообщения в Flutter-приложении с помощью виджетов Talker. + +## TalkerWrapper + +Самый простой способ показать алерты об ошибках. Оберните приложение (или поддерево) в `TalkerWrapper`: + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +TalkerWrapper( + talker: talker, + options: const TalkerWrapperOptions( + enableErrorAlerts: true, + enableExceptionAlerts: true, + ), + child: MaterialApp( + home: HomeScreen(), + ), +) +``` + +Теперь при каждом вызове `talker.handle()` с исключением или ошибкой автоматически появится SnackBar внизу экрана. + +## TalkerListener + +Для большего контроля используйте `TalkerListener`: + +```dart +TalkerListener( + talker: talker, + listener: (data) { + if (data is TalkerException) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Ошибка: ${data.displayMessage}'), + backgroundColor: Colors.red, + action: SnackBarAction( + label: 'Подробнее', + textColor: Colors.white, + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => TalkerScreen(talker: talker), + ), + ); + }, + ), + ), + ); + } + }, + child: MyApp(), +) +``` + +## TalkerBuilder + +Реактивно стройте UI на основе логов: + +```dart +TalkerBuilder( + talker: talker, + builder: (context, data) { + final errors = data.whereType().toList(); + final exceptions = data.whereType().toList(); + + if (errors.isEmpty && exceptions.isEmpty) { + return const SizedBox.shrink(); + } + + return Container( + padding: const EdgeInsets.all(8), + color: Colors.red.shade100, + child: Text( + '${errors.length + exceptions.length} ошибок произошло', + style: const TextStyle(color: Colors.red), + ), + ); + }, +) +``` + +## Диалог для критических ошибок + +```dart +TalkerListener( + talker: talker, + listener: (data) { + if (data is TalkerError && data.logLevel == LogLevel.critical) { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Критическая ошибка'), + content: Text(data.displayMessage), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => TalkerScreen(talker: talker), + ), + ); + }, + child: const Text('Просмотр логов'), + ), + ], + ), + ); + } + }, + child: MyApp(), +) +``` + +## Комбинация с Crashlytics + +Используйте `TalkerWrapper` для алертов пользователю И `TalkerObserver` для крэш-репортинга одновременно: + +```dart +final talker = TalkerFlutter.init( + observer: CrashlyticsTalkerObserver(FirebaseCrashlytics.instance), +); + +TalkerWrapper( + talker: talker, + options: const TalkerWrapperOptions( + enableErrorAlerts: true, + enableExceptionAlerts: true, + ), + child: MaterialApp(home: HomeScreen()), +) +``` + +Теперь каждая ошибка: +1. Логируется в консоль +2. Сохраняется в историю +3. Показывается пользователю как SnackBar +4. Отправляется в Crashlytics + +## Лучшие практики + +1. **Не показывайте технические детали** — используйте `displayMessage` или создавайте понятные сообщения. + +2. **Используйте TalkerWrapper для разработки** — быстрый способ видеть ошибки. Замените на кастомный UI для продакшена. + +3. **Предлагайте «Просмотр логов»** — позвольте тестировщикам открыть `TalkerScreen` из алертов. + +4. **Ограничивайте частоту алертов** — при массовых ошибках используйте debounce, чтобы не засыпать пользователя SnackBar-ами. diff --git a/website/docs/ru/guides/custom-logs.md b/website/docs/ru/guides/custom-logs.md new file mode 100644 index 000000000..6116927eb --- /dev/null +++ b/website/docs/ru/guides/custom-logs.md @@ -0,0 +1,172 @@ +# Пользовательские типы логов + +Talker позволяет создавать собственные типы логов с полным контролем над внешним видом, форматом вывода и категоризацией. + +## Зачем свои логи? + +Встроенные типы логов (`info`, `debug`, `warning` и т.д.) покрывают большинство случаев. Но когда нужны доменно-специфичные логи — аналитические события, бизнес-правила или кастомные форматы HTTP — можно определить свои. + +## Создание пользовательского лога + +Расширьте класс `TalkerLog`: + +```dart +import 'package:talker/talker.dart'; + +class AnalyticsLog extends TalkerLog { + AnalyticsLog(String event, this.params) : super(event); + + final Map params; + + /// Уникальный ключ для этого типа лога + @override + String get key => 'analytics'; + + /// Заголовок в консоли и TalkerScreen + @override + String get title => 'ANALYTICS'; + + /// Цвет в консоли + @override + AnsiPen get pen => AnsiPen()..magenta(); + + /// Полный формат сообщения + @override + String generateTextMessage({TimeFormat? timeFormat}) { + return '${super.generateTextMessage(timeFormat: timeFormat)}\n' + 'Event: $message\n' + 'Params: $params'; + } +} +``` + +## Использование + +Используйте `logCustom()` для отправки пользовательского лога: + +```dart +final talker = Talker(); + +talker.logCustom( + AnalyticsLog('user_login', {'method': 'google', 'first_time': true}), +); +``` + +Это: +- Выведет цветное сообщение в консоль с заголовком `ANALYTICS` +- Сохранит лог в истории Talker +- Покажет в `TalkerScreen` +- Передаст через `talker.stream` +- Вызовет `TalkerObserver.onLog()` + +## Регистрация кастомных ключей + +Для использования кастомных цветов и заголовков через `TalkerSettings`: + +```dart +final talker = Talker( + settings: TalkerSettings( + colors: { + 'analytics': AnsiPen()..magenta(), + }, + titles: { + 'analytics': 'ANALYTICS', + }, + ), +); +``` + +::: tip +Цвета и заголовки из настроек имеют приоритет над геттерами `pen` и `title` самого лога, что позволяет переопределять их без изменения класса. +::: + +## Примеры из практики + +### Лог бизнес-события + +```dart +class BusinessEventLog extends TalkerLog { + BusinessEventLog(String eventName, {this.revenue, this.userId}) + : super(eventName); + + final double? revenue; + final String? userId; + + @override + String get key => 'business_event'; + + @override + String get title => 'BUSINESS'; + + @override + AnsiPen get pen => AnsiPen()..xterm(220); // Золотой + + @override + String generateTextMessage({TimeFormat? timeFormat}) { + var msg = super.generateTextMessage(timeFormat: timeFormat); + if (revenue != null) msg += '\nВыручка: \$${revenue!.toStringAsFixed(2)}'; + if (userId != null) msg += '\nПользователь: $userId'; + return msg; + } +} + +talker.logCustom( + BusinessEventLog('purchase_completed', revenue: 29.99, userId: 'usr_123'), +); +``` + +### Лог SQL-запроса + +```dart +class DbQueryLog extends TalkerLog { + DbQueryLog(String query, this.duration) : super(query); + + final Duration duration; + + @override + String get key => 'db_query'; + + @override + String get title => 'DB'; + + @override + AnsiPen get pen => duration.inMilliseconds > 1000 + ? (AnsiPen()..red()) // Медленный запрос + : (AnsiPen()..green()); // Нормальный + + @override + String generateTextMessage({TimeFormat? timeFormat}) { + return '${super.generateTextMessage(timeFormat: timeFormat)}\n' + 'Запрос: $message\n' + 'Длительность: ${duration.inMilliseconds}мс'; + } +} +``` + +## Фильтрация + +Пользовательские логи фильтруются так же, как встроенные: + +```dart +final talker = Talker( + filter: BaseTalkerFilter( + titles: ['ANALYTICS', 'DB'], + ), +); +``` + +## Отображение в TalkerScreen + +Цвет в Flutter UI можно настроить через `TalkerScreenTheme`: + +```dart +TalkerScreen( + talker: talker, + theme: const TalkerScreenTheme( + logColors: { + 'analytics': Color(0xFFE040FB), + 'db_query': Color(0xFF4CAF50), + }, + ), +) +``` diff --git a/website/docs/ru/guides/routing.md b/website/docs/ru/guides/routing.md new file mode 100644 index 000000000..024c65a1d --- /dev/null +++ b/website/docs/ru/guides/routing.md @@ -0,0 +1,129 @@ +# Логирование маршрутов + +Логируйте навигационные события Flutter-приложения с `TalkerRouteObserver`. Работает с Navigator, go_router, auto_route и любым маршрутизатором, совместимым с NavigatorObserver. + +## Настройка + +### Navigator 1.0 + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +MaterialApp( + navigatorObservers: [ + TalkerRouteObserver(talker), + ], + home: HomeScreen(), +) +``` + +### go_router + +```dart +import 'package:go_router/go_router.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +final router = GoRouter( + observers: [TalkerRouteObserver(talker)], + routes: [ + GoRoute(path: '/', builder: (_, __) => HomeScreen()), + GoRoute(path: '/profile', builder: (_, __) => ProfileScreen()), + ], +); + +MaterialApp.router(routerConfig: router) +``` + +### auto_route v7+ + +```dart +import 'package:auto_route/auto_route.dart'; +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); + +MaterialApp.router( + routerConfig: _appRouter.config( + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), +) +``` + +### auto_route (legacy) + +```dart +MaterialApp.router( + routerDelegate: AutoRouterDelegate( + appRouter, + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), + routeInformationParser: appRouter.defaultRouteParser(), +) +``` + +## Что логируется + +`TalkerRouteObserver` логирует следующие события: + +| Событие | Описание | Пример | +|---------|----------|--------| +| **Push** | Новый маршрут добавлен в стек | `Route pushed: /profile` | +| **Pop** | Маршрут удалён из стека | `Route popped: /profile` | +| **Replace** | Маршрут заменён | `Route replaced: /login → /home` | +| **Remove** | Маршрут удалён | `Route removed: /splash` | + +Все логи навигации отображаются в консоли, TalkerScreen и истории с фиолетовым цветом по умолчанию. + +## Просмотр в TalkerScreen + +```dart +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), +); +``` + +Логи маршрутов имеют цветовую маркировку и легко отличаются от других типов. Их можно фильтровать в TalkerScreen, выбрав фильтр "Route". + +## Кастомизация + +### Свой цвет маршрутов + +```dart +final talker = Talker( + settings: TalkerSettings( + colors: { + TalkerLogType.route.key: AnsiPen()..cyan(), + }, + ), +); +``` + +### Свой заголовок + +```dart +final talker = Talker( + settings: TalkerSettings( + titles: { + TalkerLogType.route.key: 'NAV', + }, + ), +); +``` + +## Лучшие практики + +1. **Используйте один экземпляр Talker** во всём приложении — логи маршрутов, HTTP, BLoC-событий и ошибок в одном месте. + +2. **Комбинируйте с TalkerScreen** — добавьте кнопку отладки или жест встряхивания для открытия `TalkerScreen` и просмотра полной истории навигации. + +3. **Используйте с Crashlytics** — логи маршрутов — отличные breadcrumbs для отчётов о крэшах. diff --git a/website/docs/ru/index.md b/website/docs/ru/index.md new file mode 100644 index 000000000..eb0777819 --- /dev/null +++ b/website/docs/ru/index.md @@ -0,0 +1,222 @@ +--- +layout: home + +hero: + name: Talker + text: Продвинутый обработчик ошибок и логгер + tagline: Логируйте действия приложения, перехватывайте ошибки, показывайте алерты и отправляйте отчёты для Dart и Flutter + image: + src: /logo.png + alt: Talker + actions: + - theme: brand + text: Начать + link: /ru/getting-started/installation + - theme: alt + text: GitHub + link: https://github.com/Frezyx/talker + - theme: alt + text: Веб-демо + link: https://frezyx.github.io/talker + +features: + - icon: 📝 + title: Мощное логирование + details: Логи с несколькими уровнями — info, debug, warning, error, critical, verbose и good. Полная кастомизация цветов и поддержка пользовательских типов логов. + - icon: 🛡️ + title: Обработка ошибок + details: Перехват Exception и Error с StackTrace. Отправка данных в Crashlytics, Sentry или ваш собственный сервис через TalkerObserver. + - icon: 📱 + title: Flutter UI + details: Встроенный TalkerScreen для просмотра логов в приложении, TalkerMonitor для быстрого обзора, TalkerWrapper для алертов об ошибках и TalkerRouteObserver для навигации. + - icon: 🔌 + title: Богатые интеграции + details: Поддержка Dio, http, BLoC, Riverpod, Chopper и gRPC из коробки. Каждая с полной кастомизацией, фильтрацией и цветами. + - icon: 🎨 + title: Полная кастомизация + details: Свои цвета, заголовки, форматтеры и фильтры для любого типа логов. Создавайте собственные типы логов с полным контролем. + - icon: ✅ + title: Готов к продакшену + details: 100% покрытие тестами, MIT лицензия, совместим с любым state management, работает на всех платформах — Android, iOS, Web, macOS, Windows, Linux. +--- + + + +
+ +## Попробуйте Talker прямо сейчас + +Поиграйтесь с Talker прямо в браузере — без установки: + +
+ +
+ +
+ +
+ +## Экосистема пакетов + +Talker спроектирован для любого уровня кастомизации — выбирайте то, что вам нужно: + +
+ +
+ +### [talker](https://pub.dev/packages/talker) + +Основной Dart-пакет для логирования и обработки ошибок. Ядро экосистемы. + +
+ +
+ +### [talker_flutter](https://pub.dev/packages/talker_flutter) + +Flutter-расширения — цветные логи, TalkerScreen, TalkerMonitor, route observer и многое другое. + +
+ +
+ +### [talker_logger](https://pub.dev/packages/talker_logger) + +Настраиваемый pretty-логгер для Dart/Flutter приложений. Может использоваться отдельно. + +
+ +
+ +### [talker_dio_logger](https://pub.dev/packages/talker_dio_logger) + +Красивое логирование HTTP запросов/ответов для Dio. + +
+ +
+ +### [talker_http_logger](https://pub.dev/packages/talker_http_logger) + +HTTP-логирование для стандартного пакета http с http_interceptor. + +
+ +
+ +### [talker_bloc_logger](https://pub.dev/packages/talker_bloc_logger) + +Логирование state management для BLoC — события, переходы, изменения состояний. + +
+ +
+ +### [talker_riverpod_logger](https://pub.dev/packages/talker_riverpod_logger) + +Логирование жизненного цикла провайдеров Riverpod — add, update, dispose, fail. + +
+ +
+ +### [talker_chopper_logger](https://pub.dev/packages/talker_chopper_logger) + +HTTP-логирование для Chopper с поддержкой curl команд. + +
+ +
+ +### [talker_grpc_logger](https://pub.dev/packages/talker_grpc_logger) + +Логирование gRPC запросов/ответов с обфускацией токенов. + +
+ +
+ +
diff --git a/website/docs/ru/integrations/bloc.md b/website/docs/ru/integrations/bloc.md new file mode 100644 index 000000000..6e8295ee5 --- /dev/null +++ b/website/docs/ru/integrations/bloc.md @@ -0,0 +1,91 @@ +# BLoC Logger + +Pub + +Лёгкий и настраиваемый логгер [BLoC](https://pub.dev/packages/bloc) state management на базе Talker. + +## Установка + +```yaml +dependencies: + talker_bloc_logger: ^5.1.13 +``` + +## Базовое использование + +Установите `TalkerBlocObserver` как глобальный BLoC observer: + +```dart +import 'package:talker_bloc_logger/talker_bloc_logger.dart'; + +Bloc.observer = TalkerBlocObserver(); +``` + +Готово! Все события BLoC, переходы, изменения состояний, создания и закрытия будут логироваться. + +## Использование с Talker + +```dart +final talker = Talker(); +Bloc.observer = TalkerBlocObserver(talker: talker); +``` + +## Настройка + +### Переключение типов событий + +```dart +Bloc.observer = TalkerBlocObserver( + settings: TalkerBlocLoggerSettings( + enabled: true, + printEvents: true, + printTransitions: true, + printChanges: true, + printCreations: true, + printClosings: true, + ), +); +``` + +### Обрезка данных + +По умолчанию выводятся полные данные. Можно обрезать: + +```dart +Bloc.observer = TalkerBlocObserver( + settings: TalkerBlocLoggerSettings( + printEventFullData: false, + printStateFullData: false, + ), +); +``` + +### Фильтрация логов + +Логировать только определённые BLoC: + +```dart +Bloc.observer = TalkerBlocObserver( + settings: TalkerBlocLoggerSettings( + transitionFilter: (bloc, transition) => + bloc.runtimeType.toString() == 'AuthBloc', + eventFilter: (bloc, event) => + bloc.runtimeType.toString() == 'AuthBloc', + ), +); +``` + +## Справка по настройкам + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `enabled` | `bool` | `true` | Включить/выключить логирование | +| `printEvents` | `bool` | `true` | Логировать события | +| `printTransitions` | `bool` | `true` | Логировать переходы | +| `printChanges` | `bool` | `true` | Логировать изменения состояний | +| `printCreations` | `bool` | `true` | Логировать создания | +| `printClosings` | `bool` | `true` | Логировать закрытия | +| `printEventFullData` | `bool` | `true` | Полные данные событий | +| `printStateFullData` | `bool` | `true` | Полные данные состояний | +| `transitionFilter` | `Function?` | `null` | Фильтр переходов | +| `eventFilter` | `Function?` | `null` | Фильтр событий | diff --git a/website/docs/ru/integrations/chopper.md b/website/docs/ru/integrations/chopper.md new file mode 100644 index 000000000..9e25e2e4d --- /dev/null +++ b/website/docs/ru/integrations/chopper.md @@ -0,0 +1,111 @@ +# Chopper Logger + +Pub + +Лёгкий и настраиваемый HTTP-логгер для [Chopper](https://pub.dev/packages/chopper) на базе Talker. + +## Установка + +```yaml +dependencies: + talker_chopper_logger: ^5.1.13 +``` + +## Базовое использование + +Добавьте `TalkerChopperLogger` как интерцептор Chopper: + +```dart +import 'package:talker_chopper_logger/talker_chopper_logger.dart'; + +final chopper = ChopperClient( + baseUrl: Uri.parse('https://api.example.com'), + interceptors: [ + TalkerChopperLogger( + settings: const TalkerChopperLoggerSettings(), + ), + ], +); +``` + +## Использование с Talker + +```dart +final talker = Talker(); +final chopper = ChopperClient( + interceptors: [ + TalkerChopperLogger(talker: talker), + ], +); +``` + +## Настройка + +### Переключение логирования запросов/ответов + +```dart +TalkerChopperLoggerSettings( + printRequestData: true, + printRequestHeaders: true, + printResponseData: true, + printResponseHeaders: true, +) +``` + +### Вывод curl-команды + +```dart +TalkerChopperLoggerSettings( + printRequestCurl: true, +) +``` + +### Скрытие конфиденциальных заголовков + +```dart +TalkerChopperLoggerSettings( + hiddenHeaders: { + 'authorization', + 'cookie', + 'x-api-key', + }, +) +``` + +### Пользовательские цвета + +```dart +TalkerChopperLoggerSettings( + requestPen: AnsiPen()..cyan(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +### Фильтрация + +```dart +TalkerChopperLoggerSettings( + requestFilter: (request) => + !request.url.path.contains('/health'), + responseFilter: (response) => + response.statusCode != 304, +) +``` + +## Справка по настройкам + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `printRequestData` | `bool` | `true` | Выводить тело запроса | +| `printResponseData` | `bool` | `true` | Выводить тело ответа | +| `printRequestHeaders` | `bool` | `false` | Выводить заголовки запроса | +| `printResponseHeaders` | `bool` | `false` | Выводить заголовки ответа | +| `printResponseMessage` | `bool` | `true` | Выводить статус ответа | +| `printRequestCurl` | `bool` | `false` | Выводить curl-команду | +| `hiddenHeaders` | `Set` | `{}` | Скрытые заголовки | +| `requestPen` | `AnsiPen?` | розовый | Цвет запросов | +| `responsePen` | `AnsiPen?` | зелёный | Цвет ответов | +| `errorPen` | `AnsiPen?` | красный | Цвет ошибок | +| `requestFilter` | `Function?` | `null` | Фильтр запросов | +| `responseFilter` | `Function?` | `null` | Фильтр ответов | diff --git a/website/docs/ru/integrations/dio.md b/website/docs/ru/integrations/dio.md new file mode 100644 index 000000000..d4c3a59e2 --- /dev/null +++ b/website/docs/ru/integrations/dio.md @@ -0,0 +1,94 @@ +# Dio Logger + +Pub + +Лёгкий и настраиваемый HTTP-логгер для [Dio](https://pub.dev/packages/dio) на базе Talker. + +## Установка + +```yaml +dependencies: + talker_dio_logger: ^5.1.13 +``` + +## Базовое использование + +Добавьте `TalkerDioLogger` в интерцепторы Dio: + +```dart +import 'package:talker_dio_logger/talker_dio_logger.dart'; + +final dio = Dio(); +dio.interceptors.add( + TalkerDioLogger( + settings: const TalkerDioLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + printResponseMessage: true, + ), + ), +); +``` + +## Использование с Talker + +Передайте существующий экземпляр Talker для объединения всех логов: + +```dart +final talker = Talker(); +final dio = Dio(); +dio.interceptors.add(TalkerDioLogger(talker: talker)); +``` + +Теперь все HTTP-логи будут отображаться в `TalkerScreen`, истории и stream наряду с остальными логами. + +## Настройка + +### Переключение логирования запросов/ответов + +```dart +TalkerDioLoggerSettings( + printResponseData: true, + printRequestData: false, + printResponseHeaders: true, + printRequestHeaders: false, +) +``` + +### Пользовательские цвета + +```dart +TalkerDioLoggerSettings( + requestPen: AnsiPen()..blue(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +### Фильтрация + +Логировать только определённые запросы/ответы: + +```dart +TalkerDioLoggerSettings( + requestFilter: (RequestOptions options) => + !options.path.contains('/secure'), + responseFilter: (response) => + response.statusCode != 301, +) +``` + +## Справка по настройкам + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `printRequestData` | `bool` | `true` | Выводить тело запроса | +| `printResponseData` | `bool` | `true` | Выводить тело ответа | +| `printRequestHeaders` | `bool` | `false` | Выводить заголовки запроса | +| `printResponseHeaders` | `bool` | `false` | Выводить заголовки ответа | +| `printResponseMessage` | `bool` | `true` | Выводить статус ответа | +| `requestPen` | `AnsiPen?` | розовый | Цвет запросов в консоли | +| `responsePen` | `AnsiPen?` | зелёный | Цвет ответов в консоли | +| `errorPen` | `AnsiPen?` | красный | Цвет ошибок в консоли | +| `requestFilter` | `Function?` | `null` | Фильтр запросов | +| `responseFilter` | `Function?` | `null` | Фильтр ответов | diff --git a/website/docs/ru/integrations/grpc.md b/website/docs/ru/integrations/grpc.md new file mode 100644 index 000000000..314701d72 --- /dev/null +++ b/website/docs/ru/integrations/grpc.md @@ -0,0 +1,95 @@ +# gRPC Logger + +Pub + +Лёгкий и настраиваемый [gRPC](https://pub.dev/packages/grpc) логгер на базе Talker. + +## Установка + +```yaml +dependencies: + talker_grpc_logger: ^5.1.13 +``` + +## Базовое использование + +Добавьте `TalkerGrpcLogger` как gRPC-интерцептор: + +```dart +import 'package:talker_grpc_logger/talker_grpc_logger.dart'; + +final channel = ClientChannel( + 'localhost', + port: 50051, + options: ChannelOptions( + credentials: const ChannelCredentials.insecure(), + ), +); + +final client = YourServiceClient( + channel, + interceptors: [TalkerGrpcLogger()], +); +``` + +## Использование с Talker + +```dart +final talker = Talker(); +final client = YourServiceClient( + channel, + interceptors: [ + TalkerGrpcLogger(talker: talker), + ], +); +``` + +## Настройка + +### Обфускация токенов + +gRPC-метаданные часто содержат конфиденциальные токены. Talker может их обфусцировать: + +```dart +TalkerGrpcLogger( + settings: TalkerGrpcLoggerSettings( + // Токен будет отображаться как "eyJh***" вместо полного значения + printRequestHeaders: true, + hideTokens: true, + ), +) +``` + +### Переключение логирования запросов/ответов + +```dart +TalkerGrpcLoggerSettings( + printRequestData: true, + printResponseData: true, + printRequestHeaders: true, + printResponseHeaders: true, +) +``` + +### Пользовательские цвета + +```dart +TalkerGrpcLoggerSettings( + requestPen: AnsiPen()..cyan(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +## Справка по настройкам + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `printRequestData` | `bool` | `true` | Выводить данные запроса | +| `printResponseData` | `bool` | `true` | Выводить данные ответа | +| `printRequestHeaders` | `bool` | `false` | Выводить метаданные запроса | +| `printResponseHeaders` | `bool` | `false` | Выводить трейлеры ответа | +| `hideTokens` | `bool` | `false` | Обфусцировать токены | +| `requestPen` | `AnsiPen?` | розовый | Цвет запросов | +| `responsePen` | `AnsiPen?` | зелёный | Цвет ответов | +| `errorPen` | `AnsiPen?` | красный | Цвет ошибок | diff --git a/website/docs/ru/integrations/http.md b/website/docs/ru/integrations/http.md new file mode 100644 index 000000000..81b0d654f --- /dev/null +++ b/website/docs/ru/integrations/http.md @@ -0,0 +1,106 @@ +# HTTP Logger + +Pub + +Лёгкий HTTP-логгер для [http_interceptor](https://pub.dev/packages/http_interceptor) на базе Talker. + +## Установка + +```yaml +dependencies: + talker_http_logger: ^5.1.13 +``` + +## Базовое использование + +Добавьте `TalkerHttpLogger` в `InterceptedClient`: + +```dart +import 'package:http_interceptor/http_interceptor.dart'; +import 'package:talker_http_logger/talker_http_logger.dart'; + +void main() async { + final client = InterceptedClient.build(interceptors: [ + TalkerHttpLogger( + settings: const TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + ), + ), + ]); + + await client.get("https://google.com".toUri()); +} +``` + +## Использование с Talker + +```dart +final talker = Talker(); +final client = InterceptedClient.build( + interceptors: [ + TalkerHttpLogger(talker: talker), + ], +); +``` + +## Настройка + +### Вывод curl-команды + +```dart +TalkerHttpLoggerSettings( + printRequestCurl: true, +) +``` + +### Скрытие конфиденциальных заголовков + +```dart +TalkerHttpLoggerSettings( + printRequestHeaders: true, + printResponseHeaders: true, + hiddenHeaders: { + 'authorization', + 'cookie', + }, +) +``` + +### Пользовательские цвета + +```dart +TalkerHttpLoggerSettings( + requestPen: AnsiPen()..blue(), + responsePen: AnsiPen()..green(), + errorPen: AnsiPen()..red(), +) +``` + +### Фильтрация + +```dart +TalkerHttpLoggerSettings( + requestFilter: (Request request) => + !request.url.path.contains('/secure'), + responseFilter: (Response response) => + response.statusCode != 301, +) +``` + +## Справка по настройкам + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `printRequestData` | `bool` | `true` | Выводить тело запроса | +| `printResponseData` | `bool` | `true` | Выводить тело ответа | +| `printRequestHeaders` | `bool` | `false` | Выводить заголовки запроса | +| `printResponseHeaders` | `bool` | `false` | Выводить заголовки ответа | +| `printResponseMessage` | `bool` | `true` | Выводить статус ответа | +| `printRequestCurl` | `bool` | `false` | Выводить curl-команду | +| `hiddenHeaders` | `Set` | `{}` | Скрытые заголовки | +| `requestPen` | `AnsiPen?` | розовый | Цвет запросов | +| `responsePen` | `AnsiPen?` | зелёный | Цвет ответов | +| `errorPen` | `AnsiPen?` | красный | Цвет ошибок | +| `requestFilter` | `Function?` | `null` | Фильтр запросов | +| `responseFilter` | `Function?` | `null` | Фильтр ответов | diff --git a/website/docs/ru/integrations/riverpod.md b/website/docs/ru/integrations/riverpod.md new file mode 100644 index 000000000..1aa350ce2 --- /dev/null +++ b/website/docs/ru/integrations/riverpod.md @@ -0,0 +1,91 @@ +# Riverpod Logger + +Pub + +Лёгкий и настраиваемый логгер [Riverpod](https://pub.dev/packages/riverpod) state management на базе Talker. + +## Установка + +```yaml +dependencies: + talker_riverpod_logger: ^5.1.13 +``` + +## Базовое использование + +Добавьте `TalkerRiverpodObserver` в `ProviderScope`: + +```dart +import 'package:talker_riverpod_logger/talker_riverpod_logger.dart'; + +runApp( + ProviderScope( + observers: [TalkerRiverpodObserver()], + child: MyApp(), + ), +); +``` + +Все события жизненного цикла провайдеров — add, update, dispose и fail — будут логироваться автоматически. + +## Использование с Talker + +```dart +final talker = Talker(); + +runApp( + ProviderScope( + observers: [TalkerRiverpodObserver(talker: talker)], + child: MyApp(), + ), +); +``` + +## Настройка + +### Переключение типов событий + +```dart +TalkerRiverpodObserver( + settings: TalkerRiverpodLoggerSettings( + enabled: true, + printProviderAdded: true, + printProviderUpdated: true, + printProviderDisposed: true, + printProviderFailed: true, + ), +) +``` + +### Обрезка данных + +```dart +TalkerRiverpodObserver( + settings: TalkerRiverpodLoggerSettings( + printStateFullData: false, + ), +) +``` + +### Фильтрация логов + +```dart +TalkerRiverpodObserver( + settings: TalkerRiverpodLoggerSettings( + providerFilter: (provider) => + provider.name?.contains('auth') ?? false, + ), +) +``` + +## Справка по настройкам + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `enabled` | `bool` | `true` | Включить/выключить логирование | +| `printProviderAdded` | `bool` | `true` | Логировать добавление провайдеров | +| `printProviderUpdated` | `bool` | `true` | Логировать обновление провайдеров | +| `printProviderDisposed` | `bool` | `true` | Логировать удаление провайдеров | +| `printProviderFailed` | `bool` | `true` | Логировать ошибки провайдеров | +| `printStateFullData` | `bool` | `true` | Полные данные состояний | +| `providerFilter` | `Function?` | `null` | Фильтр провайдеров | diff --git a/website/docs/ru/packages/talker-flutter.md b/website/docs/ru/packages/talker-flutter.md new file mode 100644 index 000000000..438e468d6 --- /dev/null +++ b/website/docs/ru/packages/talker-flutter.md @@ -0,0 +1,190 @@ +# talker_flutter + +Pub + +Flutter-расширения для Talker — цветные логи, просмотр логов в приложении, алерты об ошибках, observer навигации и многое другое. + +## Установка + +```yaml +dependencies: + talker_flutter: ^5.1.13 +``` + +## Настройка + +```dart +import 'package:talker_flutter/talker_flutter.dart'; + +final talker = TalkerFlutter.init(); +``` + +::: tip Почему TalkerFlutter.init()? +Этот метод использует оптимальный способ вывода для каждой платформы: +- **Web** — `print()` +- **iOS / macOS** — `dart:developer.log` (сохраняет цвета) +- **Android / Windows / Linux** — `debugPrint` (не обрезает сообщения) +::: + +## TalkerScreen + +Полнофункциональный виджет просмотра логов. Просматривайте, фильтруйте, ищите и делитесь логами прямо в приложении. + +```dart +Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TalkerScreen(talker: talker), + ), +); +``` + +### Параметры TalkerScreen + +| Параметр | Тип | По умолч. | Описание | +|----------|-----|-----------|----------| +| `talker` | `Talker` | _обязат._ | Экземпляр Talker для отображения | +| `theme` | `TalkerScreenTheme` | `TalkerScreenTheme()` | Тема оформления | +| `appBarTitle` | `String` | `'Talker'` | Заголовок AppBar | +| `appBarLeading` | `Widget?` | `null` | Ведущий виджет AppBar | +| `itemsBuilder` | `TalkerDataBuilder?` | `null` | Кастомный билдер карточек | +| `isLogsExpanded` | `bool` | `true` | Логи развёрнуты по умолчанию | +| `isLogOrderReversed` | `bool` | `true` | Новые логи сверху | + +### Кастомные цвета темы + +```dart +TalkerScreen( + talker: talker, + theme: const TalkerScreenTheme( + logColors: { + TalkerLogType.httpResponse.key: Color(0xFF26FF3C), + TalkerLogType.error.key: Colors.redAccent, + TalkerLogType.info.key: Color.fromARGB(255, 0, 255, 247), + 'custom_log_key': Colors.green, + }, + ), +) +``` + +## TalkerView + +То же, что TalkerScreen, но без `Scaffold` — для встраивания в свои layout-ы: + +```dart +Scaffold( + appBar: AppBar(title: Text('Мой экран')), + body: TalkerView( + talker: talker, + theme: const TalkerScreenTheme(), + ), +) +``` + +## TalkerMonitor + +Быстрый обзор статуса — количество HTTP-запросов, исключений, ошибок, предупреждений и т.д. Доступен из настроек `TalkerScreen`. + +## TalkerWrapper + +Автоматические алерты об ошибках в UI: + +```dart +TalkerWrapper( + talker: talker, + options: const TalkerWrapperOptions( + enableErrorAlerts: true, + ), + child: MyApp(), +) +``` + +## TalkerListener + +Слушайте события Talker в дереве виджетов: + +```dart +TalkerListener( + talker: talker, + listener: (data) { + if (data is TalkerException || data is TalkerError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(data.displayMessage)), + ); + } + }, + child: MyApp(), +) +``` + +## TalkerBuilder + +Реактивно стройте UI на основе логов: + +```dart +TalkerBuilder( + talker: talker, + builder: (context, data) { + return ListView.builder( + itemCount: data.length, + itemBuilder: (context, index) { + return ListTile(title: Text(data[index].message)); + }, + ); + }, +) +``` + +## TalkerRouteObserver + +Логируйте события навигации. Работает с любым пакетом маршрутизации. + +### Navigator + +```dart +MaterialApp( + navigatorObservers: [ + TalkerRouteObserver(talker), + ], +) +``` + +### go_router + +```dart +GoRouter( + observers: [TalkerRouteObserver(talker)], +) +``` + +### auto_route v7+ + +```dart +MaterialApp.router( + routerConfig: _appRouter.config( + navigatorObservers: () => [ + TalkerRouteObserver(talker), + ], + ), +) +``` + +Подробнее в руководстве [Логирование маршрутов](/ru/guides/routing). + +## runTalkerZonedGuarded + +Перехват всех неперехваченных ошибок: + +```dart +void main() { + final talker = TalkerFlutter.init(); + + runTalkerZonedGuarded( + talker, + () => runApp(MyApp()), + ); +} +``` + +## Справка по API + +Полная документация API доступна на [pub.dev](https://pub.dev/documentation/talker_flutter/latest/). diff --git a/website/docs/ru/packages/talker-logger.md b/website/docs/ru/packages/talker-logger.md new file mode 100644 index 000000000..5b46c6cb8 --- /dev/null +++ b/website/docs/ru/packages/talker-logger.md @@ -0,0 +1,121 @@ +# talker_logger + +Pub + +Настраиваемый pretty-логгер для Dart/Flutter приложений. Может использоваться как самостоятельный пакет или как часть экосистемы Talker. + +## Установка + +```yaml +dependencies: + talker_logger: ^5.1.13 +``` + +## Базовое использование + +```dart +import 'package:talker_logger/talker_logger.dart'; + +final logger = TalkerLogger(); + +logger.debug('debug'); +logger.info('info'); +logger.warning('warning'); +logger.error('error'); +logger.critical('critical'); +logger.good('good'); +logger.verbose('verbose'); +``` + +## Произвольный уровень лога + +```dart +logger.log('сообщение', level: LogLevel.info); +logger.log('цветной лог', pen: AnsiPen()..xterm(49)); +``` + +## Уровни логов + +| Уровень | Описание | +|---------|----------| +| `LogLevel.critical` | Критические ошибки, требующие немедленного внимания | +| `LogLevel.error` | Ошибки выполнения | +| `LogLevel.warning` | Потенциальные проблемы | +| `LogLevel.info` | Информационные сообщения | +| `LogLevel.debug` | Отладочная информация | +| `LogLevel.verbose` | Детальная информация трассировки | +| `LogLevel.good` | Позитивные статусные сообщения | + +## Фильтрация + +Фильтруйте логи, установив минимальный уровень: + +```dart +final logger = TalkerLogger( + settings: const TalkerLoggerSettings( + level: LogLevel.critical, + ), +); + +// Это будет выведено +logger.critical('критическая ошибка'); + +// Это НЕ будет выведено (уровень ниже critical) +logger.info('информационное сообщение'); +``` + +## Пользовательское форматирование + +### Настройка встроенного форматтера + +```dart +final logger = TalkerLogger( + settings: TalkerLoggerSettings( + colors: { + LogLevel.critical: AnsiPen()..yellow(), + LogLevel.error: AnsiPen()..yellow(), + LogLevel.info: AnsiPen()..yellow(), + }, + maxLineWidth: 20, + lineSymbol: '#', + enableColors: true, + ), +); +``` + +### Свой форматтер + +Реализуйте собственный `LoggerFormatter`: + +```dart +class ColoredLoggerFormatter implements LoggerFormatter { + @override + String fmt(LogDetails details, TalkerLoggerSettings settings) { + final msg = details.message?.toString() ?? ''; + final coloredMsg = msg + .split('\n') + .map((e) => details.pen.write(e)) + .toList() + .join('\n'); + return coloredMsg; + } +} + +final logger = TalkerLogger( + formatter: ColoredLoggerFormatter(), +); +``` + +## Настройки TalkerLoggerSettings + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `level` | `LogLevel` | `LogLevel.verbose` | Минимальный уровень для вывода | +| `enableColors` | `bool` | `true` | Использовать ANSI-цвета | +| `maxLineWidth` | `int` | `110` | Максимальная ширина линий-разделителей | +| `lineSymbol` | `String` | `'─'` | Символ для линий-разделителей | +| `colors` | `Map` | _(по умолч.)_ | Цвета для каждого уровня | + +## Справка по API + +Полная документация API доступна на [pub.dev](https://pub.dev/documentation/talker_logger/latest/). diff --git a/website/docs/ru/packages/talker.md b/website/docs/ru/packages/talker.md new file mode 100644 index 000000000..0b908141e --- /dev/null +++ b/website/docs/ru/packages/talker.md @@ -0,0 +1,253 @@ +# talker + +Pub + +Основной Dart-пакет для логирования и обработки ошибок. Работает в любом Dart-проекте — серверном, CLI или Flutter. + +## Установка + +```yaml +dependencies: + talker: ^5.1.13 +``` + +## Базовое логирование + +```dart +import 'package:talker/talker.dart'; + +final talker = Talker(); + +// Логи с разными уровнями +talker.info('Приложение запущено'); +talker.debug('Загрузка конфигурации...'); +talker.warning('Кэш почти заполнен'); +talker.error('Не удалось сохранить файл'); +talker.critical('Соединение с БД потеряно!'); +talker.verbose('Детальная информация трассировки'); +talker.good('Операция завершена успешно!'); +``` + +## Обработка ошибок + +Обрабатывайте исключения и ошибки с полной поддержкой `StackTrace`: + +```dart +try { + throw Exception('Что-то пошло не так'); +} catch (e, st) { + talker.handle(e, st, 'Исключение при обработке данных'); +} +``` + +Метод `handle()` автоматически различает типы `Exception` и `Error`. + +## Продвинутое логирование + +Используйте метод `log()` для максимального контроля: + +```dart +talker.log( + 'Ошибка сервера', + logLevel: LogLevel.critical, + exception: Exception('Тайм-аут соединения'), + stackTrace: stackTrace, + pen: AnsiPen()..red(), +); +``` + +## TalkerSettings + +Настройте поведение Talker через `TalkerSettings`: + +```dart +final talker = Talker( + settings: TalkerSettings( + /// Включить/выключить все процессы Talker + enabled: true, + /// Включить/выключить сохранение логов в истории + useHistory: true, + /// Максимальное количество записей в истории + maxHistoryItems: 1000, + /// Включить/выключить вывод в консоль + useConsoleLogs: true, + /// Формат времени для логов + timeFormat: TimeFormat.timeAndSeconds, + ), +); +``` + +### Поля настроек + +| Поле | Тип | По умолч. | Описание | +|------|-----|-----------|----------| +| `enabled` | `bool` | `true` | Главный переключатель всех операций | +| `useHistory` | `bool` | `true` | Сохранять логи в историю | +| `maxHistoryItems` | `int` | `1000` | Максимум записей в истории | +| `useConsoleLogs` | `bool` | `true` | Вывод логов в консоль | +| `timeFormat` | `TimeFormat` | `timeAndSeconds` | Формат временных меток | +| `titles` | `Map` | _(по умолч.)_ | Пользовательские заголовки | +| `colors` | `Map` | _(по умолч.)_ | Пользовательские цвета | + +## Пользовательские цвета + +Задайте свой цвет для любого типа лога: + +```dart +final talker = Talker( + settings: TalkerSettings( + colors: { + TalkerLogType.httpResponse.key: AnsiPen()..red(), + TalkerLogType.error.key: AnsiPen()..green(), + TalkerLogType.info.key: AnsiPen()..blue(), + + // Пользовательские ключи + 'custom_log_key': AnsiPen()..yellow(), + }, + ), +); +``` + +### Цвета по умолчанию + +| Тип лога | Цвет | +|----------|------| +| `critical` | Красный | +| `error` | Красный | +| `exception` | Красный | +| `warning` | Жёлтый | +| `info` | Синий | +| `debug` | Серый | +| `verbose` | Серый | +| `httpRequest` | Розовый (xterm 219) | +| `httpResponse` | Зелёный (xterm 46) | +| `httpError` | Красный | +| `route` | Фиолетовый (xterm 135) | + +## Пользовательские заголовки + +Переопределите заголовки для любого типа лога: + +```dart +final talker = Talker( + settings: TalkerSettings( + titles: { + TalkerLogType.exception.key: 'Что угодно', + TalkerLogType.error.key: 'О', + TalkerLogType.info.key: 'и', + + // Пользовательские ключи + 'custom_log_key': 'Мой заголовок', + }, + ), +); +``` + +## Пользовательские типы логов + +Создайте собственный тип лога, расширив `TalkerLog`: + +```dart +class HttpLog extends TalkerLog { + HttpLog(String super.message); + + static const logKey = 'http_log'; + static final logTitle = 'HTTP'; + static final logPen = AnsiPen()..cyan(); + + @override + String get title => logTitle; + + @override + String get key => logKey; + + @override + AnsiPen get pen => logPen; +} + +// Использование +talker.logCustom(HttpLog('GET /api/users — 200 OK')); +``` + +Полное руководство — [Пользовательские логи](/ru/guides/custom-logs). + +## TalkerObserver + +Наблюдайте за всеми событиями Talker извне — идеально для отправки данных во внешние сервисы: + +```dart +class MyTalkerObserver extends TalkerObserver { + @override + void onError(TalkerError err) { + // Отправить в Crashlytics, Sentry и т.д. + super.onError(err); + } + + @override + void onException(TalkerException exception) { + // Отправить в сервис отслеживания ошибок + super.onException(exception); + } + + @override + void onLog(TalkerDataInterface log) { + // Отправить в Grafana, бэкенд аналитики и т.д. + super.onLog(log); + } +} + +final talker = Talker(observer: MyTalkerObserver()); +``` + +См. руководство [Интеграция с Crashlytics](/ru/guides/crashlytics). + +## Stream + +Слушайте все события Talker через broadcast stream: + +```dart +talker.stream.listen((data) { + print('Новое событие: ${data.message}'); +}); +``` + +## История + +Доступ к полной истории логов: + +```dart +// Получить всю историю +final logs = talker.history; + +// Очистить историю +talker.cleanHistory(); +``` + +## Фильтрация + +Используйте `TalkerFilter` для выбора конкретных логов: + +```dart +final talker = Talker( + filter: BaseTalkerFilter( + titles: ['error', 'exception'], + types: [TalkerError, TalkerException], + ), +); +``` + +## Включение / Выключение + +Управляйте Talker во время выполнения: + +```dart +// Остановить всё логирование и обработку ошибок +talker.disable(); + +// Возобновить работу +talker.enable(); +``` + +## Справка по API + +Полная документация API доступна на [pub.dev](https://pub.dev/documentation/talker/latest/). diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 000000000..ff697aa46 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,2518 @@ +{ + "name": "talker-docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "talker-docs", + "version": "1.0.0", + "devDependencies": { + "vitepress": "^1.6.3" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.15.0.tgz", + "integrity": "sha512-D1QZ8dQx5zC9yrxNao9ER9bojmmzUdL1i2P9waIRiwnZ5fI26YswcCd6VHR/Q4W3PASfVf2My4YQ2FhGGDewTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.49.0.tgz", + "integrity": "sha512-Q1MSRhh4Du9WeLIl1S9O+BDUMaL01uuQtmzCyEzOBtu1xBDr3wvqrTJtfEceEkA5/Nw1BdGSHa6sDT3xTAF90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.49.0.tgz", + "integrity": "sha512-v50elhC80oyQw+8o8BwM+VvPuOo36+3W8VCfR4hsHoafQtGbMtP63U5eNcUydbVsM0py3JLoBaL1yKBK4L01sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.49.0.tgz", + "integrity": "sha512-BDmVDtpDvymfLE5YQ2cPnfWJUVTDJqwpJa03Fsb7yJFJmbeKsUOGsnRkYsTbdzf0FfcvyvBB5zdcbrAIL249bg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.49.0.tgz", + "integrity": "sha512-lDCXsnZDx7zQ5GzSi1EL3l07EbksjrdpMgixFRCdi2QqeBe42HIQJfPPqdWtwrAXjORRopsPx2z+gGYJP/79Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.49.0.tgz", + "integrity": "sha512-5k/KB+DsnesNKvMUEwTKSzExOf5zYbiPg7DVO7g1Y/+bhMb3wmxp9RFwfqwPfmoRTjptqvwhR6a0593tWVkmAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.49.0.tgz", + "integrity": "sha512-pjHNcrdjn7p3RQ5Ql1Baiwfdn9bkS+z4gqONJJP8kuZFqYP8Olthy4G7fl5bCB29UjdUj5EWlaElQKCtPluCtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.0.tgz", + "integrity": "sha512-uGv2P3lcviuaZy8ZOAyN60cZdhOVyjXwaDC27a1qdp3Pb5Azn+lLSJwkHU4TNRpphHmIei9HZuUxwQroujdPjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.49.0.tgz", + "integrity": "sha512-sH10mftYlmvfGbvAgTtHYbCIstmNUdiAkX//0NAyBcJRB6NnZmNsdLxdFGbE8ZqlGXzoe0zcUIau+DxKpXtqCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.49.0.tgz", + "integrity": "sha512-RqhGcVVxLpK+lA0GZKywlQIXsI704flc12nv/hOdrwiuk/Uyhxs46KLM4ngip7wutU+7t0PYZWiVayrqBPN/ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.49.0.tgz", + "integrity": "sha512-kg8omGRvmIPhhqtUqSIpS3regFKWuoWh3WqyUhGk27N4T7q8I++8TsDYsV8vK7oBEzw706m2vUBtN5fw2fDjmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.49.0.tgz", + "integrity": "sha512-BaZ6NTI9VdSbDcsMucdKhTuFFxv6B+3dAZZBozX12fKopYsELh7dBLfZwm8evDCIicmNjIjobi4VNnNshrCSuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.49.0.tgz", + "integrity": "sha512-2nxISxS5xO5DLAj6QzMImgJv6CqpZhJVkhcTFULESR/k4IpbkJTEHmViVTxw9MlrU8B5GfwHevFd7vKL3a7MXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.49.0.tgz", + "integrity": "sha512-S/B94C6piEUXGpN3y5ysmNKMEqdfNVAXYY+FxivEAV5IGJjbEuLZfT8zPPZUWGw9vh6lgP80Hye2G5aVBNIa8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.71", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.71.tgz", + "integrity": "sha512-rNoDFbq1fAYiEexBvrw613/xiUOPEu5MKVV/X8lI64AgdTzLQUUemr9f9fplxUMPoxCBP2rWzlhOEeTHk/Sf0Q==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", + "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.28", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", + "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", + "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.28", + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", + "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", + "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", + "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", + "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/runtime-core": "3.5.28", + "@vue/shared": "3.5.28", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", + "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "vue": "3.5.28" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", + "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.0.tgz", + "integrity": "sha512-Tse7vx7WOvbU+kpq/L3BrBhSWTPbtMa59zIEhMn+Z2NoxZlpcCRUDCRxQ7kDFs1T3CHxDgvb+mDuILiBBpBaAA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@algolia/abtesting": "1.15.0", + "@algolia/client-abtesting": "5.49.0", + "@algolia/client-analytics": "5.49.0", + "@algolia/client-common": "5.49.0", + "@algolia/client-insights": "5.49.0", + "@algolia/client-personalization": "5.49.0", + "@algolia/client-query-suggestions": "5.49.0", + "@algolia/client-search": "5.49.0", + "@algolia/ingestion": "1.49.0", + "@algolia/monitoring": "1.49.0", + "@algolia/recommend": "5.49.0", + "@algolia/requester-browser-xhr": "5.49.0", + "@algolia/requester-fetch": "5.49.0", + "@algolia/requester-node-http": "5.49.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", + "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-sfc": "3.5.28", + "@vue/runtime-dom": "3.5.28", + "@vue/server-renderer": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..cdc25082a --- /dev/null +++ b/website/package.json @@ -0,0 +1,14 @@ +{ + "name": "talker-docs", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "devDependencies": { + "vitepress": "^1.6.3" + } +} diff --git a/website/public/logo.png b/website/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3f1427311e38fb77fe8501f9089bc756ee788776 GIT binary patch literal 1677 zcmZ`(YdqBX9{&C3;xv7D~f34 zPR&lDL&JY5VYXyzT?QFl+%}Bbm_nV|m*>TK@%=vE=XpNQ=Xv+d+aJJCS2I)t0I2&h z=|KQM9K}sl!6^2@zAL2)z{Gj6ya4!_kK2gAD){y&W)KU2G%^5iHUM7~5w7EB&*-8Rgq0XHs@8pw{aY66ZhA!ltUqm? z>(*o-={dj0?x*F5YyG2C=@9$nI8xSwKHRgYDl0`)&U7DNM z)qUWzOr9;qPU##9MGc8ui{4K4me1Zea?4LCqq@q|OWvY0+-B9sy;)QneNgg{yU3!# zY;K4Zf%gpvCwsoyk(J>4Rb8g?gp=i>fIXSb&+IloKh@z($Sf4NcA|=C0c$IBxdFg3qc;mk_-nTrs|)Qf1YfISz%IuCA4^+C6~u zTNWSH=`d|wh4#@yJc52x21%Y-Fzw&2Mw5(&il;8V*Ur>6is<(d!T1XUFx)q+go0Wz zjDEBDqGNL()ATu6aV6>j6`S>OY$fCOx%f3a$hkO}+^=73p289iGv(g(`>R3z=dRv2 zpBK>?I9g|&v6P!TS$uXUR_banwF(t8TbNW83Y;9&R)iAd1wSnXH_aEhlK_q9y_rRY z?roge4gSb>YB&k|3S#e}n^fO($3fKC7N6VEi9mlqS?=}vT?|s+A7;SZ1CJxa zprg5a8VoqNndZ;2Dyw;g_CGAnCm*f@z2bprRWOcOzM})zi$gX^K8GiKPy&Gc=aU7k zCXxoMGlqBDhM+G*y9hf{VygNHz{FhG_&1mxn%mme^6pPp$fP?2fn?sr4X1QrTbm1E zp|&U^t-x3ix|J+0$8xk<9OE@PL>q_-r>lqx-lF8h$kcFn+X{qEgDu;)f10PBYI^}C zMfi8%e=w2Orqs+=p6T52XpsZ6==!#;wFbPGzyGTVh?9PQ-rRd32Mx7$W$q^DAy1U1>gBF#rl$!vFI zYHMH}x#Kj*JPgv&He3H_*#!v#n|g0!Uc^JKdI)5>ex&<#INRIpl1zk$Q__$V6FB?5 z4D-asKBSe`fPr&xQt8A_tuP{+h>RN3hfZQh$db32!H`5b)Hl55OX6!K6Xz@f=L_kl zb1*bky&P6!c!d@=p+bX0=(qd>;EEwe(>kMB8{nc(iQ?S+zm90*kP_n0MmQPWeODx$ zAlY&nUfIyp*!Oea#A*odE+;WCwHsk6F{TH`03Q#c(s}8KXX-N>fKF?f*oGEZU;QwG zrTm9@zz#vBnm^J6o?#!ZKEnvqg!?|B^1J5UJ&{7I@j5!JOon^Rx}DJZmglT4UlbFb z))yh|su>Lfm7}ljc^a>D1}@k4R8Xr$B;wba5~8M*w`%b*(NmvWczNxTbjbGCM5mj5 zkINT3?#0#Se{WflmR^`RdgMm}4=R17e_eg`yY;KCF%kZE+$RU-oePFk<&pOSbZ55M zyxV)8T0e8>m#B~Gugo>CH)kAA-}sZ;{tveB;