diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml
index b0852259a..dd96e716a 100644
--- a/.github/workflows/build-and-test-v3.yml
+++ b/.github/workflows/build-and-test-v3.yml
@@ -223,16 +223,23 @@ jobs:
cd ${{ matrix.template }}
wails3 build
- results:
- if: ${{ always() }}
- runs-on: ubuntu-latest
- name: v3 Build Results
- needs: [test_go, test_js, test_templates]
- steps:
- - run: |
- result="${{ needs.build.result }}"
- if [[ $result == "success" || $result == "skipped" ]]; then
- exit 0
- else
- exit 1
- fi
\ No newline at end of file
+results:
+ if: ${{ always() }}
+ runs-on: ubuntu-latest
+ name: v3 Build Results
+ needs: [test_go, test_js, test_templates]
+ steps:
+ - run: |
+ go_result="${{ needs.test_go.result }}"
+ js_result="${{ needs.test_js.result }}"
+ templates_result="${{ needs.test_templates.result }}"
+
+ if [[ $go_result == "success" || $go_result == "skipped" ]] && \
+ [[ $js_result == "success" || $js_result == "skipped" ]] && \
+ [[ $templates_result == "success" || $templates_result == "skipped" ]]; then
+ echo "All required jobs succeeded or were skipped"
+ exit 0
+ else
+ echo "One or more required jobs failed"
+ exit 1
+ fi
\ No newline at end of file
diff --git a/.github/workflows/pr-v3.yml b/.github/workflows/pr-v3.yml
index a6893f732..7349008ad 100644
--- a/.github/workflows/pr-v3.yml
+++ b/.github/workflows/pr-v3.yml
@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v3
- name: Verify Changed files
- uses: tj-actions/verify-changed-files@v17
+ uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
id: verify-changed-files
with:
files: |
diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml
index eb7275f30..88517c46c 100644
--- a/.github/workflows/publish-npm.yml
+++ b/.github/workflows/publish-npm.yml
@@ -23,7 +23,7 @@ jobs:
- name: Detect committed package.json changes
id: package-json-changes
- uses: tj-actions/changed-files@v45
+ uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
v3/internal/runtime/desktop/@wailsio/runtime/package.json
@@ -32,7 +32,7 @@ jobs:
if: >-
steps.package-json-changes.outputs.any_modified != 'true'
id: source-changes
- uses: tj-actions/changed-files@v45
+ uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
v3/internal/runtime/Taskfile.yaml
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
index 453e4cb85..7533a3a04 100644
--- a/.github/workflows/semgrep.yml
+++ b/.github/workflows/semgrep.yml
@@ -5,6 +5,7 @@ on:
branches:
- main
- master
+ - v3-alpha
paths:
- .github/workflows/semgrep.yml
schedule:
diff --git a/.github/workflows/upload-source-documents.yml b/.github/workflows/upload-source-documents.yml
index df15246fc..69d6c3e48 100644
--- a/.github/workflows/upload-source-documents.yml
+++ b/.github/workflows/upload-source-documents.yml
@@ -15,7 +15,7 @@ jobs:
- name: Verify Changed files
id: changed-files
- uses: tj-actions/changed-files@v41
+ uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
website/**/*.mdx
diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx
index d9af1adf3..786eaa26c 100644
--- a/docs/src/content/docs/changelog.mdx
+++ b/docs/src/content/docs/changelog.mdx
@@ -76,6 +76,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134)
+- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony)
+- Add Notification support by [@popaprozac] in [#4098](https://github.com/wailsapp/wails/pull/4098)
+- Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177)
+
### Fixed
- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062)
@@ -107,8 +111,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony)
- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
+- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116)
- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does
- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125)
+- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188)
+- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony).
+- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219).
### Changed
@@ -137,6 +145,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
+- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133)
+- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac] in [#4171](https://github.com/wailsapp/wails/pull/4171)
## v3.0.0-alpha.9 - 2025-01-13
diff --git a/docs/src/content/docs/guides/file-associations.mdx b/docs/src/content/docs/guides/file-associations.mdx
index 3394b576f..eebc29364 100644
--- a/docs/src/content/docs/guides/file-associations.mdx
+++ b/docs/src/content/docs/guides/file-associations.mdx
@@ -62,6 +62,7 @@ fileAssociations:
| description | Description shown in file properties | Windows |
| iconName | Name of the icon file (without extension) in the build folder | All |
| role | Application's role for this file type (e.g., `Editor`, `Viewer`) | macOS |
+| mimeType | MIME type for the file (e.g., `image/jpeg`) | macOS |
## Listening for File Open Events
@@ -105,6 +106,8 @@ Let's walk through setting up file associations for a simple text editor:
Run `wails3 generate icons --help` for more information.
:::
+ - For macOS add copy statement like `cp build/darwin/documenticon.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources` in the `create:app:bundle:` task.
+
2. ### Configure File Associations
Edit the `build/config.yml` file to add your file associations:
diff --git a/docs/src/content/docs/learn/application-menu.mdx b/docs/src/content/docs/learn/application-menu.mdx
index df344a02c..01f2d271d 100644
--- a/docs/src/content/docs/learn/application-menu.mdx
+++ b/docs/src/content/docs/learn/application-menu.mdx
@@ -16,6 +16,43 @@ Create a new application menu using the `NewMenu` method:
menu := app.NewMenu()
```
+## Setting the Menu
+
+The way to set the menu varies on the platform:
+
+
+
+
+ On macOS, there is only one menu bar per application. Set the menu using the `SetMenu` method of the application:
+
+ ```go
+ app.SetMenu(menu)
+ ```
+
+
+
+
+
+ On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window:
+
+ ```go
+ window.SetMenu(menu)
+ ```
+
+
+
+
+
+ On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window:
+
+ ```go
+ window.SetMenu(menu)
+ ```
+
+
+
+
+
## Menu Roles
Wails provides predefined menu roles that automatically create platform-appropriate menu structures:
diff --git a/docs/src/content/docs/learn/build.mdx b/docs/src/content/docs/learn/build.mdx
index aeb5804af..e45c6e3b9 100644
--- a/docs/src/content/docs/learn/build.mdx
+++ b/docs/src/content/docs/learn/build.mdx
@@ -123,6 +123,7 @@ the application on macOS. Key features include:
- Building binaries for amd64, arm64 and universal (both) architectures
- Generating `.icns` icon file
- Creating an `.app` bundle for distributing
+- Ad-hoc signing `.app` bundles
- Setting macOS-specific build flags and environment variables
## Task Execution and Command Aliases
diff --git a/docs/src/content/docs/learn/notifications.mdx b/docs/src/content/docs/learn/notifications.mdx
new file mode 100644
index 000000000..5a59f5e2a
--- /dev/null
+++ b/docs/src/content/docs/learn/notifications.mdx
@@ -0,0 +1,304 @@
+---
+title: Notifications
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+
+## Introduction
+
+Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields.
+
+## Basic Usage
+
+### Creating the Service
+
+First, initialize the notifications service:
+
+```go
+import "github.com/wailsapp/wails/v3/pkg/application"
+import "github.com/wailsapp/wails/v3/services/notifications"
+
+// Create a new notification service
+notifier := notifications.New()
+
+//Register the service with the application
+app := application.New(application.Options{
+ Services: []application.Service{
+ application.NewService(notifier),
+ },
+})
+```
+
+## Notification Authorization
+
+Notifications on macOS require user authorization. Request and check authorization:
+
+```go
+authorized, err := notifier.CheckNotificationAuthorization()
+if err != nil {
+ // Handle authorization error
+}
+if authorized {
+ // Send notifications
+} else {
+ // Request authorization
+ authorized, err = notifier.RequestNotificationAuthorization()
+}
+```
+On Windows and Linux this always returns `true`.
+
+## Notification Types
+
+### Basic Notifications
+
+Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users:
+
+```go
+notifier.SendNotification(notifications.NotificationOptions{
+ ID: "unique-id",
+ Title: "New Calendar Invite",
+ Subtitle: "From: Jane Doe", // Optional
+ Body: "Tap to view the event",
+})
+
+```
+
+### Interactive Notifications
+Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:
+
+```go
+// Define a unique category id
+categoryID := "unique-category-id"
+
+// Define a category with actions
+category := notifications.NotificationCategory{
+ ID: categoryID,
+ Actions: []notifications.NotificationAction{
+ {
+ ID: "OPEN",
+ Title: "Open",
+ },
+ {
+ ID: "ARCHIVE",
+ Title: "Archive",
+ Destructive: true, /* macOS-specific */
+ },
+ },
+ HasReplyField: true,
+ ReplyPlaceholder: "message...",
+ ReplyButtonTitle: "Reply",
+}
+
+// Register the category
+notifier.RegisterNotificationCategory(category)
+
+// Send an interactive notification with the actions registered in the provided category
+notifier.SendNotificationWithActions(notifications.NotificationOptions{
+ ID: "unique-id",
+ Title: "New Message",
+ Subtitle: "From: Jane Doe",
+ Body: "Are you able to make it?",
+ CategoryID: categoryID,
+})
+```
+
+## Notification Responses
+
+Process user interactions with notifications:
+
+```go
+notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
+ response := result.Response
+ fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
+
+ if response.ActionIdentifier == "TEXT_REPLY" {
+ fmt.Printf("User replied: %s\n", response.UserText)
+ }
+
+ if data, ok := response.UserInfo["sender"].(string); ok {
+ fmt.Printf("Original sender: %s\n", data)
+ }
+
+ // Emit an event to the frontend
+ app.EmitEvent("notification", result.Response)
+})
+```
+
+## Notification Customisation
+
+### Custom Metadata
+
+Basic and interactive notifications can include custom data:
+
+```go
+notifier.SendNotification(notifications.NotificationOptions{
+ ID: "unique-id",
+ Title: "New Calendar Invite",
+ Subtitle: "From: Jane Doe", // Optional
+ Body: "Tap to view the event",
+ Data: map[string]interface{}{
+ "sender": "jane.doe@example.com",
+ "timestamp": "2025-03-10T15:30:00Z",
+ }
+})
+
+```
+
+## Platform Considerations
+
+
+
+
+ On macOS, notifications:
+
+ - Require user authorization
+ - Require the app to be notorized for distribution
+ - Use system-standard notification appearances
+ - Support `subtitle`
+ - Support user text input
+ - Support the `Destructive` action option
+ - Automatically handle dark/light mode
+
+
+
+
+
+ On Windows, notifications:
+
+ - Use Windows system toast styles
+ - Adapt to Windows theme settings
+ - Support user text input
+ - Support high DPI displays
+ - Do not support `subtitle`
+
+
+
+
+
+ On Linux, dialog behaviour depends on the desktop environment:
+
+ - Use native notifications when available
+ - Follow desktop environment theme
+ - Position according to desktop environment rules
+ - Support `subtitle`
+ - Do not support user text input
+
+
+
+
+## Best Practices
+
+1. Check and request for authorization:
+ - macOS requires user authorization
+
+2. Provide clear and concise notifications:
+ - Use descriptive titles, subtitles, text, and action titles
+
+3. Handle dialog responses appropriately:
+ - Check for errors in notification responses
+ - Provide feedback for user actions
+
+4. Consider platform conventions:
+ - Follow platform-specific notification patterns
+ - Respect system settings
+
+## Examples
+
+Explore this example:
+
+- [Notifications](/examples/notifications)
+
+## API Reference
+
+### Service Management
+| Method | Description |
+|--------------------------------------------|-------------------------------------------------------|
+| `New()` | Creates a new notifications service |
+
+### Notification Authorization
+| Method | Description |
+|----------------------------------------------|------------------------------------------------------------|
+| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) |
+| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) |
+
+### Sending Notifications
+| Method | Description |
+|------------------------------------------------------------|---------------------------------------------------|
+| `SendNotification(options NotificationOptions)` | Sends a basic notification |
+| `SendNotificationWithActions(options NotificationOptions)` | Sends an interactive notification with actions |
+
+### Notification Categories
+| Method | Description |
+|---------------------------------------------------------------|---------------------------------------------------|
+| `RegisterNotificationCategory(category NotificationCategory)` | Registers a reusable notification category |
+| `RemoveNotificationCategory(categoryID string)` | Removes a previously registered category |
+
+### Managing Notifications
+| Method | Description |
+|-------------------------------------------------|---------------------------------------------------------------------|
+| `RemoveAllPendingNotifications()` | Removes all pending notifications (macOS and Linux only) |
+| `RemovePendingNotification(identifier string)` | Removes a specific pending notification (macOS and Linux only) |
+| `RemoveAllDeliveredNotifications()` | Removes all delivered notifications (macOS and Linux only) |
+| `RemoveDeliveredNotification(identifier string)`| Removes a specific delivered notification (macOS and Linux only) |
+| `RemoveNotification(identifier string)` | Removes a notification (Linux-specific) |
+
+### Event Handling
+| Method | Description |
+|--------------------------------------------------------------------|-------------------------------------------------|
+| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses |
+
+### Structs and Types
+
+#### NotificationOptions
+```go
+type NotificationOptions struct {
+ ID string // Unique identifier for the notification
+ Title string // Main notification title
+ Subtitle string // Subtitle text (macOS and Linux only)
+ Body string // Main notification content
+ CategoryID string // Category identifier for interactive notifications
+ Data map[string]interface{} // Custom data to associate with the notification
+}
+```
+
+#### NotificationCategory
+```go
+type NotificationCategory struct {
+ ID string // Unique identifier for the category
+ Actions []NotificationAction // Button actions for the notification
+ HasReplyField bool // Whether to include a text input field
+ ReplyPlaceholder string // Placeholder text for the input field
+ ReplyButtonTitle string // Text for the reply button
+}
+```
+
+#### NotificationAction
+```go
+type NotificationAction struct {
+ ID string // Unique identifier for the action
+ Title string // Button text
+ Destructive bool // Whether the action is destructive (macOS-specific)
+}
+```
+
+#### NotificationResponse
+```go
+type NotificationResponse struct {
+ ID string // Notification identifier
+ ActionIdentifier string // Action that was triggered
+ CategoryID string // Category of the notification
+ Title string // Title of the notification
+ Subtitle string // Subtitle of the notification
+ Body string // Body text of the notification
+ UserText string // Text entered by the user
+ UserInfo map[string]interface{} // Custom data from the notification
+}
+```
+
+#### NotificationResult
+```go
+type NotificationResult struct {
+ Response NotificationResponse // Response data
+ Error error // Any error that occurred
+}
+```
\ No newline at end of file
diff --git a/v3/cmd/wails3/README.md b/v3/cmd/wails3/README.md
index 91ecdafeb..8924153dd 100644
--- a/v3/cmd/wails3/README.md
+++ b/v3/cmd/wails3/README.md
@@ -21,13 +21,13 @@ It can be used to generate many things including:
The `icon` command generates icons for your project.
-| Flag | Type | Description | Default |
-|--------------------|--------|------------------------------------------------------|-----------------------|
-| `-example` | bool | Generates example icon file (appicon.png) | |
-| `-input` | string | The input image file | |
+| Flag | Type | Description | Default |
+|--------------------|--------|------------------------------------------------------|----------------------|
+| `-example` | bool | Generates example icon file (appicon.png) | |
+| `-input` | string | The input image file | |
| `-sizes` | string | The sizes to generate in .ico file (comma separated) | "256,128,64,48,32,16" |
-| `-windowsFilename` | string | The output filename for the Windows icon | icons.ico |
-| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns |
+| `-windowsFilename` | string | The output filename for the Windows icon | icon.ico |
+| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns |
```bash
wails3 generate icon -input myicon.png -sizes "32,64,128" -windowsFilename myicon.ico -macFilename myicon.icns
diff --git a/v3/examples/file-association/build/Taskfile.common.yml b/v3/examples/file-association/build/Taskfile.common.yml
index 2b8a4d26d..650c8ea83 100644
--- a/v3/examples/file-association/build/Taskfile.common.yml
+++ b/v3/examples/file-association/build/Taskfile.common.yml
@@ -56,7 +56,7 @@ tasks:
- "appicon.png"
generates:
- "icons.icns"
- - "icons.ico"
+ - "icon.ico"
cmds:
- wails3 generate icons -input appicon.png
diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go
index 62bcb454b..218f93a2d 100644
--- a/v3/examples/menu/main.go
+++ b/v3/examples/menu/main.go
@@ -27,16 +27,7 @@ func main() {
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
- fileMenu := menu.AddRole(application.FileMenu)
- _ = fileMenu
- //fileMenu.FindByRole(application.Open).OnClick(func(context *application.Context) {
- // selection, err := application.OpenFileDialog().PromptForSingleSelection()
- // if err != nil {
- // println("Error: " + err.Error())
- // return
- // }
- // println("You selected: " + selection)
- //})
+ menu.AddRole(application.FileMenu)
menu.AddRole(application.EditMenu)
menu.AddRole(application.WindowMenu)
menu.AddRole(application.HelpMenu)
@@ -44,6 +35,12 @@ func main() {
// Let's make a "Demo" menu
myMenu := menu.AddSubmenu("Demo")
+ // Hidden menu item that can be unhidden
+ hidden := myMenu.Add("I was hidden").SetHidden(true)
+ myMenu.Add("Toggle the hidden menu").OnClick(func(ctx *application.Context) {
+ hidden.SetHidden(!hidden.Hidden())
+ })
+
// Disabled menu item
myMenu.Add("Not Enabled").SetEnabled(false)
@@ -118,7 +115,8 @@ func main() {
})
app.SetMenu(menu)
- app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41))
+ window := app.NewWebviewWindow().SetBackgroundColour(application.NewRGB(33, 37, 41))
+ window.SetMenu(menu)
err := app.Run()
diff --git a/v3/examples/notifications/README.md b/v3/examples/notifications/README.md
new file mode 100644
index 000000000..ad12c3f40
--- /dev/null
+++ b/v3/examples/notifications/README.md
@@ -0,0 +1,59 @@
+# Welcome to Your New Wails3 Project!
+
+Congratulations on generating your Wails3 application! This README will guide you through the next steps to get your project up and running.
+
+## Getting Started
+
+1. Navigate to your project directory in the terminal.
+
+2. To run your application in development mode, use the following command:
+
+ ```
+ wails3 dev
+ ```
+
+ This will start your application and enable hot-reloading for both frontend and backend changes.
+
+3. To build your application for production, use:
+
+ ```
+ wails3 build
+ ```
+
+ This will create a production-ready executable in the `build` directory.
+
+## Exploring Wails3 Features
+
+Now that you have your project set up, it's time to explore the features that Wails3 offers:
+
+1. **Check out the examples**: The best way to learn is by example. Visit the `examples` directory in the `v3/examples` directory to see various sample applications.
+
+2. **Run an example**: To run any of the examples, navigate to the example's directory and use:
+
+ ```
+ go run .
+ ```
+
+ Note: Some examples may be under development during the alpha phase.
+
+3. **Explore the documentation**: Visit the [Wails3 documentation](https://v3.wails.io/) for in-depth guides and API references.
+
+4. **Join the community**: Have questions or want to share your progress? Join the [Wails Discord](https://discord.gg/JDdSxwjhGf) or visit the [Wails discussions on GitHub](https://github.com/wailsapp/wails/discussions).
+
+## Project Structure
+
+Take a moment to familiarize yourself with your project structure:
+
+- `frontend/`: Contains your frontend code (HTML, CSS, JavaScript/TypeScript)
+- `main.go`: The entry point of your Go backend
+- `app.go`: Define your application structure and methods here
+- `wails.json`: Configuration file for your Wails project
+
+## Next Steps
+
+1. Modify the frontend in the `frontend/` directory to create your desired UI.
+2. Add backend functionality in `main.go`.
+3. Use `wails3 dev` to see your changes in real-time.
+4. When ready, build your application with `wails3 build`.
+
+Happy coding with Wails3! If you encounter any issues or have questions, don't hesitate to consult the documentation or reach out to the Wails community.
diff --git a/v3/examples/notifications/Taskfile.yml b/v3/examples/notifications/Taskfile.yml
new file mode 100644
index 000000000..1455cd70c
--- /dev/null
+++ b/v3/examples/notifications/Taskfile.yml
@@ -0,0 +1,34 @@
+version: '3'
+
+includes:
+ common: ./build/Taskfile.yml
+ windows: ./build/windows/Taskfile.yml
+ darwin: ./build/darwin/Taskfile.yml
+ linux: ./build/linux/Taskfile.yml
+
+vars:
+ APP_NAME: "Notifications\\ Demo"
+ BIN_DIR: "bin"
+ VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
+
+tasks:
+ build:
+ summary: Builds the application
+ cmds:
+ - task: "{{OS}}:build"
+
+ package:
+ summary: Packages a production build of the application
+ cmds:
+ - task: "{{OS}}:package"
+
+ run:
+ summary: Runs the application
+ cmds:
+ - task: "{{OS}}:run"
+
+ dev:
+ summary: Runs the application in development mode
+ cmds:
+ - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
+
diff --git a/v3/examples/notifications/build/Taskfile.yml b/v3/examples/notifications/build/Taskfile.yml
new file mode 100644
index 000000000..5f3517efc
--- /dev/null
+++ b/v3/examples/notifications/build/Taskfile.yml
@@ -0,0 +1,86 @@
+version: '3'
+
+tasks:
+ go:mod:tidy:
+ summary: Runs `go mod tidy`
+ internal: true
+ cmds:
+ - go mod tidy
+
+ install:frontend:deps:
+ summary: Install frontend dependencies
+ dir: frontend
+ sources:
+ - package.json
+ - package-lock.json
+ generates:
+ - node_modules/*
+ preconditions:
+ - sh: npm version
+ msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
+ cmds:
+ - npm install
+
+ build:frontend:
+ label: build:frontend (PRODUCTION={{.PRODUCTION}})
+ summary: Build the frontend project
+ dir: frontend
+ sources:
+ - "**/*"
+ generates:
+ - dist/**/*
+ deps:
+ - task: install:frontend:deps
+ - task: generate:bindings
+ vars:
+ BUILD_FLAGS:
+ ref: .BUILD_FLAGS
+ cmds:
+ - npm run {{.BUILD_COMMAND}} -q
+ env:
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+ vars:
+ BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}'
+
+
+ generate:bindings:
+ label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
+ summary: Generates bindings for the frontend
+ deps:
+ - task: go:mod:tidy
+ sources:
+ - "**/*.[jt]s"
+ - exclude: frontend/**/*
+ - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output
+ - "**/*.go"
+ - go.mod
+ - go.sum
+ generates:
+ - frontend/bindings/**/*
+ cmds:
+ - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts
+
+ generate:icons:
+ summary: Generates Windows `.ico` and Mac `.icns` files from an image
+ dir: build
+ sources:
+ - "appicon.png"
+ generates:
+ - "darwin/icons.icns"
+ - "windows/icon.ico"
+ cmds:
+ - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
+
+ dev:frontend:
+ summary: Runs the frontend in development mode
+ dir: frontend
+ deps:
+ - task: install:frontend:deps
+ cmds:
+ - npm run dev -- --port {{.VITE_PORT}} --strictPort
+
+ update:build-assets:
+ summary: Updates the build assets
+ dir: build
+ cmds:
+ - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .
diff --git a/v3/examples/notifications/build/appicon.png b/v3/examples/notifications/build/appicon.png
new file mode 100644
index 000000000..63617fe4f
Binary files /dev/null and b/v3/examples/notifications/build/appicon.png differ
diff --git a/v3/examples/notifications/build/config.yml b/v3/examples/notifications/build/config.yml
new file mode 100644
index 000000000..bc09a6d28
--- /dev/null
+++ b/v3/examples/notifications/build/config.yml
@@ -0,0 +1,62 @@
+# This file contains the configuration for this project.
+# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
+# Note that this will overwrite any changes you have made to the assets.
+version: '3'
+
+# This information is used to generate the build assets.
+info:
+ companyName: "My Company" # The name of the company
+ productName: "My Product" # The name of the application
+ productIdentifier: "com.mycompany.myproduct" # The unique product identifier
+ description: "A program that does X" # The application description
+ copyright: "(c) 2025, My Company" # Copyright text
+ comments: "Some Product Comments" # Comments
+ version: "v0.0.1" # The application version
+
+# Dev mode configuration
+dev_mode:
+ root_path: .
+ log_level: warn
+ debounce: 1000
+ ignore:
+ dir:
+ - .git
+ - node_modules
+ - frontend
+ - bin
+ file:
+ - .DS_Store
+ - .gitignore
+ - .gitkeep
+ watched_extension:
+ - "*.go"
+ git_ignore: true
+ executes:
+ - cmd: wails3 task common:install:frontend:deps
+ type: once
+ - cmd: wails3 task common:dev:frontend
+ type: background
+ - cmd: go mod tidy
+ type: blocking
+ - cmd: wails3 task build
+ type: blocking
+ - cmd: wails3 task run
+ type: primary
+
+# File Associations
+# More information at: https://v3.wails.io/noit/done/yet
+fileAssociations:
+# - ext: wails
+# name: Wails
+# description: Wails Application File
+# iconName: wailsFileIcon
+# role: Editor
+# - ext: jpg
+# name: JPEG
+# description: Image File
+# iconName: jpegFileIcon
+# role: Editor
+
+# Other data
+other:
+ - name: My Other Data
\ No newline at end of file
diff --git a/v3/examples/notifications/build/darwin/Info.dev.plist b/v3/examples/notifications/build/darwin/Info.dev.plist
new file mode 100644
index 000000000..3a5b9735f
--- /dev/null
+++ b/v3/examples/notifications/build/darwin/Info.dev.plist
@@ -0,0 +1,32 @@
+
+
+
+ CFBundlePackageType
+ APPL
+ CFBundleName
+ My Product
+ CFBundleExecutable
+ Notifications Demo
+ CFBundleIdentifier
+ com.wails.notifications-demo
+ CFBundleVersion
+ 0.1.0
+ CFBundleGetInfoString
+ This is a comment
+ CFBundleShortVersionString
+ 0.1.0
+ CFBundleIconFile
+ icons
+ LSMinimumSystemVersion
+ 10.15.0
+ NSHighResolutionCapable
+ true
+ NSHumanReadableCopyright
+ © now, My Company
+ NSAppTransportSecurity
+
+ NSAllowsLocalNetworking
+
+
+
+
\ No newline at end of file
diff --git a/v3/examples/notifications/build/darwin/Info.plist b/v3/examples/notifications/build/darwin/Info.plist
new file mode 100644
index 000000000..464270019
--- /dev/null
+++ b/v3/examples/notifications/build/darwin/Info.plist
@@ -0,0 +1,27 @@
+
+
+
+ CFBundlePackageType
+ APPL
+ CFBundleName
+ My Product
+ CFBundleExecutable
+ Notifications Demo
+ CFBundleIdentifier
+ com.wails.notifications-demo
+ CFBundleVersion
+ 0.1.0
+ CFBundleGetInfoString
+ This is a comment
+ CFBundleShortVersionString
+ 0.1.0
+ CFBundleIconFile
+ icons
+ LSMinimumSystemVersion
+ 10.15.0
+ NSHighResolutionCapable
+ true
+ NSHumanReadableCopyright
+ © now, My Company
+
+
\ No newline at end of file
diff --git a/v3/examples/notifications/build/darwin/Taskfile.yml b/v3/examples/notifications/build/darwin/Taskfile.yml
new file mode 100644
index 000000000..3b6a9dc99
--- /dev/null
+++ b/v3/examples/notifications/build/darwin/Taskfile.yml
@@ -0,0 +1,80 @@
+version: '3'
+
+includes:
+ common: ../Taskfile.yml
+
+tasks:
+ build:
+ summary: Creates a production build of the application
+ deps:
+ - task: common:go:mod:tidy
+ - task: common:build:frontend
+ vars:
+ BUILD_FLAGS:
+ ref: .BUILD_FLAGS
+ PRODUCTION:
+ ref: .PRODUCTION
+ - task: common:generate:icons
+ cmds:
+ - go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
+ vars:
+ BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
+ DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
+ OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
+ env:
+ GOOS: darwin
+ CGO_ENABLED: 1
+ GOARCH: '{{.ARCH | default ARCH}}'
+ CGO_CFLAGS: "-mmacosx-version-min=10.15"
+ CGO_LDFLAGS: "-mmacosx-version-min=10.15"
+ MACOSX_DEPLOYMENT_TARGET: "10.15"
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+
+ build:universal:
+ summary: Builds darwin universal binary (arm64 + amd64)
+ deps:
+ - task: build
+ vars:
+ ARCH: amd64
+ OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64"
+ - task: build
+ vars:
+ ARCH: arm64
+ OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
+ cmds:
+ - lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
+ - rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
+
+ package:
+ summary: Packages a production build of the application into a `.app` bundle
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - task: create:app:bundle
+
+ package:universal:
+ summary: Packages darwin universal binary (arm64 + amd64)
+ deps:
+ - task: build:universal
+ cmds:
+ - task: create:app:bundle
+
+
+ create:app:bundle:
+ summary: Creates an `.app` bundle
+ cmds:
+ - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
+ - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
+ - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
+ - cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
+
+ run:
+ cmds:
+ - mkdir -p {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
+ - cp build/darwin/icons.icns {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/Resources
+ - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/MacOS
+ - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/Info.plist
+ - codesign --force --deep --sign - {{.BIN_DIR}}/dev/{{.APP_NAME}}.app
+ - '{{.BIN_DIR}}/dev/{{.APP_NAME}}.app/Contents/MacOS/{{.APP_NAME}}'
diff --git a/v3/examples/notifications/build/darwin/icons.icns b/v3/examples/notifications/build/darwin/icons.icns
new file mode 100644
index 000000000..1b5bd4c86
Binary files /dev/null and b/v3/examples/notifications/build/darwin/icons.icns differ
diff --git a/v3/examples/notifications/build/linux/Taskfile.yml b/v3/examples/notifications/build/linux/Taskfile.yml
new file mode 100644
index 000000000..560cc9c92
--- /dev/null
+++ b/v3/examples/notifications/build/linux/Taskfile.yml
@@ -0,0 +1,119 @@
+version: '3'
+
+includes:
+ common: ../Taskfile.yml
+
+tasks:
+ build:
+ summary: Builds the application for Linux
+ deps:
+ - task: common:go:mod:tidy
+ - task: common:build:frontend
+ vars:
+ BUILD_FLAGS:
+ ref: .BUILD_FLAGS
+ PRODUCTION:
+ ref: .PRODUCTION
+ - task: common:generate:icons
+ cmds:
+ - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
+ vars:
+ BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
+ env:
+ GOOS: linux
+ CGO_ENABLED: 1
+ GOARCH: '{{.ARCH | default ARCH}}'
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+
+ package:
+ summary: Packages a production build of the application for Linux
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - task: create:appimage
+ - task: create:deb
+ - task: create:rpm
+ - task: create:aur
+
+ create:appimage:
+ summary: Creates an AppImage
+ dir: build/linux/appimage
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ - task: generate:dotdesktop
+ cmds:
+ - cp {{.APP_BINARY}} {{.APP_NAME}}
+ - cp ../../appicon.png appicon.png
+ - wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
+ vars:
+ APP_NAME: '{{.APP_NAME}}'
+ APP_BINARY: '../../../bin/{{.APP_NAME}}'
+ ICON: '../../appicon.png'
+ DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
+ OUTPUT_DIR: '../../../bin'
+
+ create:deb:
+ summary: Creates a deb package
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - task: generate:dotdesktop
+ - task: generate:deb
+
+ create:rpm:
+ summary: Creates a rpm package
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - task: generate:dotdesktop
+ - task: generate:rpm
+
+ create:aur:
+ summary: Creates a arch linux packager package
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ - task: generate:dotdesktop
+ - task: generate:aur
+
+ generate:deb:
+ summary: Creates a deb package
+ cmds:
+ - wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
+
+ generate:rpm:
+ summary: Creates a rpm package
+ cmds:
+ - wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
+
+ generate:aur:
+ summary: Creates a arch linux packager package
+ cmds:
+ - wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
+
+ generate:dotdesktop:
+ summary: Generates a `.desktop` file
+ dir: build
+ cmds:
+ - mkdir -p {{.ROOT_DIR}}/build/linux/appimage
+ - wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}"
+ vars:
+ APP_NAME: '{{.APP_NAME}}'
+ EXEC: '{{.APP_NAME}}'
+ ICON: 'appicon'
+ CATEGORIES: 'Development;'
+ OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
+
+ run:
+ cmds:
+ - '{{.BIN_DIR}}/{{.APP_NAME}}'
diff --git a/v3/examples/notifications/build/linux/appimage/build.sh b/v3/examples/notifications/build/linux/appimage/build.sh
new file mode 100644
index 000000000..85901c34e
--- /dev/null
+++ b/v3/examples/notifications/build/linux/appimage/build.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# Copyright (c) 2018-Present Lea Anthony
+# SPDX-License-Identifier: MIT
+
+# Fail script on any error
+set -euxo pipefail
+
+# Define variables
+APP_DIR="${APP_NAME}.AppDir"
+
+# Create AppDir structure
+mkdir -p "${APP_DIR}/usr/bin"
+cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
+cp "${ICON_PATH}" "${APP_DIR}/"
+cp "${DESKTOP_FILE}" "${APP_DIR}/"
+
+if [[ $(uname -m) == *x86_64* ]]; then
+ # Download linuxdeploy and make it executable
+ wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+ chmod +x linuxdeploy-x86_64.AppImage
+
+ # Run linuxdeploy to bundle the application
+ ./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
+else
+ # Download linuxdeploy and make it executable (arm64)
+ wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
+ chmod +x linuxdeploy-aarch64.AppImage
+
+ # Run linuxdeploy to bundle the application (arm64)
+ ./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
+fi
+
+# Rename the generated AppImage
+mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"
+
diff --git a/v3/examples/notifications/build/linux/nfpm/nfpm.yaml b/v3/examples/notifications/build/linux/nfpm/nfpm.yaml
new file mode 100644
index 000000000..c2cb7cd81
--- /dev/null
+++ b/v3/examples/notifications/build/linux/nfpm/nfpm.yaml
@@ -0,0 +1,50 @@
+# Feel free to remove those if you don't want/need to use them.
+# Make sure to check the documentation at https://nfpm.goreleaser.com
+#
+# The lines below are called `modelines`. See `:help modeline`
+
+name: "notifications"
+arch: ${GOARCH}
+platform: "linux"
+version: "0.1.0"
+section: "default"
+priority: "extra"
+maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
+description: "My Product Description"
+vendor: "My Company"
+homepage: "https://wails.io"
+license: "MIT"
+release: "1"
+
+contents:
+ - src: "./bin/notifications"
+ dst: "/usr/local/bin/notifications"
+ - src: "./build/appicon.png"
+ dst: "/usr/share/icons/hicolor/128x128/apps/notifications.png"
+ - src: "./build/linux/notifications.desktop"
+ dst: "/usr/share/applications/notifications.desktop"
+
+depends:
+ - gtk3
+ - libwebkit2gtk
+
+# replaces:
+# - foobar
+# provides:
+# - bar
+# depends:
+# - gtk3
+# - libwebkit2gtk
+# recommends:
+# - whatever
+# suggests:
+# - something-else
+# conflicts:
+# - not-foo
+# - not-bar
+# changelog: "changelog.yaml"
+# scripts:
+# preinstall: ./build/linux/nfpm/scripts/preinstall.sh
+# postinstall: ./build/linux/nfpm/scripts/postinstall.sh
+# preremove: ./build/linux/nfpm/scripts/preremove.sh
+# postremove: ./build/linux/nfpm/scripts/postremove.sh
diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/postinstall.sh b/v3/examples/notifications/build/linux/nfpm/scripts/postinstall.sh
new file mode 100644
index 000000000..a9bf588e2
--- /dev/null
+++ b/v3/examples/notifications/build/linux/nfpm/scripts/postinstall.sh
@@ -0,0 +1 @@
+#!/bin/bash
diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/postremove.sh b/v3/examples/notifications/build/linux/nfpm/scripts/postremove.sh
new file mode 100644
index 000000000..a9bf588e2
--- /dev/null
+++ b/v3/examples/notifications/build/linux/nfpm/scripts/postremove.sh
@@ -0,0 +1 @@
+#!/bin/bash
diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/preinstall.sh b/v3/examples/notifications/build/linux/nfpm/scripts/preinstall.sh
new file mode 100644
index 000000000..a9bf588e2
--- /dev/null
+++ b/v3/examples/notifications/build/linux/nfpm/scripts/preinstall.sh
@@ -0,0 +1 @@
+#!/bin/bash
diff --git a/v3/examples/notifications/build/linux/nfpm/scripts/preremove.sh b/v3/examples/notifications/build/linux/nfpm/scripts/preremove.sh
new file mode 100644
index 000000000..a9bf588e2
--- /dev/null
+++ b/v3/examples/notifications/build/linux/nfpm/scripts/preremove.sh
@@ -0,0 +1 @@
+#!/bin/bash
diff --git a/v3/examples/notifications/build/windows/Taskfile.yml b/v3/examples/notifications/build/windows/Taskfile.yml
new file mode 100644
index 000000000..be6e4125e
--- /dev/null
+++ b/v3/examples/notifications/build/windows/Taskfile.yml
@@ -0,0 +1,63 @@
+version: '3'
+
+includes:
+ common: ../Taskfile.yml
+
+tasks:
+ build:
+ summary: Builds the application for Windows
+ deps:
+ - task: common:go:mod:tidy
+ - task: common:build:frontend
+ vars:
+ BUILD_FLAGS:
+ ref: .BUILD_FLAGS
+ PRODUCTION:
+ ref: .PRODUCTION
+ - task: common:generate:icons
+ cmds:
+ - task: generate:syso
+ - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe
+ - cmd: powershell Remove-item *.syso
+ platforms: [windows]
+ - cmd: rm -f *.syso
+ platforms: [linux, darwin]
+ vars:
+ BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
+ env:
+ GOOS: windows
+ CGO_ENABLED: 0
+ GOARCH: '{{.ARCH | default ARCH}}'
+ PRODUCTION: '{{.PRODUCTION | default "false"}}'
+
+ package:
+ summary: Packages a production build of the application into a `.exe` bundle
+ cmds:
+ - task: create:nsis:installer
+
+ generate:syso:
+ summary: Generates Windows `.syso` file
+ dir: build
+ cmds:
+ - wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
+ vars:
+ ARCH: '{{.ARCH | default ARCH}}'
+
+ create:nsis:installer:
+ summary: Creates an NSIS installer
+ dir: build/windows/nsis
+ deps:
+ - task: build
+ vars:
+ PRODUCTION: "true"
+ cmds:
+ # Create the Microsoft WebView2 bootstrapper if it doesn't exist
+ - wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
+ - makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
+ vars:
+ ARCH: '{{.ARCH | default ARCH}}'
+ ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
+
+ run:
+ cmds:
+ - '{{.BIN_DIR}}\\{{.APP_NAME}}.exe'
diff --git a/v3/examples/notifications/build/windows/icon.ico b/v3/examples/notifications/build/windows/icon.ico
new file mode 100644
index 000000000..bfa0690b7
Binary files /dev/null and b/v3/examples/notifications/build/windows/icon.ico differ
diff --git a/v3/examples/notifications/build/windows/info.json b/v3/examples/notifications/build/windows/info.json
new file mode 100644
index 000000000..850b2b5b0
--- /dev/null
+++ b/v3/examples/notifications/build/windows/info.json
@@ -0,0 +1,15 @@
+{
+ "fixed": {
+ "file_version": "0.1.0"
+ },
+ "info": {
+ "0000": {
+ "ProductVersion": "0.1.0",
+ "CompanyName": "My Company",
+ "FileDescription": "My Product Description",
+ "LegalCopyright": "© now, My Company",
+ "ProductName": "My Product",
+ "Comments": "This is a comment"
+ }
+ }
+}
\ No newline at end of file
diff --git a/v3/examples/notifications/build/windows/nsis/project.nsi b/v3/examples/notifications/build/windows/nsis/project.nsi
new file mode 100644
index 000000000..4cb18e04f
--- /dev/null
+++ b/v3/examples/notifications/build/windows/nsis/project.nsi
@@ -0,0 +1,112 @@
+Unicode true
+
+####
+## Please note: Template replacements don't work in this file. They are provided with default defines like
+## mentioned underneath.
+## If the keyword is not defined, "wails_tools.nsh" will populate them.
+## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually
+## from outside of Wails for debugging and development of the installer.
+##
+## For development first make a wails nsis build to populate the "wails_tools.nsh":
+## > wails build --target windows/amd64 --nsis
+## Then you can call makensis on this file with specifying the path to your binary:
+## For a AMD64 only installer:
+## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
+## For a ARM64 only installer:
+## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
+## For a installer with both architectures:
+## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
+####
+## The following information is taken from the wails_tools.nsh file, but they can be overwritten here.
+####
+## !define INFO_PROJECTNAME "my-project" # Default "notifications"
+## !define INFO_COMPANYNAME "My Company" # Default "My Company"
+## !define INFO_PRODUCTNAME "My Product Name" # Default "My Product"
+## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.1.0"
+## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© now, My Company"
+###
+## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
+## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
+####
+## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
+####
+## Include the wails tools
+####
+!include "wails_tools.nsh"
+
+# The version information for this two must consist of 4 parts
+VIProductVersion "${INFO_PRODUCTVERSION}.0"
+VIFileVersion "${INFO_PRODUCTVERSION}.0"
+
+VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
+VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
+VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
+VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
+VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
+VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
+
+# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
+ManifestDPIAware true
+
+!include "MUI.nsh"
+
+!define MUI_ICON "..\icon.ico"
+!define MUI_UNICON "..\icon.ico"
+# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
+!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
+!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
+
+!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
+# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
+!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
+!insertmacro MUI_PAGE_INSTFILES # Installing page.
+!insertmacro MUI_PAGE_FINISH # Finished installation page.
+
+!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page
+
+!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
+
+## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
+#!uninstfinalize 'signtool --file "%1"'
+#!finalize 'signtool --file "%1"'
+
+Name "${INFO_PRODUCTNAME}"
+OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
+InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
+ShowInstDetails show # This will always show the installation details.
+
+Function .onInit
+ !insertmacro wails.checkArchitecture
+FunctionEnd
+
+Section
+ !insertmacro wails.setShellContext
+
+ !insertmacro wails.webview2runtime
+
+ SetOutPath $INSTDIR
+
+ !insertmacro wails.files
+
+ CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
+ CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
+
+ !insertmacro wails.associateFiles
+
+ !insertmacro wails.writeUninstaller
+SectionEnd
+
+Section "uninstall"
+ !insertmacro wails.setShellContext
+
+ RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
+
+ RMDir /r $INSTDIR
+
+ Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
+ Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
+
+ !insertmacro wails.unassociateFiles
+
+ !insertmacro wails.deleteUninstaller
+SectionEnd
diff --git a/v3/examples/notifications/build/windows/nsis/wails_tools.nsh b/v3/examples/notifications/build/windows/nsis/wails_tools.nsh
new file mode 100644
index 000000000..c47c784a4
--- /dev/null
+++ b/v3/examples/notifications/build/windows/nsis/wails_tools.nsh
@@ -0,0 +1,212 @@
+# DO NOT EDIT - Generated automatically by `wails build`
+
+!include "x64.nsh"
+!include "WinVer.nsh"
+!include "FileFunc.nsh"
+
+!ifndef INFO_PROJECTNAME
+ !define INFO_PROJECTNAME "notifications"
+!endif
+!ifndef INFO_COMPANYNAME
+ !define INFO_COMPANYNAME "My Company"
+!endif
+!ifndef INFO_PRODUCTNAME
+ !define INFO_PRODUCTNAME "My Product"
+!endif
+!ifndef INFO_PRODUCTVERSION
+ !define INFO_PRODUCTVERSION "0.1.0"
+!endif
+!ifndef INFO_COPYRIGHT
+ !define INFO_COPYRIGHT "© now, My Company"
+!endif
+!ifndef PRODUCT_EXECUTABLE
+ !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
+!endif
+!ifndef UNINST_KEY_NAME
+ !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
+!endif
+!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
+
+!ifndef REQUEST_EXECUTION_LEVEL
+ !define REQUEST_EXECUTION_LEVEL "admin"
+!endif
+
+RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
+
+!ifdef ARG_WAILS_AMD64_BINARY
+ !define SUPPORTS_AMD64
+!endif
+
+!ifdef ARG_WAILS_ARM64_BINARY
+ !define SUPPORTS_ARM64
+!endif
+
+!ifdef SUPPORTS_AMD64
+ !ifdef SUPPORTS_ARM64
+ !define ARCH "amd64_arm64"
+ !else
+ !define ARCH "amd64"
+ !endif
+!else
+ !ifdef SUPPORTS_ARM64
+ !define ARCH "arm64"
+ !else
+ !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
+ !endif
+!endif
+
+!macro wails.checkArchitecture
+ !ifndef WAILS_WIN10_REQUIRED
+ !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
+ !endif
+
+ !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
+ !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
+ !endif
+
+ ${If} ${AtLeastWin10}
+ !ifdef SUPPORTS_AMD64
+ ${if} ${IsNativeAMD64}
+ Goto ok
+ ${EndIf}
+ !endif
+
+ !ifdef SUPPORTS_ARM64
+ ${if} ${IsNativeARM64}
+ Goto ok
+ ${EndIf}
+ !endif
+
+ IfSilent silentArch notSilentArch
+ silentArch:
+ SetErrorLevel 65
+ Abort
+ notSilentArch:
+ MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
+ Quit
+ ${else}
+ IfSilent silentWin notSilentWin
+ silentWin:
+ SetErrorLevel 64
+ Abort
+ notSilentWin:
+ MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
+ Quit
+ ${EndIf}
+
+ ok:
+!macroend
+
+!macro wails.files
+ !ifdef SUPPORTS_AMD64
+ ${if} ${IsNativeAMD64}
+ File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
+ ${EndIf}
+ !endif
+
+ !ifdef SUPPORTS_ARM64
+ ${if} ${IsNativeARM64}
+ File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
+ ${EndIf}
+ !endif
+!macroend
+
+!macro wails.writeUninstaller
+ WriteUninstaller "$INSTDIR\uninstall.exe"
+
+ SetRegView 64
+ WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
+ WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
+ WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
+ WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
+ WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
+ WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
+
+ ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
+ IntFmt $0 "0x%08X" $0
+ WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
+!macroend
+
+!macro wails.deleteUninstaller
+ Delete "$INSTDIR\uninstall.exe"
+
+ SetRegView 64
+ DeleteRegKey HKLM "${UNINST_KEY}"
+!macroend
+
+!macro wails.setShellContext
+ ${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
+ SetShellVarContext all
+ ${else}
+ SetShellVarContext current
+ ${EndIf}
+!macroend
+
+# Install webview2 by launching the bootstrapper
+# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
+!macro wails.webview2runtime
+ !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
+ !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
+ !endif
+
+ SetRegView 64
+ # If the admin key exists and is not empty then webview2 is already installed
+ ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+ ${If} $0 != ""
+ Goto ok
+ ${EndIf}
+
+ ${If} ${REQUEST_EXECUTION_LEVEL} == "user"
+ # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
+ ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+ ${If} $0 != ""
+ Goto ok
+ ${EndIf}
+ ${EndIf}
+
+ SetDetailsPrint both
+ DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
+ SetDetailsPrint listonly
+
+ InitPluginsDir
+ CreateDirectory "$pluginsdir\webview2bootstrapper"
+ SetOutPath "$pluginsdir\webview2bootstrapper"
+ File "MicrosoftEdgeWebview2Setup.exe"
+ ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
+
+ SetDetailsPrint both
+ ok:
+!macroend
+
+# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
+!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
+ ; Backup the previously associated file class
+ ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
+ WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
+
+ WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
+
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
+!macroend
+
+!macro APP_UNASSOCIATE EXT FILECLASS
+ ; Backup the previously associated file class
+ ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
+ WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
+
+ DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
+!macroend
+
+!macro wails.associateFiles
+ ; Create file associations
+
+!macroend
+
+!macro wails.unassociateFiles
+ ; Delete app associations
+
+!macroend
\ No newline at end of file
diff --git a/v3/examples/notifications/build/windows/wails.exe.manifest b/v3/examples/notifications/build/windows/wails.exe.manifest
new file mode 100644
index 000000000..0299e62ca
--- /dev/null
+++ b/v3/examples/notifications/build/windows/wails.exe.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+ true/pm
+ permonitorv2,permonitor
+
+
+
\ No newline at end of file
diff --git a/v3/examples/notifications/frontend/Inter Font License.txt b/v3/examples/notifications/frontend/Inter Font License.txt
new file mode 100644
index 000000000..b525cbf3a
--- /dev/null
+++ b/v3/examples/notifications/frontend/Inter Font License.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts
new file mode 100644
index 000000000..bbdce6579
--- /dev/null
+++ b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/index.ts
@@ -0,0 +1,13 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+import * as Service from "./service.js";
+export {
+ Service
+};
+
+export {
+ NotificationAction,
+ NotificationCategory,
+ NotificationOptions
+} from "./models.js";
diff --git a/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts
new file mode 100644
index 000000000..d7f48edfe
--- /dev/null
+++ b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/models.ts
@@ -0,0 +1,107 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import { Create as $Create } from "@wailsio/runtime";
+
+/**
+ * NotificationAction represents an action button for a notification.
+ */
+export class NotificationAction {
+ "id"?: string;
+ "title"?: string;
+
+ /**
+ * (macOS-specific)
+ */
+ "destructive"?: boolean;
+
+ /** Creates a new NotificationAction instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new NotificationAction instance from a string or object.
+ */
+ static createFrom($$source: any = {}): NotificationAction {
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ return new NotificationAction($$parsedSource as Partial);
+ }
+}
+
+/**
+ * NotificationCategory groups actions for notifications.
+ */
+export class NotificationCategory {
+ "id"?: string;
+ "actions"?: NotificationAction[];
+ "hasReplyField"?: boolean;
+ "replyPlaceholder"?: string;
+ "replyButtonTitle"?: string;
+
+ /** Creates a new NotificationCategory instance. */
+ constructor($$source: Partial = {}) {
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new NotificationCategory instance from a string or object.
+ */
+ static createFrom($$source: any = {}): NotificationCategory {
+ const $$createField1_0 = $$createType1;
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ if ("actions" in $$parsedSource) {
+ $$parsedSource["actions"] = $$createField1_0($$parsedSource["actions"]);
+ }
+ return new NotificationCategory($$parsedSource as Partial);
+ }
+}
+
+/**
+ * NotificationOptions contains configuration for a notification
+ */
+export class NotificationOptions {
+ "id": string;
+ "title": string;
+
+ /**
+ * (macOS and Linux only)
+ */
+ "subtitle"?: string;
+ "body"?: string;
+ "categoryId"?: string;
+ "data"?: { [_: string]: any };
+
+ /** Creates a new NotificationOptions instance. */
+ constructor($$source: Partial = {}) {
+ if (!("id" in $$source)) {
+ this["id"] = "";
+ }
+ if (!("title" in $$source)) {
+ this["title"] = "";
+ }
+
+ Object.assign(this, $$source);
+ }
+
+ /**
+ * Creates a new NotificationOptions instance from a string or object.
+ */
+ static createFrom($$source: any = {}): NotificationOptions {
+ const $$createField5_0 = $$createType2;
+ let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
+ if ("data" in $$parsedSource) {
+ $$parsedSource["data"] = $$createField5_0($$parsedSource["data"]);
+ }
+ return new NotificationOptions($$parsedSource as Partial);
+ }
+}
+
+// Private type creation functions
+const $$createType0 = NotificationAction.createFrom;
+const $$createType1 = $Create.Array($$createType0);
+const $$createType2 = $Create.Map($Create.Any, $Create.Any);
diff --git a/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/service.ts b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/service.ts
new file mode 100644
index 000000000..28f1cb3b2
--- /dev/null
+++ b/v3/examples/notifications/frontend/bindings/github.com/wailsapp/wails/v3/pkg/services/notifications/service.ts
@@ -0,0 +1,62 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+/**
+ * Service represents the notifications service
+ * @module
+ */
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
+
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore: Unused imports
+import * as $models from "./models.js";
+
+export function CheckNotificationAuthorization(): $CancellablePromise {
+ return $Call.ByID(2789931702);
+}
+
+export function RegisterNotificationCategory(category: $models.NotificationCategory): $CancellablePromise {
+ return $Call.ByID(2679064664, category);
+}
+
+export function RemoveAllDeliveredNotifications(): $CancellablePromise {
+ return $Call.ByID(384520397);
+}
+
+export function RemoveAllPendingNotifications(): $CancellablePromise {
+ return $Call.ByID(1423986276);
+}
+
+export function RemoveDeliveredNotification(identifier: string): $CancellablePromise {
+ return $Call.ByID(149440045, identifier);
+}
+
+export function RemoveNotification(identifier: string): $CancellablePromise {
+ return $Call.ByID(3702062929, identifier);
+}
+
+export function RemoveNotificationCategory(categoryID: string): $CancellablePromise {
+ return $Call.ByID(229511469, categoryID);
+}
+
+export function RemovePendingNotification(identifier: string): $CancellablePromise {
+ return $Call.ByID(3872412470, identifier);
+}
+
+/**
+ * Public methods that delegate to the implementation.
+ */
+export function RequestNotificationAuthorization(): $CancellablePromise {
+ return $Call.ByID(729898933);
+}
+
+export function SendNotification(options: $models.NotificationOptions): $CancellablePromise {
+ return $Call.ByID(2246903123, options);
+}
+
+export function SendNotificationWithActions(options: $models.NotificationOptions): $CancellablePromise {
+ return $Call.ByID(1615199806, options);
+}
diff --git a/v3/examples/notifications/frontend/index.html b/v3/examples/notifications/frontend/index.html
new file mode 100644
index 000000000..ae067c8a2
--- /dev/null
+++ b/v3/examples/notifications/frontend/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+ Wails App
+
+
+
+
+
Wails + Typescript + Desktop Notifications
+
Send notifications 👇
+
+
+
+
+
+
+
+
+
diff --git a/v3/examples/notifications/frontend/package-lock.json b/v3/examples/notifications/frontend/package-lock.json
new file mode 100644
index 000000000..cdd50c49d
--- /dev/null
+++ b/v3/examples/notifications/frontend/package-lock.json
@@ -0,0 +1,935 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "devDependencies": {
+ "@wailsio/runtime": "latest",
+ "typescript": "^4.9.3",
+ "vite": "^5.0.0"
+ }
+ },
+ "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/@rollup/rollup-android-arm-eabi": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz",
+ "integrity": "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.37.0.tgz",
+ "integrity": "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.37.0.tgz",
+ "integrity": "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.37.0.tgz",
+ "integrity": "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.37.0.tgz",
+ "integrity": "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.37.0.tgz",
+ "integrity": "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.37.0.tgz",
+ "integrity": "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.37.0.tgz",
+ "integrity": "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.37.0.tgz",
+ "integrity": "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.37.0.tgz",
+ "integrity": "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.37.0.tgz",
+ "integrity": "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.37.0.tgz",
+ "integrity": "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.37.0.tgz",
+ "integrity": "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.37.0.tgz",
+ "integrity": "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.37.0.tgz",
+ "integrity": "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz",
+ "integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.37.0.tgz",
+ "integrity": "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.37.0.tgz",
+ "integrity": "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.37.0.tgz",
+ "integrity": "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.37.0.tgz",
+ "integrity": "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@wailsio/runtime": {
+ "version": "3.0.0-alpha.66",
+ "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz",
+ "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/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/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.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "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.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.37.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz",
+ "integrity": "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.37.0",
+ "@rollup/rollup-android-arm64": "4.37.0",
+ "@rollup/rollup-darwin-arm64": "4.37.0",
+ "@rollup/rollup-darwin-x64": "4.37.0",
+ "@rollup/rollup-freebsd-arm64": "4.37.0",
+ "@rollup/rollup-freebsd-x64": "4.37.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.37.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.37.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.37.0",
+ "@rollup/rollup-linux-arm64-musl": "4.37.0",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.37.0",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.37.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.37.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.37.0",
+ "@rollup/rollup-linux-x64-gnu": "4.37.0",
+ "@rollup/rollup-linux-x64-musl": "4.37.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.37.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.37.0",
+ "@rollup/rollup-win32-x64-msvc": "4.37.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.15",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz",
+ "integrity": "sha512-6ANcZRivqL/4WtwPGTKNaosuNJr5tWiftOC7liM7G9+rMb8+oeJeyzymDu4rTN93seySBmbjSfsS3Vzr19KNtA==",
+ "dev": true,
+ "license": "MIT",
+ "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
+ }
+ }
+ }
+ }
+}
diff --git a/v3/examples/notifications/frontend/package.json b/v3/examples/notifications/frontend/package.json
new file mode 100644
index 000000000..4d675f189
--- /dev/null
+++ b/v3/examples/notifications/frontend/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build:dev": "tsc && vite build --minify false --mode development",
+ "build": "tsc && vite build --mode production",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "typescript": "^4.9.3",
+ "vite": "^5.0.0",
+ "@wailsio/runtime": "latest"
+ }
+}
\ No newline at end of file
diff --git a/v3/examples/notifications/frontend/public/Inter-Medium.ttf b/v3/examples/notifications/frontend/public/Inter-Medium.ttf
new file mode 100644
index 000000000..a01f3777a
Binary files /dev/null and b/v3/examples/notifications/frontend/public/Inter-Medium.ttf differ
diff --git a/v3/examples/notifications/frontend/public/style.css b/v3/examples/notifications/frontend/public/style.css
new file mode 100644
index 000000000..074717bca
--- /dev/null
+++ b/v3/examples/notifications/frontend/public/style.css
@@ -0,0 +1,131 @@
+:root {
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
+ "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ font-size: 16px;
+ line-height: 24px;
+ font-weight: 400;
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: rgba(27, 38, 54, 1);
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+* {
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+}
+
+@font-face {
+ font-family: "Inter";
+ font-style: normal;
+ font-weight: 400;
+ src: local(""),
+ url("./Inter-Medium.ttf") format("truetype");
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+
+a:hover {
+ color: #535bf2;
+}
+
+.controls {
+ display: flex;
+ gap: 1em;
+}
+
+button {
+ height: 30px;
+ line-height: 30px;
+ border-radius: 3px;
+ border: none;
+ margin: 0 0 0 20px;
+ padding: 0 8px;
+ cursor: pointer;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ place-content: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 3em;
+}
+
+h1, h3 {
+ line-height: 1.1;
+ text-align: center;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+}
+
+.logo:hover {
+ filter: drop-shadow(0 0 2em #e80000aa);
+}
+
+.logo.vanilla:hover {
+ filter: drop-shadow(0 0 2em #f7df1eaa);
+}
+
+.footer {
+ margin-top: 1rem;
+ align-content: center;
+ text-align: center;
+}
+
+.footer table {
+ font-size: 12px;
+ border-collapse: collapse;
+ margin: 0 auto;
+}
+
+.footer table, th, td {
+ border: 1px solid #ddd;
+ padding: 0.5em;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+
+ a:hover {
+ color: #747bff;
+ }
+
+ button {
+ background-color: #f9f9f9;
+ }
+}
\ No newline at end of file
diff --git a/v3/examples/notifications/frontend/public/typescript.svg b/v3/examples/notifications/frontend/public/typescript.svg
new file mode 100644
index 000000000..d91c910cc
--- /dev/null
+++ b/v3/examples/notifications/frontend/public/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/v3/examples/notifications/frontend/public/wails.png b/v3/examples/notifications/frontend/public/wails.png
new file mode 100644
index 000000000..8bdf42483
Binary files /dev/null and b/v3/examples/notifications/frontend/public/wails.png differ
diff --git a/v3/examples/notifications/frontend/src/main.ts b/v3/examples/notifications/frontend/src/main.ts
new file mode 100644
index 000000000..437fb8c94
--- /dev/null
+++ b/v3/examples/notifications/frontend/src/main.ts
@@ -0,0 +1,95 @@
+import { Events } from "@wailsio/runtime";
+import * as Notifications from "../bindings/github.com/wailsapp/wails/v3/pkg/services/notifications";
+
+document.querySelector("#basic")?.addEventListener("click", async () => {
+ try {
+ const authorized = await Notifications.Service.CheckNotificationAuthorization();
+ if (authorized) {
+ await Notifications.Service.SendNotification({
+ id: crypto.randomUUID(),
+ title: "Notification Title",
+ subtitle: "Subtitle on macOS and Linux",
+ body: "Body text of notification.",
+ data: {
+ "user-id": "user-123",
+ "message-id": "msg-123",
+ "timestamp": Date.now(),
+ },
+ });
+ } else {
+ console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n");
+ }
+ } catch (error) {
+ console.error(error);
+ }
+});
+document.querySelector("#complex")?.addEventListener("click", async () => {
+ try {
+ const authorized = await Notifications.Service.CheckNotificationAuthorization();
+ if (authorized) {
+ const CategoryID = "frontend-notification-id";
+
+ await Notifications.Service.RegisterNotificationCategory({
+ id: CategoryID,
+ actions: [
+ { id: "VIEW", title: "View" },
+ { id: "MARK_READ", title: "Mark as read" },
+ { id: "DELETE", title: "Delete", destructive: true },
+ ],
+ hasReplyField: true,
+ replyPlaceholder: "Message...",
+ replyButtonTitle: "Reply",
+ });
+
+ await Notifications.Service.SendNotificationWithActions({
+ id: crypto.randomUUID(),
+ title: "Notification Title",
+ subtitle: "Subtitle on macOS and Linux",
+ body: "Body text of notification.",
+ categoryId: CategoryID,
+ data: {
+ "user-id": "user-123",
+ "message-id": "msg-123",
+ "timestamp": Date.now(),
+ },
+ });
+ } else {
+ console.warn("Notifications are not authorized.\n You can attempt to request again or let the user know in the UI.\n");
+ }
+ } catch (error) {
+ console.error(error);
+ }
+});
+
+const unlisten = Events.On("notification:action", (response) => {
+ console.info(`Recieved a ${response.name} event`);
+ const { userInfo, ...base } = response.data[0];
+ console.info("Notification Response:");
+ console.table(base);
+ console.info("Notification Response Metadata:");
+ console.table(userInfo);
+ const table = `
+ Notification Response
+
+
+ ${Object.keys(base).map(key => `| ${key} | `).join("")}
+
+
+ ${Object.values(base).map(value => `${value} | `).join("")}
+
+
+ Notification Metadata
+
+
+ ${Object.keys(userInfo).map(key => `| ${key} | `).join("")}
+
+
+ ${Object.values(userInfo).map(value => `${value} | `).join("")}
+
+
+ `;
+ const footer = document.querySelector("#response");
+ if (footer) footer.innerHTML = table;
+});
+
+window.onbeforeunload = () => unlisten();
\ No newline at end of file
diff --git a/v3/examples/notifications/frontend/src/vite-env.d.ts b/v3/examples/notifications/frontend/src/vite-env.d.ts
new file mode 100644
index 000000000..11f02fe2a
--- /dev/null
+++ b/v3/examples/notifications/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/v3/examples/notifications/frontend/tsconfig.json b/v3/examples/notifications/frontend/tsconfig.json
new file mode 100644
index 000000000..c267ecf24
--- /dev/null
+++ b/v3/examples/notifications/frontend/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ESNext", "DOM"],
+ "moduleResolution": "Node",
+ "strict": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "noEmit": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": false,
+ "noImplicitAny": false,
+ "noImplicitReturns": true,
+ "skipLibCheck": true
+ },
+ "include": ["src"]
+}
diff --git a/v3/examples/notifications/go.mod b/v3/examples/notifications/go.mod
new file mode 100644
index 000000000..39537e938
--- /dev/null
+++ b/v3/examples/notifications/go.mod
@@ -0,0 +1,53 @@
+module notifications
+
+go 1.24.0
+
+toolchain go1.24.1
+
+require github.com/wailsapp/wails/v3 v3.0.0-dev
+
+require (
+ dario.cat/mergo v1.0.1 // indirect
+ git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
+ github.com/Microsoft/go-winio v0.6.2 // indirect
+ github.com/ProtonMail/go-crypto v1.1.6 // indirect
+ github.com/adrg/xdg v0.5.3 // indirect
+ github.com/bep/debounce v1.2.1 // indirect
+ github.com/cloudflare/circl v1.6.0 // indirect
+ github.com/cyphar/filepath-securejoin v0.4.1 // indirect
+ github.com/ebitengine/purego v0.8.2 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+ github.com/go-git/go-billy/v5 v5.6.2 // indirect
+ github.com/go-git/go-git/v5 v5.13.2 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
+ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
+ github.com/kevinburke/ssh_config v1.2.0 // indirect
+ github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
+ github.com/leaanthony/u v1.1.1 // indirect
+ github.com/lmittmann/tint v1.0.7 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/pjbgf/sha1cd v0.3.2 // indirect
+ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/samber/lo v1.49.1 // indirect
+ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
+ github.com/skeema/knownhosts v1.3.1 // indirect
+ github.com/wailsapp/go-webview2 v1.0.21 // indirect
+ github.com/wailsapp/mimetype v1.4.1 // indirect
+ github.com/xanzy/ssh-agent v0.3.3 // indirect
+ golang.org/x/crypto v0.36.0 // indirect
+ golang.org/x/net v0.37.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
+)
+
+replace github.com/wailsapp/wails/v3 => ../wails/v3
diff --git a/v3/examples/notifications/main.go b/v3/examples/notifications/main.go
new file mode 100644
index 000000000..264e7273e
--- /dev/null
+++ b/v3/examples/notifications/main.go
@@ -0,0 +1,154 @@
+package main
+
+import (
+ "embed"
+ _ "embed"
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/events"
+ "github.com/wailsapp/wails/v3/pkg/services/notifications"
+)
+
+// Wails uses Go's `embed` package to embed the frontend files into the binary.
+// Any files in the frontend/dist folder will be embedded into the binary and
+// made available to the frontend.
+// See https://pkg.go.dev/embed for more information.
+
+//go:embed all:frontend/dist
+var assets embed.FS
+
+// main function serves as the application's entry point. It initializes the application, creates a window,
+// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and
+// logs any error that might occur.
+func main() {
+ // Create a new Notification Service
+ ns := notifications.New()
+
+ // Create a new Wails application by providing the necessary options.
+ // Variables 'Name' and 'Description' are for application metadata.
+ // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
+ // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
+ // 'Mac' options tailor the application when running an macOS.
+ app := application.New(application.Options{
+ Name: "Notifications Demo",
+ Description: "A demo of using desktop notifications with Wails",
+ Services: []application.Service{
+ application.NewService(ns),
+ },
+ Assets: application.AssetOptions{
+ Handler: application.AssetFileServerFS(assets),
+ },
+ Mac: application.MacOptions{
+ ApplicationShouldTerminateAfterLastWindowClosed: true,
+ },
+ })
+
+ // Create a new window with the necessary options.
+ // 'Title' is the title of the window.
+ // 'Mac' options tailor the window when running on macOS.
+ // 'BackgroundColour' is the background colour of the window.
+ // 'URL' is the URL that will be loaded into the webview.
+ app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
+ Title: "Window 1",
+ Mac: application.MacWindow{
+ InvisibleTitleBarHeight: 50,
+ Backdrop: application.MacBackdropTranslucent,
+ TitleBar: application.MacTitleBarHiddenInset,
+ },
+ BackgroundColour: application.NewRGB(27, 38, 54),
+ URL: "/",
+ })
+
+ app.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) {
+ // Create a goroutine that spawns desktop notifications from Go
+ go func() {
+ var authorized bool
+ var err error
+ authorized, err = ns.CheckNotificationAuthorization()
+ if err != nil {
+ println(fmt.Errorf("checking app notification authorization failed: %s", err))
+ }
+
+ if !authorized {
+ authorized, err = ns.RequestNotificationAuthorization()
+ if err != nil {
+ println(fmt.Errorf("requesting app notification authorization failed: %s", err))
+ }
+ }
+
+ if authorized {
+ ns.OnNotificationResponse(func(result notifications.NotificationResult) {
+ if result.Error != nil {
+ println(fmt.Errorf("parsing notification result failed: %s", result.Error))
+ } else {
+ fmt.Printf("Response: %+v\n", result.Response)
+ println("Sending response to frontend...")
+ app.EmitEvent("notification:action", result.Response)
+ }
+ })
+
+ err = ns.SendNotification(notifications.NotificationOptions{
+ ID: "uuid-basic-1",
+ Title: "Notification Title",
+ Subtitle: "Subtitle on macOS and Linux",
+ Body: "Body text of notification.",
+ Data: map[string]interface{}{
+ "user-id": "user-123",
+ "message-id": "msg-123",
+ "timestamp": time.Now().Unix(),
+ },
+ })
+ if err != nil {
+ println(fmt.Errorf("sending basic notification failed: %s", err))
+ }
+
+ // Delay before sending next notification
+ time.Sleep(time.Second * 2)
+
+ const CategoryID = "backend-notification-id"
+
+ err = ns.RegisterNotificationCategory(notifications.NotificationCategory{
+ ID: CategoryID,
+ Actions: []notifications.NotificationAction{
+ {ID: "VIEW", Title: "View"},
+ {ID: "MARK_READ", Title: "Mark as read"},
+ {ID: "DELETE", Title: "Delete", Destructive: true},
+ },
+ HasReplyField: true,
+ ReplyPlaceholder: "Message...",
+ ReplyButtonTitle: "Reply",
+ })
+ if err != nil {
+ println(fmt.Errorf("creating notification category failed: %s", err))
+ }
+
+ err = ns.SendNotificationWithActions(notifications.NotificationOptions{
+ ID: "uuid-with-actions-1",
+ Title: "Actions Notification Title",
+ Subtitle: "Subtitle on macOS and Linux",
+ Body: "Body text of notification with actions.",
+ CategoryID: CategoryID,
+ Data: map[string]interface{}{
+ "user-id": "user-123",
+ "message-id": "msg-123",
+ "timestamp": time.Now().Unix(),
+ },
+ })
+ if err != nil {
+ println(fmt.Errorf("sending notification with actions failed: %s", err))
+ }
+ }
+ }()
+ })
+
+ // Run the application. This blocks until the application has been exited.
+ err := app.Run()
+
+ // If an error occurred while running the application, log it and exit.
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/v3/go.mod b/v3/go.mod
index c4ce9ceef..947f0495f 100644
--- a/v3/go.mod
+++ b/v3/go.mod
@@ -3,19 +3,20 @@ module github.com/wailsapp/wails/v3
go 1.24.0
require (
+ git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3
github.com/Masterminds/semver v1.5.0
github.com/adrg/xdg v0.5.3
github.com/atterpac/refresh v0.8.6
github.com/bep/debounce v1.2.1
- github.com/charmbracelet/glamour v0.8.0
+ github.com/charmbracelet/glamour v0.9.0
github.com/ebitengine/purego v0.8.2
github.com/go-git/go-git/v5 v5.13.2
github.com/go-ole/go-ole v1.3.0
github.com/godbus/dbus/v5 v5.1.0
- github.com/google/go-cmp v0.6.0
+ github.com/google/go-cmp v0.7.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
- github.com/goreleaser/nfpm/v2 v2.41.2
+ github.com/goreleaser/nfpm/v2 v2.41.3
github.com/jackmordaunt/icns/v2 v2.2.7
github.com/jaypipes/ghw v0.13.0
github.com/leaanthony/clir v1.7.0
@@ -32,19 +33,22 @@ require (
github.com/pterm/pterm v0.12.80
github.com/samber/lo v1.49.1
github.com/tc-hib/winres v0.3.1
- github.com/wailsapp/go-webview2 v1.0.19
+ github.com/wailsapp/go-webview2 v1.0.21
github.com/wailsapp/mimetype v1.4.1
github.com/wailsapp/task/v3 v3.40.1-patched3
- golang.org/x/sys v0.30.0
- golang.org/x/term v0.29.0
- golang.org/x/tools v0.30.0
+ golang.org/x/sys v0.31.0
+ golang.org/x/term v0.30.0
+ golang.org/x/tools v0.31.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
- modernc.org/sqlite v1.35.0
+ modernc.org/sqlite v1.36.0
)
require (
atomicgo.dev/schedule v0.1.0 // indirect
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
+ github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
)
@@ -59,7 +63,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
- github.com/ProtonMail/go-crypto v1.1.5 // indirect
+ github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/alecthomas/chroma/v2 v2.15.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
@@ -67,7 +71,7 @@ require (
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/chainguard-dev/git-urls v1.0.2 // indirect
- github.com/charmbracelet/lipgloss v1.0.0 // indirect
+ github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/containerd/console v1.0.4 // indirect
@@ -95,7 +99,7 @@ require (
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
- github.com/klauspost/compress v1.17.11 // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
@@ -109,7 +113,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
- github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
+ github.com/muesli/termenv v0.16.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/radovskyb/watcher v1.0.7 // indirect
@@ -126,17 +130,17 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.8 // indirect
- github.com/yuin/goldmark-emoji v1.0.4 // indirect
+ github.com/yuin/goldmark-emoji v1.0.5 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
- golang.org/x/crypto v0.33.0 // indirect
+ golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac
golang.org/x/image v0.24.0 // indirect
- golang.org/x/mod v0.23.0 // indirect
- golang.org/x/net v0.35.0 // indirect
- golang.org/x/sync v0.11.0 // indirect
- golang.org/x/text v0.22.0 // indirect
+ golang.org/x/mod v0.24.0 // indirect
+ golang.org/x/net v0.37.0 // indirect
+ golang.org/x/sync v0.12.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
howett.net/plist v1.0.1 // indirect
@@ -144,4 +148,4 @@ require (
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
mvdan.cc/sh/v3 v3.10.0 // indirect
-)
\ No newline at end of file
+)
diff --git a/v3/go.sum b/v3/go.sum
index 87f3fdbbc..2c07fe6c8 100644
--- a/v3/go.sum
+++ b/v3/go.sum
@@ -8,6 +8,8 @@ atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=
atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
+git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
@@ -36,8 +38,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
-github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
+github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
@@ -57,8 +59,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
-github.com/atterpac/refresh v0.8.4 h1:jRgX8TwZaMTqDmcFemhtnpJsrlmEmwwSILUQczHTxY8=
-github.com/atterpac/refresh v0.8.4/go.mod h1:fJpWySLdpbANS8Ej5OvfZVZIVvi/9bmnhTjKS5EjQes=
github.com/atterpac/refresh v0.8.6 h1:Q5miKV2qs9jW+USw8WZ/54Zz8/RSh/bOz5U6JvvDZmM=
github.com/atterpac/refresh v0.8.6/go.mod h1:fJpWySLdpbANS8Ej5OvfZVZIVvi/9bmnhTjKS5EjQes=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -77,14 +77,20 @@ github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7a
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
-github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
-github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
-github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
-github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/glamour v0.9.0 h1:1Hm3wxww7qXvGI+Fb3zDmIZo5oDOvVOWJ4OrIB+ef7c=
+github.com/charmbracelet/glamour v0.9.0/go.mod h1:+SHvIS8qnwhgTpVMiXwn7OfGomSqff1cHBCI8jLOetk=
+github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
+github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
+github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
+github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
+github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
@@ -138,8 +144,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
@@ -158,8 +164,8 @@ github.com/goreleaser/chglog v0.6.2 h1:qroqdMHzwoAPTHHzJtbCfYbwg/yWJrNQApZ6IQAq8
github.com/goreleaser/chglog v0.6.2/go.mod h1:BP0xQQc6B8aM+4dhvSLlVTv0rvhuOF0JacDO1+h7L3U=
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
-github.com/goreleaser/nfpm/v2 v2.41.2 h1:yOjpPlft5zpMPusbIWICphycIjE5orpY/IyMbkBbIJU=
-github.com/goreleaser/nfpm/v2 v2.41.2/go.mod h1:zvk0z+wsPKe7Qdsp7z0ZJ9asnbwwhJUEsdOsPkgVC1E=
+github.com/goreleaser/nfpm/v2 v2.41.3 h1:IRRsqv5NgiCKUy57HjQgfVBFb44VH8+r1mWeEF8OuA4=
+github.com/goreleaser/nfpm/v2 v2.41.3/go.mod h1:0t54RfPX6/iKANsVLbB3XgtfQXzG1nS4HmSavN92qVY=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -183,8 +189,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
-github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
@@ -245,8 +251,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
-github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
-github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
+github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
+github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
@@ -317,8 +323,8 @@ github.com/tc-hib/winres v0.3.1 h1:CwRjEGrKdbi5CvZ4ID+iyVhgyfatxFoizjPhzez9Io4=
github.com/tc-hib/winres v0.3.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
-github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
+github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
+github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/task/v3 v3.40.1-patched3 h1:i6O1WNdSur9CGaiMDIYGjsmj/qS4465zqv+WEs6sPRs=
@@ -334,8 +340,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark-emoji v1.0.4 h1:vCwMkPZSNefSUnOW2ZKRUjBSD5Ok3W78IXhGxxAEF90=
-github.com/yuin/goldmark-emoji v1.0.4/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
+github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
+github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
@@ -345,8 +351,8 @@ gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4=
@@ -356,21 +362,21 @@ golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
-golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
+golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
-golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -389,29 +395,29 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
-golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
-golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
+golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
+golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -450,8 +456,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
-modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
+modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=
+modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
diff --git a/v3/internal/commands/build-assets.go b/v3/internal/commands/build-assets.go
index 12b5694fd..1fa9138f5 100644
--- a/v3/internal/commands/build-assets.go
+++ b/v3/internal/commands/build-assets.go
@@ -4,14 +4,15 @@ import (
"embed"
_ "embed"
"fmt"
- "github.com/leaanthony/gosod"
- "gopkg.in/yaml.v3"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"time"
+
+ "github.com/leaanthony/gosod"
+ "gopkg.in/yaml.v3"
)
//go:embed build_assets
@@ -121,6 +122,7 @@ type FileAssociation struct {
Description string `yaml:"description"`
IconName string `yaml:"iconName"`
Role string `yaml:"role"`
+ MimeType string `yaml:"mimeType"`
}
type UpdateConfig struct {
diff --git a/v3/internal/commands/build_assets/config.yml b/v3/internal/commands/build_assets/config.yml
index bc09a6d28..8a5e2f4c8 100644
--- a/v3/internal/commands/build_assets/config.yml
+++ b/v3/internal/commands/build_assets/config.yml
@@ -56,6 +56,7 @@ fileAssociations:
# description: Image File
# iconName: jpegFileIcon
# role: Editor
+# mimeType: image/jpeg # (optional)
# Other data
other:
diff --git a/v3/internal/commands/build_assets/darwin/Taskfile.yml b/v3/internal/commands/build_assets/darwin/Taskfile.yml
index 30e7da823..f0791fea9 100644
--- a/v3/internal/commands/build_assets/darwin/Taskfile.yml
+++ b/v3/internal/commands/build_assets/darwin/Taskfile.yml
@@ -69,7 +69,13 @@ tasks:
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
- cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
+ - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app
run:
cmds:
- - '{{.BIN_DIR}}/{{.APP_NAME}}'
+ - mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}
+ - cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources
+ - cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS
+ - cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist
+ - codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app
+ - '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'
diff --git a/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl b/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl
index 47f39f537..e44a679d2 100644
--- a/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl
+++ b/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl
@@ -2,27 +2,50 @@
CFBundlePackageType
- APPL
+ APPL
CFBundleName
- {{.ProductName}}
+ {{.ProductName}}
CFBundleExecutable
- {{.BinaryName}}
+ {{.BinaryName}}
CFBundleIdentifier
- {{.ProductIdentifier}}
+ {{.ProductIdentifier}}
CFBundleVersion
- {{.ProductVersion}}
+ {{.ProductVersion}}
CFBundleGetInfoString
- {{.ProductComments}}
+ {{.ProductComments}}
CFBundleShortVersionString
- {{.ProductVersion}}
+ {{.ProductVersion}}
CFBundleIconFile
- icons
+ icons
LSMinimumSystemVersion
- 10.15.0
+ 10.15.0
NSHighResolutionCapable
- true
+ true
NSHumanReadableCopyright
- {{.ProductCopyright}}
+ {{.ProductCopyright}}
+ {{- if .FileAssociations}}
+ CFBundleDocumentTypes
+
+ {{- range .FileAssociations}}
+
+ CFBundleTypeExtensions
+
+ {{.Ext}}
+
+ CFBundleTypeName
+ {{.Name}}
+ CFBundleTypeRole
+ {{.Role}}
+ CFBundleTypeIconFile
+ {{.IconName}}
+ {{- if .MimeType}}
+ CFBundleTypeMimeType
+ {{.MimeType}}
+ {{- end}}
+
+ {{- end}}
+
+ {{- end}}
NSAppTransportSecurity
NSAllowsLocalNetworking
diff --git a/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl b/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl
index 3dce3676c..c20032556 100644
--- a/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl
+++ b/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl
@@ -2,26 +2,49 @@
CFBundlePackageType
- APPL
+ APPL
CFBundleName
- {{.ProductName}}
+ {{.ProductName}}
CFBundleExecutable
- {{.BinaryName}}
+ {{.BinaryName}}
CFBundleIdentifier
- {{.ProductIdentifier}}
+ {{.ProductIdentifier}}
CFBundleVersion
- {{.ProductVersion}}
+ {{.ProductVersion}}
CFBundleGetInfoString
- {{.ProductComments}}
+ {{.ProductComments}}
CFBundleShortVersionString
- {{.ProductVersion}}
+ {{.ProductVersion}}
CFBundleIconFile
- icons
+ icons
LSMinimumSystemVersion
- 10.15.0
+ 10.15.0
NSHighResolutionCapable
- true
+ true
NSHumanReadableCopyright
- {{.ProductCopyright}}
+ {{.ProductCopyright}}
+ {{- if .FileAssociations}}
+ CFBundleDocumentTypes
+
+ {{- range .FileAssociations}}
+
+ CFBundleTypeExtensions
+
+ {{.Ext}}
+
+ CFBundleTypeName
+ {{.Name}}
+ CFBundleTypeRole
+ {{.Role}}
+ CFBundleTypeIconFile
+ {{.IconName}}
+ {{- if .MimeType}}
+ CFBundleTypeMimeType
+ {{.MimeType}}
+ {{- end}}
+
+ {{- end}}
+
+ {{- end}}
\ No newline at end of file
diff --git a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl
index ca5c11ccc..aae481317 100644
--- a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl
+++ b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl
@@ -158,7 +158,7 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
- ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+ ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
diff --git a/v3/internal/templates/lit-ts/frontend/package.json b/v3/internal/templates/lit-ts/frontend/package.json
index 80a5e0d96..d208c3fbd 100644
--- a/v3/internal/templates/lit-ts/frontend/package.json
+++ b/v3/internal/templates/lit-ts/frontend/package.json
@@ -10,11 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"lit": "^3.1.0"
},
"devDependencies": {
"typescript": "^5.2.2",
- "vite": "^5.0.0",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/lit/frontend/package.json b/v3/internal/templates/lit/frontend/package.json
index 7369a3f47..ec30e751a 100644
--- a/v3/internal/templates/lit/frontend/package.json
+++ b/v3/internal/templates/lit/frontend/package.json
@@ -10,10 +10,10 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"lit": "^3.1.0"
},
"devDependencies": {
- "vite": "^5.0.0",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/preact-ts/frontend/package.json b/v3/internal/templates/preact-ts/frontend/package.json
index e26f733b7..b5dd75296 100644
--- a/v3/internal/templates/preact-ts/frontend/package.json
+++ b/v3/internal/templates/preact-ts/frontend/package.json
@@ -10,12 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"preact": "^10.19.3"
},
"devDependencies": {
"@preact/preset-vite": "^2.7.0",
"typescript": "^5.2.2",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/preact/frontend/package.json b/v3/internal/templates/preact/frontend/package.json
index 27506286f..863d1fc23 100644
--- a/v3/internal/templates/preact/frontend/package.json
+++ b/v3/internal/templates/preact/frontend/package.json
@@ -10,11 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"preact": "^10.19.3"
},
"devDependencies": {
"@preact/preset-vite": "^2.7.0",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/qwik-ts/frontend/package.json b/v3/internal/templates/qwik-ts/frontend/package.json
index 8452e0e60..b3f359a6b 100644
--- a/v3/internal/templates/qwik-ts/frontend/package.json
+++ b/v3/internal/templates/qwik-ts/frontend/package.json
@@ -9,12 +9,12 @@
"build": "tsc && vite build --mode production",
"preview": "vite preview"
},
- "devDependencies": {
- "typescript": "^5.2.2",
- "vite": "^5.0.8"
- },
"dependencies": {
"@builder.io/qwik": "^1.3.0",
"@wailsio/runtime": "latest"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.0.8"
}
}
diff --git a/v3/internal/templates/qwik/frontend/package.json b/v3/internal/templates/qwik/frontend/package.json
index 782a10d42..3139e426b 100644
--- a/v3/internal/templates/qwik/frontend/package.json
+++ b/v3/internal/templates/qwik/frontend/package.json
@@ -9,12 +9,12 @@
"build": "vite build --mode production",
"preview": "vite preview"
},
- "devDependencies": {
- "typescript": "^5.2.2",
- "vite": "^5.0.8"
- },
"dependencies": {
"@builder.io/qwik": "^1.3.0",
"@wailsio/runtime": "latest"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.0.8"
}
}
diff --git a/v3/internal/templates/react-swc-ts/frontend/package.json b/v3/internal/templates/react-swc-ts/frontend/package.json
index 5627a60f2..0dcc8cdcd 100644
--- a/v3/internal/templates/react-swc-ts/frontend/package.json
+++ b/v3/internal/templates/react-swc-ts/frontend/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -18,7 +19,6 @@
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react-swc": "^3.5.0",
"typescript": "^5.2.2",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
}
diff --git a/v3/internal/templates/react-swc/frontend/package.json b/v3/internal/templates/react-swc/frontend/package.json
index b86bd3722..158ba6880 100644
--- a/v3/internal/templates/react-swc/frontend/package.json
+++ b/v3/internal/templates/react-swc/frontend/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -17,7 +18,6 @@
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react-swc": "^3.5.0",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
}
diff --git a/v3/internal/templates/react-ts/frontend/package.json b/v3/internal/templates/react-ts/frontend/package.json
index a72ca0f47..f718c0073 100644
--- a/v3/internal/templates/react-ts/frontend/package.json
+++ b/v3/internal/templates/react-ts/frontend/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -18,7 +19,6 @@
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"typescript": "^5.2.2",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
}
diff --git a/v3/internal/templates/react/frontend/package.json b/v3/internal/templates/react/frontend/package.json
index ff295952b..59d4d62b3 100644
--- a/v3/internal/templates/react/frontend/package.json
+++ b/v3/internal/templates/react/frontend/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -17,7 +18,6 @@
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
}
diff --git a/v3/internal/templates/solid-ts/frontend/package.json b/v3/internal/templates/solid-ts/frontend/package.json
index 703d31562..741674ea7 100644
--- a/v3/internal/templates/solid-ts/frontend/package.json
+++ b/v3/internal/templates/solid-ts/frontend/package.json
@@ -10,12 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"solid-js": "^1.8.7"
},
"devDependencies": {
"typescript": "^5.2.2",
"vite": "^5.0.8",
- "vite-plugin-solid": "^2.8.0",
- "@wailsio/runtime": "latest"
+ "vite-plugin-solid": "^2.8.0"
}
}
diff --git a/v3/internal/templates/solid/frontend/package.json b/v3/internal/templates/solid/frontend/package.json
index 9062b3686..03ed9141f 100644
--- a/v3/internal/templates/solid/frontend/package.json
+++ b/v3/internal/templates/solid/frontend/package.json
@@ -10,11 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"solid-js": "^1.8.7"
},
"devDependencies": {
"vite": "^5.0.8",
- "vite-plugin-solid": "^2.8.0",
- "@wailsio/runtime": "latest"
+ "vite-plugin-solid": "^2.8.0"
}
}
diff --git a/v3/internal/templates/svelte-ts/frontend/package.json b/v3/internal/templates/svelte-ts/frontend/package.json
index f3491e928..c14954e77 100644
--- a/v3/internal/templates/svelte-ts/frontend/package.json
+++ b/v3/internal/templates/svelte-ts/frontend/package.json
@@ -10,6 +10,9 @@
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
+ "dependencies": {
+ "@wailsio/runtime": "latest"
+ },
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@tsconfig/svelte": "^5.0.2",
@@ -17,7 +20,6 @@
"svelte-check": "^3.6.2",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/svelte/frontend/package.json b/v3/internal/templates/svelte/frontend/package.json
index 3a9ca1053..4655c0e95 100644
--- a/v3/internal/templates/svelte/frontend/package.json
+++ b/v3/internal/templates/svelte/frontend/package.json
@@ -9,10 +9,12 @@
"build": "vite build --mode production",
"preview": "vite preview"
},
+ "dependencies": {
+ "@wailsio/runtime": "latest"
+ },
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"svelte": "^4.2.8",
- "vite": "^5.0.8",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.8"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/sveltekit-ts/frontend/package-lock.json b/v3/internal/templates/sveltekit-ts/frontend/package-lock.json
deleted file mode 100644
index 4ba4bd3f3..000000000
--- a/v3/internal/templates/sveltekit-ts/frontend/package-lock.json
+++ /dev/null
@@ -1,1288 +0,0 @@
-{
- "name": "frontend",
- "version": "0.0.1",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "frontend",
- "version": "0.0.1",
- "dependencies": {
- "@sveltejs/adapter-static": "^3.0.5",
- "@wailsio/runtime": "^3.0.0-alpha.28"
- },
- "devDependencies": {
- "@sveltejs/kit": "^2.0.0",
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.2.7",
- "svelte-check": "^4.0.0",
- "typescript": "^5.0.0",
- "vite": "^5.0.3"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@polka/url": {
- "version": "1.0.0-next.28",
- "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
- "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw=="
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
- "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
- "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
- "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
- "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
- "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
- "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
- "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
- "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
- "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
- "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
- "cpu": [
- "riscv64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
- "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
- "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
- "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
- "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
- "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
- "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sveltejs/adapter-static": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.5.tgz",
- "integrity": "sha512-kFJR7RxeB6FBvrKZWAEzIALatgy11ISaaZbcPup8JdWUdrmmfUHHTJ738YHJTEfnCiiXi6aX8Q6ePY7tnSMD6Q==",
- "peerDependencies": {
- "@sveltejs/kit": "^2.0.0"
- }
- },
- "node_modules/@sveltejs/kit": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.7.1.tgz",
- "integrity": "sha512-TBVnkwgYQT3EafGQK6Eyh5FlLEBlRhCmqPTwcdOs+QdnyUc3eCAxRWtXlFxIWtmk6pqv11zdng8qTpThdTogew==",
- "hasInstallScript": true,
- "dependencies": {
- "@types/cookie": "^0.6.0",
- "cookie": "^0.6.0",
- "devalue": "^5.1.0",
- "esm-env": "^1.0.0",
- "import-meta-resolve": "^4.1.0",
- "kleur": "^4.1.5",
- "magic-string": "^0.30.5",
- "mrmime": "^2.0.0",
- "sade": "^1.8.1",
- "set-cookie-parser": "^2.6.0",
- "sirv": "^3.0.0",
- "tiny-glob": "^0.2.9"
- },
- "bin": {
- "svelte-kit": "svelte-kit.js"
- },
- "engines": {
- "node": ">=18.13"
- },
- "peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.3"
- }
- },
- "node_modules/@sveltejs/vite-plugin-svelte": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
- "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
- "dependencies": {
- "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
- "debug": "^4.3.4",
- "deepmerge": "^4.3.1",
- "kleur": "^4.1.5",
- "magic-string": "^0.30.10",
- "svelte-hmr": "^0.16.0",
- "vitefu": "^0.2.5"
- },
- "engines": {
- "node": "^18.0.0 || >=20"
- },
- "peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.0"
- }
- },
- "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
- "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
- "dependencies": {
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.0.0 || >=20"
- },
- "peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.0"
- }
- },
- "node_modules/@types/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
- },
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
- },
- "node_modules/@wailsio/runtime": {
- "version": "3.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.28.tgz",
- "integrity": "sha512-caMnAcKxxDrIWYgCZAMY2kdL++X4ehO2+JvH5na21xfDqz3VnHkEjxsH3jfhgd34M8LY80QEH8iqoMYytDFE/g==",
- "dependencies": {
- "nanoid": "^5.0.7"
- }
- },
- "node_modules/@wailsio/runtime/node_modules/nanoid": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
- "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.js"
- },
- "engines": {
- "node": "^18 || >=20"
- }
- },
- "node_modules/acorn": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
- "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/chokidar": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
- "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
- "dev": true,
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/code-red": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
- "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@types/estree": "^1.0.1",
- "acorn": "^8.10.0",
- "estree-walker": "^3.0.3",
- "periscopic": "^3.1.0"
- }
- },
- "node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/devalue": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
- "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="
- },
- "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==",
- "hasInstallScript": true,
- "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/esm-env": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
- "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
- },
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
- "node_modules/fdir": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.0.tgz",
- "integrity": "sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==",
- "dev": true,
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/globalyzer": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
- "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
- },
- "node_modules/globrex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
- "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
- },
- "node_modules/import-meta-resolve": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
- "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/is-reference": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
- "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
- "dependencies": {
- "@types/estree": "*"
- }
- },
- "node_modules/kleur": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
- "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/locate-character": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
- "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
- },
- "node_modules/magic-string": {
- "version": "0.30.12",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
- "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
- }
- },
- "node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
- },
- "node_modules/mri": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
- "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mrmime": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
- "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/periscopic": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
- "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^3.0.0",
- "is-reference": "^3.0.0"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
- "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
- },
- "node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
- "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"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/readdirp": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
- "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
- "dev": true,
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/rollup": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
- "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
- "dependencies": {
- "@types/estree": "1.0.6"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.24.0",
- "@rollup/rollup-android-arm64": "4.24.0",
- "@rollup/rollup-darwin-arm64": "4.24.0",
- "@rollup/rollup-darwin-x64": "4.24.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
- "@rollup/rollup-linux-arm64-gnu": "4.24.0",
- "@rollup/rollup-linux-arm64-musl": "4.24.0",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
- "@rollup/rollup-linux-s390x-gnu": "4.24.0",
- "@rollup/rollup-linux-x64-gnu": "4.24.0",
- "@rollup/rollup-linux-x64-musl": "4.24.0",
- "@rollup/rollup-win32-arm64-msvc": "4.24.0",
- "@rollup/rollup-win32-ia32-msvc": "4.24.0",
- "@rollup/rollup-win32-x64-msvc": "4.24.0",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/sade": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
- "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
- "dependencies": {
- "mri": "^1.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/set-cookie-parser": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",
- "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ=="
- },
- "node_modules/sirv": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
- "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
- "dependencies": {
- "@polka/url": "^1.0.0-next.24",
- "mrmime": "^2.0.0",
- "totalist": "^3.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "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==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/svelte": {
- "version": "4.2.19",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
- "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
- "dependencies": {
- "@ampproject/remapping": "^2.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/estree": "^1.0.1",
- "acorn": "^8.9.0",
- "aria-query": "^5.3.0",
- "axobject-query": "^4.0.0",
- "code-red": "^1.0.3",
- "css-tree": "^2.3.1",
- "estree-walker": "^3.0.3",
- "is-reference": "^3.0.1",
- "locate-character": "^3.0.0",
- "magic-string": "^0.30.4",
- "periscopic": "^3.1.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/svelte-check": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.0.5.tgz",
- "integrity": "sha512-icBTBZ3ibBaywbXUat3cK6hB5Du+Kq9Z8CRuyLmm64XIe2/r+lQcbuBx/IQgsbrC+kT2jQ0weVpZSSRIPwB6jQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.25",
- "chokidar": "^4.0.1",
- "fdir": "^6.2.0",
- "picocolors": "^1.0.0",
- "sade": "^1.7.4"
- },
- "bin": {
- "svelte-check": "bin/svelte-check"
- },
- "engines": {
- "node": ">= 18.0.0"
- },
- "peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "typescript": ">=5.0.0"
- }
- },
- "node_modules/svelte-hmr": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
- "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
- "engines": {
- "node": "^12.20 || ^14.13.1 || >= 16"
- },
- "peerDependencies": {
- "svelte": "^3.19.0 || ^4.0.0"
- }
- },
- "node_modules/tiny-glob": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
- "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
- "dependencies": {
- "globalyzer": "0.1.0",
- "globrex": "^0.1.2"
- }
- },
- "node_modules/totalist": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
- "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/typescript": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
- "dev": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/vite": {
- "version": "5.4.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
- "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
- "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/vitefu": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
- "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
- "peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
- },
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
- }
- }
- }
-}
diff --git a/v3/internal/templates/sveltekit-ts/frontend/package.json b/v3/internal/templates/sveltekit-ts/frontend/package.json
index c545acab9..58a6b3eb7 100644
--- a/v3/internal/templates/sveltekit-ts/frontend/package.json
+++ b/v3/internal/templates/sveltekit-ts/frontend/package.json
@@ -1,26 +1,26 @@
{
- "name": "frontend",
- "version": "0.0.1",
- "private": true,
- "scripts": {
- "dev": "vite dev",
- "build:dev": "vite build --minify false --mode development",
- "build": "vite build --mode production",
- "preview": "vite preview",
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
- },
- "devDependencies": {
- "@sveltejs/kit": "^2.0.0",
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.2.7",
- "svelte-check": "^4.0.0",
- "typescript": "^5.0.0",
- "vite": "^5.0.3"
- },
- "type": "module",
- "dependencies": {
- "@sveltejs/adapter-static": "^3.0.5",
- "@wailsio/runtime": "^3.0.0-alpha.28"
- }
+ "name": "frontend",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite dev",
+ "build:dev": "vite build --minify false --mode development",
+ "build": "vite build --mode production",
+ "preview": "vite preview",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
+ },
+ "dependencies": {
+ "@wailsio/runtime": "^3.0.0-alpha.28"
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-static": "^3.0.5",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "svelte": "^4.2.7",
+ "svelte-check": "^4.0.0",
+ "typescript": "^5.0.0",
+ "vite": "^5.0.3"
+ }
}
diff --git a/v3/internal/templates/sveltekit/frontend/package-lock.json b/v3/internal/templates/sveltekit/frontend/package-lock.json
deleted file mode 100644
index 7e20bdb45..000000000
--- a/v3/internal/templates/sveltekit/frontend/package-lock.json
+++ /dev/null
@@ -1,1221 +0,0 @@
-{
- "name": "frontend",
- "version": "0.0.1",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "frontend",
- "version": "0.0.1",
- "dependencies": {
- "@sveltejs/adapter-static": "^3.0.5",
- "@wailsio/runtime": "^3.0.0-alpha.28"
- },
- "devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
- "@sveltejs/kit": "^2.0.0",
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.2.7",
- "vite": "^5.0.3"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@polka/url": {
- "version": "1.0.0-next.28",
- "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
- "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw=="
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
- "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
- "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
- "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
- "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
- "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
- "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
- "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
- "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
- "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
- "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
- "cpu": [
- "riscv64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
- "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
- "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
- "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
- "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
- "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
- "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sveltejs/adapter-auto": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.5.tgz",
- "integrity": "sha512-27LR+uKccZ62lgq4N/hvyU2G+hTP9fxWEAfnZcl70HnyfAjMSsGk1z/SjAPXNCD1mVJIE7IFu3TQ8cQ/UH3c0A==",
- "dev": true,
- "dependencies": {
- "import-meta-resolve": "^4.1.0"
- },
- "peerDependencies": {
- "@sveltejs/kit": "^2.0.0"
- }
- },
- "node_modules/@sveltejs/adapter-static": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.5.tgz",
- "integrity": "sha512-kFJR7RxeB6FBvrKZWAEzIALatgy11ISaaZbcPup8JdWUdrmmfUHHTJ738YHJTEfnCiiXi6aX8Q6ePY7tnSMD6Q==",
- "peerDependencies": {
- "@sveltejs/kit": "^2.0.0"
- }
- },
- "node_modules/@sveltejs/kit": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.7.1.tgz",
- "integrity": "sha512-TBVnkwgYQT3EafGQK6Eyh5FlLEBlRhCmqPTwcdOs+QdnyUc3eCAxRWtXlFxIWtmk6pqv11zdng8qTpThdTogew==",
- "hasInstallScript": true,
- "dependencies": {
- "@types/cookie": "^0.6.0",
- "cookie": "^0.6.0",
- "devalue": "^5.1.0",
- "esm-env": "^1.0.0",
- "import-meta-resolve": "^4.1.0",
- "kleur": "^4.1.5",
- "magic-string": "^0.30.5",
- "mrmime": "^2.0.0",
- "sade": "^1.8.1",
- "set-cookie-parser": "^2.6.0",
- "sirv": "^3.0.0",
- "tiny-glob": "^0.2.9"
- },
- "bin": {
- "svelte-kit": "svelte-kit.js"
- },
- "engines": {
- "node": ">=18.13"
- },
- "peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.3"
- }
- },
- "node_modules/@sveltejs/vite-plugin-svelte": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
- "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
- "dependencies": {
- "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
- "debug": "^4.3.4",
- "deepmerge": "^4.3.1",
- "kleur": "^4.1.5",
- "magic-string": "^0.30.10",
- "svelte-hmr": "^0.16.0",
- "vitefu": "^0.2.5"
- },
- "engines": {
- "node": "^18.0.0 || >=20"
- },
- "peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.0"
- }
- },
- "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
- "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
- "dependencies": {
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.0.0 || >=20"
- },
- "peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.0"
- }
- },
- "node_modules/@types/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
- },
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
- },
- "node_modules/@wailsio/runtime": {
- "version": "3.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.28.tgz",
- "integrity": "sha512-caMnAcKxxDrIWYgCZAMY2kdL++X4ehO2+JvH5na21xfDqz3VnHkEjxsH3jfhgd34M8LY80QEH8iqoMYytDFE/g==",
- "dependencies": {
- "nanoid": "^5.0.7"
- }
- },
- "node_modules/@wailsio/runtime/node_modules/nanoid": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
- "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.js"
- },
- "engines": {
- "node": "^18 || >=20"
- }
- },
- "node_modules/acorn": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
- "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/code-red": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
- "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@types/estree": "^1.0.1",
- "acorn": "^8.10.0",
- "estree-walker": "^3.0.3",
- "periscopic": "^3.1.0"
- }
- },
- "node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/devalue": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
- "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="
- },
- "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==",
- "hasInstallScript": true,
- "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/esm-env": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
- "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
- },
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dependencies": {
- "@types/estree": "^1.0.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==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/globalyzer": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
- "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
- },
- "node_modules/globrex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
- "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
- },
- "node_modules/import-meta-resolve": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
- "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/is-reference": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
- "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
- "dependencies": {
- "@types/estree": "*"
- }
- },
- "node_modules/kleur": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
- "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/locate-character": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
- "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
- },
- "node_modules/magic-string": {
- "version": "0.30.12",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
- "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
- }
- },
- "node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
- },
- "node_modules/mri": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
- "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mrmime": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
- "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/periscopic": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
- "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^3.0.0",
- "is-reference": "^3.0.0"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
- "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
- },
- "node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
- "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"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/rollup": {
- "version": "4.24.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
- "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
- "dependencies": {
- "@types/estree": "1.0.6"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.24.0",
- "@rollup/rollup-android-arm64": "4.24.0",
- "@rollup/rollup-darwin-arm64": "4.24.0",
- "@rollup/rollup-darwin-x64": "4.24.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
- "@rollup/rollup-linux-arm64-gnu": "4.24.0",
- "@rollup/rollup-linux-arm64-musl": "4.24.0",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
- "@rollup/rollup-linux-s390x-gnu": "4.24.0",
- "@rollup/rollup-linux-x64-gnu": "4.24.0",
- "@rollup/rollup-linux-x64-musl": "4.24.0",
- "@rollup/rollup-win32-arm64-msvc": "4.24.0",
- "@rollup/rollup-win32-ia32-msvc": "4.24.0",
- "@rollup/rollup-win32-x64-msvc": "4.24.0",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/sade": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
- "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
- "dependencies": {
- "mri": "^1.1.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/set-cookie-parser": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",
- "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ=="
- },
- "node_modules/sirv": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
- "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
- "dependencies": {
- "@polka/url": "^1.0.0-next.24",
- "mrmime": "^2.0.0",
- "totalist": "^3.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "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==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/svelte": {
- "version": "4.2.19",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
- "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
- "dependencies": {
- "@ampproject/remapping": "^2.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/estree": "^1.0.1",
- "acorn": "^8.9.0",
- "aria-query": "^5.3.0",
- "axobject-query": "^4.0.0",
- "code-red": "^1.0.3",
- "css-tree": "^2.3.1",
- "estree-walker": "^3.0.3",
- "is-reference": "^3.0.1",
- "locate-character": "^3.0.0",
- "magic-string": "^0.30.4",
- "periscopic": "^3.1.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/svelte-hmr": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
- "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
- "engines": {
- "node": "^12.20 || ^14.13.1 || >= 16"
- },
- "peerDependencies": {
- "svelte": "^3.19.0 || ^4.0.0"
- }
- },
- "node_modules/tiny-glob": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
- "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
- "dependencies": {
- "globalyzer": "0.1.0",
- "globrex": "^0.1.2"
- }
- },
- "node_modules/totalist": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
- "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/vite": {
- "version": "5.4.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz",
- "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==",
- "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/vitefu": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
- "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
- "peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
- },
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
- }
- }
- }
-}
diff --git a/v3/internal/templates/sveltekit/frontend/package.json b/v3/internal/templates/sveltekit/frontend/package.json
index 03427d5b8..54d96b52c 100644
--- a/v3/internal/templates/sveltekit/frontend/package.json
+++ b/v3/internal/templates/sveltekit/frontend/package.json
@@ -1,25 +1,24 @@
{
- "name": "frontend",
- "version": "0.0.1",
- "private": true,
- "scripts": {
- "dev": "vite dev",
- "build:dev": "vite build --minify false --mode development",
- "build": "vite build --mode production",
- "preview": "vite preview",
- "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
- },
- "devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
- "@sveltejs/kit": "^2.0.0",
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.2.7",
- "vite": "^5.0.3"
- },
- "type": "module",
- "dependencies": {
- "@sveltejs/adapter-static": "^3.0.5",
- "@wailsio/runtime": "^3.0.0-alpha.28"
- }
+ "name": "frontend",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite dev",
+ "build:dev": "vite build --minify false --mode development",
+ "build": "vite build --mode production",
+ "preview": "vite preview",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
+ },
+ "dependencies": {
+ "@wailsio/runtime": "^3.0.0-alpha.28"
+ },
+ "devDependencies": {
+ "@sveltejs/adapter-static": "^3.0.5",
+ "@sveltejs/kit": "^2.0.0",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "svelte": "^4.2.7",
+ "vite": "^5.0.3"
+ }
}
diff --git a/v3/internal/templates/vanilla-ts/frontend/package.json b/v3/internal/templates/vanilla-ts/frontend/package.json
index 4d675f189..b39da7ece 100644
--- a/v3/internal/templates/vanilla-ts/frontend/package.json
+++ b/v3/internal/templates/vanilla-ts/frontend/package.json
@@ -9,9 +9,11 @@
"build": "tsc && vite build --mode production",
"preview": "vite preview"
},
+ "dependencies": {
+ "@wailsio/runtime": "latest"
+ },
"devDependencies": {
"typescript": "^4.9.3",
- "vite": "^5.0.0",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/vanilla/frontend/package.json b/v3/internal/templates/vanilla/frontend/package.json
index 9ae87549e..0a118e984 100644
--- a/v3/internal/templates/vanilla/frontend/package.json
+++ b/v3/internal/templates/vanilla/frontend/package.json
@@ -9,8 +9,10 @@
"build": "vite build --mode production",
"preview": "vite preview"
},
- "devDependencies": {
- "vite": "^5.0.0",
+ "dependencies": {
"@wailsio/runtime": "latest"
+ },
+ "devDependencies": {
+ "vite": "^5.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/vue-ts/frontend/package.json b/v3/internal/templates/vue-ts/frontend/package.json
index 60bd3c63d..dc08ffa11 100644
--- a/v3/internal/templates/vue-ts/frontend/package.json
+++ b/v3/internal/templates/vue-ts/frontend/package.json
@@ -10,13 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"vue": "^3.2.45"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"typescript": "^4.9.3",
"vite": "^5.0.0",
- "@wailsio/runtime": "latest",
"vue-tsc": "^1.0.11"
}
-}
\ No newline at end of file
+}
diff --git a/v3/internal/templates/vue/frontend/package.json b/v3/internal/templates/vue/frontend/package.json
index 7cd67be52..6958956b3 100644
--- a/v3/internal/templates/vue/frontend/package.json
+++ b/v3/internal/templates/vue/frontend/package.json
@@ -10,11 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
+ "@wailsio/runtime": "latest",
"vue": "^3.2.45"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
- "vite": "^5.0.0",
- "@wailsio/runtime": "latest"
+ "vite": "^5.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go
index 21c0792cc..b3a8f6f34 100644
--- a/v3/pkg/application/application.go
+++ b/v3/pkg/application/application.go
@@ -345,6 +345,10 @@ type App struct {
singleInstanceManager *singleInstanceManager
}
+func (a *App) Config() Options {
+ return a.options
+}
+
func (a *App) handleWarning(msg string) {
if a.options.WarningHandler != nil {
a.options.WarningHandler(msg)
diff --git a/v3/pkg/application/dialogs.go b/v3/pkg/application/dialogs.go
index 1e52225ce..2c832d5f2 100644
--- a/v3/pkg/application/dialogs.go
+++ b/v3/pkg/application/dialogs.go
@@ -291,6 +291,9 @@ func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) {
}
selections, err := InvokeSyncWithResultAndError(d.impl.show)
+ if err != nil {
+ return nil, err
+ }
var result []string
for filename := range selections {
diff --git a/v3/pkg/application/dialogs_windows.go b/v3/pkg/application/dialogs_windows.go
index 36e153141..ef5b625fc 100644
--- a/v3/pkg/application/dialogs_windows.go
+++ b/v3/pkg/application/dialogs_windows.go
@@ -247,7 +247,20 @@ func showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any,
}()
if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
- return multi.ShowAndGetResults()
+ paths, err := multi.ShowAndGetResults()
+ if err != nil {
+ return nil, err
+ }
+
+ for i, path := range paths {
+ paths[i] = filepath.Clean(path)
+ }
+ return paths, nil
}
- return dlg.ShowAndGetResult()
+
+ path, err := dlg.ShowAndGetResult()
+ if err != nil {
+ return nil, err
+ }
+ return filepath.Clean(path), nil
}
diff --git a/v3/pkg/application/dialogs_windows_test.go b/v3/pkg/application/dialogs_windows_test.go
new file mode 100644
index 000000000..af4dabf75
--- /dev/null
+++ b/v3/pkg/application/dialogs_windows_test.go
@@ -0,0 +1,57 @@
+//go:build windows
+
+package application_test
+
+import (
+ "path/filepath"
+ "testing"
+
+ "github.com/matryer/is"
+)
+
+func TestCleanPath(t *testing.T) {
+ i := is.New(t)
+ tests := []struct {
+ name string
+ inputPath string
+ expected string
+ }{
+ {
+ name: "path with double separators",
+ inputPath: `C:\\temp\\folder`,
+ expected: `C:\temp\folder`,
+ },
+ {
+ name: "path with forward slashes",
+ inputPath: `C://temp//folder`,
+ expected: `C:\temp\folder`,
+ },
+ {
+ name: "path with trailing separator",
+ inputPath: `C:\\temp\\folder\\`,
+ expected: `C:\temp\folder`,
+ },
+ {
+ name: "path with escaped tab character",
+ inputPath: `C:\\Users\\test\\tab.txt`,
+ expected: `C:\Users\test\tab.txt`,
+ },
+ {
+ name: "newline character",
+ inputPath: `C:\\Users\\test\\newline\\n.txt`,
+ expected: `C:\Users\test\newline\n.txt`,
+ },
+ {
+ name: "UNC path with multiple separators",
+ inputPath: `\\\\\\\\host\\share\\test.txt`,
+ expected: `\\\\host\share\test.txt`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cleaned := filepath.Clean(tt.inputPath)
+ i.Equal(cleaned, tt.expected)
+ })
+ }
+}
diff --git a/v3/pkg/application/menu_darwin.go b/v3/pkg/application/menu_darwin.go
index 1e3fc7197..a17031489 100644
--- a/v3/pkg/application/menu_darwin.go
+++ b/v3/pkg/application/menu_darwin.go
@@ -84,9 +84,6 @@ func (m *macosMenu) update() {
func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
for _, item := range menu.items {
- if item.hidden {
- continue
- }
switch item.itemType {
case submenu:
submenu := item.submenu
@@ -102,6 +99,9 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
case text, checkbox, radio:
menuItem := newMenuItemImpl(item)
item.impl = menuItem
+ if item.hidden {
+ menuItem.setHidden(true)
+ }
C.addMenuItem(parent, menuItem.nsMenuItem)
case separator:
C.addMenuSeparator(parent)
diff --git a/v3/pkg/application/menu_windows.go b/v3/pkg/application/menu_windows.go
index 04a089982..b75ac74d1 100644
--- a/v3/pkg/application/menu_windows.go
+++ b/v3/pkg/application/menu_windows.go
@@ -36,6 +36,14 @@ func (w *windowsMenu) update() {
func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
for _, item := range inputMenu.items {
+ w.currentMenuID++
+ itemID := w.currentMenuID
+ w.menuMapping[itemID] = item
+
+ menuItemImpl := newMenuItemImpl(item, parentMenu, itemID)
+ menuItemImpl.parent = inputMenu
+ item.impl = menuItemImpl
+
if item.Hidden() {
if item.accelerator != nil && item.callback != nil {
if w.parentWindow != nil {
@@ -44,11 +52,7 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
globalApplication.removeKeyBinding(item.accelerator.String())
}
}
- continue
}
- w.currentMenuID++
- itemID := w.currentMenuID
- w.menuMapping[itemID] = item
flags := uint32(w32.MF_STRING)
if item.disabled {
@@ -84,6 +88,11 @@ func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
}
var menuText = w32.MustStringToUTF16Ptr(thisText)
+ // If the item is hidden, don't append
+ if item.Hidden() {
+ continue
+ }
+
w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
if item.bitmap != nil {
w32.SetMenuIcons(parentMenu, itemID, item.bitmap, nil)
diff --git a/v3/pkg/application/menuitem_windows.go b/v3/pkg/application/menuitem_windows.go
index 44d825f1b..825b2f05c 100644
--- a/v3/pkg/application/menuitem_windows.go
+++ b/v3/pkg/application/menuitem_windows.go
@@ -24,9 +24,9 @@ type windowsMenuItem struct {
}
func (m *windowsMenuItem) setHidden(hidden bool) {
- m.hidden = hidden
- if m.hidden {
- // iterate the parent items and find the menu item before us
+ if hidden && !m.hidden {
+ m.hidden = true
+ // iterate the parent items and find the menu item after us
for i, item := range m.parent.items {
if item == m.menuItem {
if i < len(m.parent.items)-1 {
@@ -37,13 +37,11 @@ func (m *windowsMenuItem) setHidden(hidden bool) {
break
}
}
- // Get the position of this menu item in the parent menu
- // m.pos = w32.GetMenuItemPosition(m.hMenu, uint32(m.id))
// Remove from parent menu
w32.RemoveMenu(m.hMenu, m.id, w32.MF_BYCOMMAND)
- } else {
- // Add to parent menu
- // Get the position of the item before us
+ } else if !hidden && m.hidden {
+ m.hidden = false
+ // Add to parent menu before the "itemAfter"
var pos int
if m.itemAfter != nil {
for i, item := range m.parent.items {
diff --git a/v3/pkg/application/popupmenu_windows.go b/v3/pkg/application/popupmenu_windows.go
index fbfd5eef9..bad9ad71e 100644
--- a/v3/pkg/application/popupmenu_windows.go
+++ b/v3/pkg/application/popupmenu_windows.go
@@ -61,6 +61,14 @@ func (p *Win32Menu) newMenu() w32.HMENU {
func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
currentRadioGroup := RadioGroup{}
for _, item := range inputMenu.items {
+ p.currentMenuID++
+ itemID := p.currentMenuID
+ p.menuMapping[itemID] = item
+
+ menuItemImpl := newMenuItemImpl(item, parentMenu, itemID)
+ menuItemImpl.parent = inputMenu
+ item.impl = menuItemImpl
+
if item.Hidden() {
if item.accelerator != nil {
if p.parentWindow != nil {
@@ -71,14 +79,7 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
globalApplication.removeKeyBinding(item.accelerator.String())
}
}
- continue
}
- p.currentMenuID++
- itemID := p.currentMenuID
- p.menuMapping[itemID] = item
-
- menuItemImpl := newMenuItemImpl(item, parentMenu, itemID)
- menuItemImpl.parent = inputMenu
flags := uint32(w32.MF_STRING)
if item.disabled {
@@ -131,6 +132,12 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
}
}
}
+
+ // If the item is hidden, don't append
+ if item.Hidden() {
+ continue
+ }
+
ok := w32.AppendMenu(parentMenu, flags, uintptr(itemID), w32.MustStringToUTF16Ptr(menuText))
if !ok {
globalApplication.fatal("error adding menu item '%s'", menuText)
@@ -141,8 +148,6 @@ func (p *Win32Menu) buildMenu(parentMenu w32.HMENU, inputMenu *Menu) {
globalApplication.fatal("error setting menu icons: %w", err)
}
}
-
- item.impl = menuItemImpl
}
if len(currentRadioGroup) > 0 {
for _, radioMember := range currentRadioGroup {
diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go
index b5903e33c..374c423b7 100644
--- a/v3/pkg/application/webview_window.go
+++ b/v3/pkg/application/webview_window.go
@@ -108,6 +108,7 @@ type (
showMenuBar()
hideMenuBar()
toggleMenuBar()
+ setMenu(menu *Menu)
}
)
@@ -168,6 +169,22 @@ type WebviewWindow struct {
unconditionallyClose bool
}
+func (w *WebviewWindow) SetMenu(menu *Menu) {
+ switch runtime.GOOS {
+ case "darwin":
+ return
+ case "windows":
+ w.options.Windows.Menu = menu
+ case "linux":
+ w.options.Linux.Menu = menu
+ }
+ if w.impl != nil {
+ InvokeSync(func() {
+ w.impl.setMenu(menu)
+ })
+ }
+}
+
// EmitEvent emits an event from the window
func (w *WebviewWindow) EmitEvent(name string, data ...any) {
globalApplication.emitEvent(&CustomEvent{
diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go
index 668d3c8e9..28e693a23 100644
--- a/v3/pkg/application/webview_window_darwin.go
+++ b/v3/pkg/application/webview_window_darwin.go
@@ -1427,6 +1427,7 @@ func (w *macosWebviewWindow) delete() {
func (w *macosWebviewWindow) redo() {
}
-func (w *macosWebviewWindow) showMenuBar() {}
-func (w *macosWebviewWindow) hideMenuBar() {}
-func (w *macosWebviewWindow) toggleMenuBar() {}
+func (w *macosWebviewWindow) showMenuBar() {}
+func (w *macosWebviewWindow) hideMenuBar() {}
+func (w *macosWebviewWindow) toggleMenuBar() {}
+func (w *macosWebviewWindow) setMenu(_ *Menu) {}
diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go
index 4bef34335..8c50c6117 100644
--- a/v3/pkg/application/webview_window_linux.go
+++ b/v3/pkg/application/webview_window_linux.go
@@ -235,6 +235,15 @@ func (w *linuxWebviewWindow) setPhysicalBounds(physicalBounds Rect) {
w.setBounds(physicalBounds)
}
+func (w *linuxWebviewWindow) setMenu(menu *Menu) {
+ if menu == nil {
+ w.gtkmenu = nil
+ return
+ }
+ w.parent.options.Linux.Menu = menu
+ w.gtkmenu = (menu.impl).(*linuxMenu).native
+}
+
func (w *linuxWebviewWindow) run() {
for eventId := range w.parent.eventListeners {
w.on(eventId)
diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go
index ecd20002f..05647b09b 100644
--- a/v3/pkg/application/webview_window_windows.go
+++ b/v3/pkg/application/webview_window_windows.go
@@ -73,6 +73,13 @@ type windowsWebviewWindow struct {
isMinimizing bool
}
+func (w *windowsWebviewWindow) setMenu(menu *Menu) {
+ menu.Update()
+ w.menu = NewApplicationMenu(w, menu)
+ w.menu.parentWindow = w
+ w32.SetMenu(w.hwnd, w.menu.menu)
+}
+
func (w *windowsWebviewWindow) cut() {
w.execJS("document.execCommand('cut')")
}
diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go
index d1a4219ba..0d688126b 100644
--- a/v3/pkg/application/window.go
+++ b/v3/pkg/application/window.go
@@ -84,4 +84,5 @@ type Window interface {
ZoomIn()
ZoomOut()
ZoomReset() Window
+ SetMenu(menu *Menu)
}
diff --git a/v3/pkg/services/notifications/notifications.go b/v3/pkg/services/notifications/notifications.go
new file mode 100644
index 000000000..1a33d6d56
--- /dev/null
+++ b/v3/pkg/services/notifications/notifications.go
@@ -0,0 +1,216 @@
+// Package notifications provides cross-platform notification capabilities for desktop applications.
+// It supports macOS, Windows, and Linux with a consistent API while handling platform-specific
+// differences internally. Key features include:
+// - Basic notifications with title, subtitle, and body
+// - Interactive notifications with buttons and actions
+// - Notification categories for reusing configurations
+// - User feedback handling with a unified callback system
+//
+// Platform-specific notes:
+// - macOS: Requires a properly bundled and signed application
+// - Windows: Uses Windows Toast notifications
+// - Linux: Uses D-Bus and does not support text inputs
+package notifications
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type platformNotifier interface {
+ // Lifecycle methods
+ Startup(ctx context.Context, options application.ServiceOptions) error
+ Shutdown() error
+
+ // Core notification methods
+ RequestNotificationAuthorization() (bool, error)
+ CheckNotificationAuthorization() (bool, error)
+ SendNotification(options NotificationOptions) error
+ SendNotificationWithActions(options NotificationOptions) error
+
+ // Category management
+ RegisterNotificationCategory(category NotificationCategory) error
+ RemoveNotificationCategory(categoryID string) error
+
+ // Notification management
+ RemoveAllPendingNotifications() error
+ RemovePendingNotification(identifier string) error
+ RemoveAllDeliveredNotifications() error
+ RemoveDeliveredNotification(identifier string) error
+ RemoveNotification(identifier string) error
+}
+
+// Service represents the notifications service
+type Service struct {
+ impl platformNotifier
+
+ // notificationResponseCallback is called when a notification result is received.
+ // Only one callback can be assigned at a time.
+ notificationResultCallback func(result NotificationResult)
+
+ callbackLock sync.RWMutex
+}
+
+var (
+ notificationServiceOnce sync.Once
+ NotificationService *Service
+ notificationServiceLock sync.RWMutex
+)
+
+// NotificationAction represents an action button for a notification.
+type NotificationAction struct {
+ ID string `json:"id,omitempty"`
+ Title string `json:"title,omitempty"`
+ Destructive bool `json:"destructive,omitempty"` // (macOS-specific)
+}
+
+// NotificationCategory groups actions for notifications.
+type NotificationCategory struct {
+ ID string `json:"id,omitempty"`
+ Actions []NotificationAction `json:"actions,omitempty"`
+ HasReplyField bool `json:"hasReplyField,omitempty"`
+ ReplyPlaceholder string `json:"replyPlaceholder,omitempty"`
+ ReplyButtonTitle string `json:"replyButtonTitle,omitempty"`
+}
+
+// NotificationOptions contains configuration for a notification
+type NotificationOptions struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only)
+ Body string `json:"body,omitempty"`
+ CategoryID string `json:"categoryId,omitempty"`
+ Data map[string]interface{} `json:"data,omitempty"`
+}
+
+const DefaultActionIdentifier = "DEFAULT_ACTION"
+
+// NotificationResponse represents the response sent by interacting with a notification.
+type NotificationResponse struct {
+ ID string `json:"id,omitempty"`
+ ActionIdentifier string `json:"actionIdentifier,omitempty"`
+ CategoryID string `json:"categoryIdentifier,omitempty"`
+ Title string `json:"title,omitempty"`
+ Subtitle string `json:"subtitle,omitempty"` // (macOS and Linux only)
+ Body string `json:"body,omitempty"`
+ UserText string `json:"userText,omitempty"`
+ UserInfo map[string]interface{} `json:"userInfo,omitempty"`
+}
+
+// NotificationResult represents the result of a notification response,
+// returning the response or any errors that occurred.
+type NotificationResult struct {
+ Response NotificationResponse
+ Error error
+}
+
+// ServiceName returns the name of the service.
+func (ns *Service) ServiceName() string {
+ return "github.com/wailsapp/wails/v3/services/notifications"
+}
+
+// OnNotificationResponse registers a callback function that will be called when
+// a notification response is received from the user.
+//
+//wails:ignore
+func (ns *Service) OnNotificationResponse(callback func(result NotificationResult)) {
+ ns.callbackLock.Lock()
+ defer ns.callbackLock.Unlock()
+
+ ns.notificationResultCallback = callback
+}
+
+// handleNotificationResponse is an internal method to handle notification responses
+// and invoke the registered callback if one exists.
+func (ns *Service) handleNotificationResult(result NotificationResult) {
+ ns.callbackLock.RLock()
+ callback := ns.notificationResultCallback
+ ns.callbackLock.RUnlock()
+
+ if callback != nil {
+ callback(result)
+ }
+}
+
+// ServiceStartup is called when the service is loaded.
+func (ns *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
+ return ns.impl.Startup(ctx, options)
+}
+
+// ServiceShutdown is called when the service is unloaded.
+func (ns *Service) ServiceShutdown() error {
+ return ns.impl.Shutdown()
+}
+
+// Public methods that delegate to the implementation.
+func (ns *Service) RequestNotificationAuthorization() (bool, error) {
+ return ns.impl.RequestNotificationAuthorization()
+}
+
+func (ns *Service) CheckNotificationAuthorization() (bool, error) {
+ return ns.impl.CheckNotificationAuthorization()
+}
+
+func (ns *Service) SendNotification(options NotificationOptions) error {
+ if err := validateNotificationOptions(options); err != nil {
+ return err
+ }
+ return ns.impl.SendNotification(options)
+}
+
+func (ns *Service) SendNotificationWithActions(options NotificationOptions) error {
+ if err := validateNotificationOptions(options); err != nil {
+ return err
+ }
+ return ns.impl.SendNotificationWithActions(options)
+}
+
+func (ns *Service) RegisterNotificationCategory(category NotificationCategory) error {
+ return ns.impl.RegisterNotificationCategory(category)
+}
+
+func (ns *Service) RemoveNotificationCategory(categoryID string) error {
+ return ns.impl.RemoveNotificationCategory(categoryID)
+}
+
+func (ns *Service) RemoveAllPendingNotifications() error {
+ return ns.impl.RemoveAllPendingNotifications()
+}
+
+func (ns *Service) RemovePendingNotification(identifier string) error {
+ return ns.impl.RemovePendingNotification(identifier)
+}
+
+func (ns *Service) RemoveAllDeliveredNotifications() error {
+ return ns.impl.RemoveAllDeliveredNotifications()
+}
+
+func (ns *Service) RemoveDeliveredNotification(identifier string) error {
+ return ns.impl.RemoveDeliveredNotification(identifier)
+}
+
+func (ns *Service) RemoveNotification(identifier string) error {
+ return ns.impl.RemoveNotification(identifier)
+}
+
+func getNotificationService() *Service {
+ notificationServiceLock.RLock()
+ defer notificationServiceLock.RUnlock()
+ return NotificationService
+}
+
+// validateNotificationOptions validates an ID and Title are provided for notifications.
+func validateNotificationOptions(options NotificationOptions) error {
+ if options.ID == "" {
+ return fmt.Errorf("notification ID cannot be empty")
+ }
+
+ if options.Title == "" {
+ return fmt.Errorf("notification title cannot be empty")
+ }
+
+ return nil
+}
diff --git a/v3/pkg/services/notifications/notifications_darwin.go b/v3/pkg/services/notifications/notifications_darwin.go
new file mode 100644
index 000000000..2c8f33d15
--- /dev/null
+++ b/v3/pkg/services/notifications/notifications_darwin.go
@@ -0,0 +1,423 @@
+//go:build darwin
+
+package notifications
+
+/*
+#cgo CFLAGS:-x objective-c
+#cgo LDFLAGS: -framework Foundation -framework Cocoa
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
+#cgo LDFLAGS: -framework UserNotifications
+#endif
+
+#import "./notifications_darwin.h"
+*/
+import "C"
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "sync"
+ "time"
+ "unsafe"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type darwinNotifier struct {
+ channels map[int]chan notificationChannel
+ channelsLock sync.Mutex
+ nextChannelID int
+}
+
+type notificationChannel struct {
+ Success bool
+ Error error
+}
+
+type ChannelHandler interface {
+ GetChannel(id int) (chan notificationChannel, bool)
+}
+
+const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier"
+
+// Creates a new Notifications Service.
+// Your app must be packaged and signed for this feature to work.
+func New() *Service {
+ notificationServiceOnce.Do(func() {
+ impl := &darwinNotifier{
+ channels: make(map[int]chan notificationChannel),
+ nextChannelID: 0,
+ }
+
+ NotificationService = &Service{
+ impl: impl,
+ }
+ })
+
+ return NotificationService
+}
+
+func (dn *darwinNotifier) Startup(ctx context.Context, options application.ServiceOptions) error {
+ if !isNotificationAvailable() {
+ return fmt.Errorf("notifications are not available on this system")
+ }
+ if !checkBundleIdentifier() {
+ return fmt.Errorf("notifications require a valid bundle identifier")
+ }
+ return nil
+}
+
+func (dn *darwinNotifier) Shutdown() error {
+ return nil
+}
+
+// isNotificationAvailable checks if notifications are available on the system.
+func isNotificationAvailable() bool {
+ return bool(C.isNotificationAvailable())
+}
+
+func checkBundleIdentifier() bool {
+ return bool(C.checkBundleIdentifier())
+}
+
+// RequestNotificationAuthorization requests permission for notifications.
+// Default timeout is 3 minutes
+func (dn *darwinNotifier) RequestNotificationAuthorization() (bool, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
+ defer cancel()
+
+ id, resultCh := dn.registerChannel()
+
+ C.requestNotificationAuthorization(C.int(id))
+
+ select {
+ case result := <-resultCh:
+ return result.Success, result.Error
+ case <-ctx.Done():
+ dn.cleanupChannel(id)
+ return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err())
+ }
+}
+
+// CheckNotificationAuthorization checks current notification permission status.
+func (dn *darwinNotifier) CheckNotificationAuthorization() (bool, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+ defer cancel()
+
+ id, resultCh := dn.registerChannel()
+
+ C.checkNotificationAuthorization(C.int(id))
+
+ select {
+ case result := <-resultCh:
+ return result.Success, result.Error
+ case <-ctx.Done():
+ dn.cleanupChannel(id)
+ return false, fmt.Errorf("notification authorization timed out after 15s: %w", ctx.Err())
+ }
+}
+
+// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
+func (dn *darwinNotifier) SendNotification(options NotificationOptions) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ cIdentifier := C.CString(options.ID)
+ cTitle := C.CString(options.Title)
+ cSubtitle := C.CString(options.Subtitle)
+ cBody := C.CString(options.Body)
+ defer C.free(unsafe.Pointer(cIdentifier))
+ defer C.free(unsafe.Pointer(cTitle))
+ defer C.free(unsafe.Pointer(cSubtitle))
+ defer C.free(unsafe.Pointer(cBody))
+
+ var cDataJSON *C.char
+ if options.Data != nil {
+ jsonData, err := json.Marshal(options.Data)
+ if err != nil {
+ return fmt.Errorf("failed to marshal notification data: %w", err)
+ }
+ cDataJSON = C.CString(string(jsonData))
+ defer C.free(unsafe.Pointer(cDataJSON))
+ }
+
+ id, resultCh := dn.registerChannel()
+ C.sendNotification(C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON)
+
+ select {
+ case result := <-resultCh:
+ if !result.Success {
+ if result.Error != nil {
+ return result.Error
+ }
+ return fmt.Errorf("sending notification failed")
+ }
+ return nil
+ case <-ctx.Done():
+ dn.cleanupChannel(id)
+ return fmt.Errorf("sending notification timed out: %w", ctx.Err())
+ }
+}
+
+// SendNotificationWithActions sends a notification with additional actions and inputs.
+// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
+// If a NotificationCategory is not registered a basic notification will be sent.
+func (dn *darwinNotifier) SendNotificationWithActions(options NotificationOptions) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ cIdentifier := C.CString(options.ID)
+ cTitle := C.CString(options.Title)
+ cSubtitle := C.CString(options.Subtitle)
+ cBody := C.CString(options.Body)
+ cCategoryID := C.CString(options.CategoryID)
+ defer C.free(unsafe.Pointer(cIdentifier))
+ defer C.free(unsafe.Pointer(cTitle))
+ defer C.free(unsafe.Pointer(cSubtitle))
+ defer C.free(unsafe.Pointer(cBody))
+ defer C.free(unsafe.Pointer(cCategoryID))
+
+ var cDataJSON *C.char
+ if options.Data != nil {
+ jsonData, err := json.Marshal(options.Data)
+ if err != nil {
+ return fmt.Errorf("failed to marshal notification data: %w", err)
+ }
+ cDataJSON = C.CString(string(jsonData))
+ defer C.free(unsafe.Pointer(cDataJSON))
+ }
+
+ id, resultCh := dn.registerChannel()
+ C.sendNotificationWithActions(C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON)
+
+ select {
+ case result := <-resultCh:
+ if !result.Success {
+ if result.Error != nil {
+ return result.Error
+ }
+ return fmt.Errorf("sending notification failed")
+ }
+ return nil
+ case <-ctx.Done():
+ dn.cleanupChannel(id)
+ return fmt.Errorf("sending notification timed out: %w", ctx.Err())
+ }
+}
+
+// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
+// Registering a category with the same name as a previously registered NotificationCategory will override it.
+func (dn *darwinNotifier) RegisterNotificationCategory(category NotificationCategory) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ cCategoryID := C.CString(category.ID)
+ defer C.free(unsafe.Pointer(cCategoryID))
+
+ actionsJSON, err := json.Marshal(category.Actions)
+ if err != nil {
+ return fmt.Errorf("failed to marshal notification category: %w", err)
+ }
+ cActionsJSON := C.CString(string(actionsJSON))
+ defer C.free(unsafe.Pointer(cActionsJSON))
+
+ var cReplyPlaceholder, cReplyButtonTitle *C.char
+ if category.HasReplyField {
+ cReplyPlaceholder = C.CString(category.ReplyPlaceholder)
+ cReplyButtonTitle = C.CString(category.ReplyButtonTitle)
+ defer C.free(unsafe.Pointer(cReplyPlaceholder))
+ defer C.free(unsafe.Pointer(cReplyButtonTitle))
+ }
+
+ id, resultCh := dn.registerChannel()
+ C.registerNotificationCategory(C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField),
+ cReplyPlaceholder, cReplyButtonTitle)
+
+ select {
+ case result := <-resultCh:
+ if !result.Success {
+ if result.Error != nil {
+ return result.Error
+ }
+ return fmt.Errorf("category registration failed")
+ }
+ return nil
+ case <-ctx.Done():
+ dn.cleanupChannel(id)
+ return fmt.Errorf("category registration timed out: %w", ctx.Err())
+ }
+}
+
+// RemoveNotificationCategory remove a previously registered NotificationCategory.
+func (dn *darwinNotifier) RemoveNotificationCategory(categoryId string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ cCategoryID := C.CString(categoryId)
+ defer C.free(unsafe.Pointer(cCategoryID))
+
+ id, resultCh := dn.registerChannel()
+ C.removeNotificationCategory(C.int(id), cCategoryID)
+
+ select {
+ case result := <-resultCh:
+ if !result.Success {
+ if result.Error != nil {
+ return result.Error
+ }
+ return fmt.Errorf("category removal failed")
+ }
+ return nil
+ case <-ctx.Done():
+ dn.cleanupChannel(id)
+ return fmt.Errorf("category removal timed out: %w", ctx.Err())
+ }
+}
+
+// RemoveAllPendingNotifications removes all pending notifications.
+func (dn *darwinNotifier) RemoveAllPendingNotifications() error {
+ C.removeAllPendingNotifications()
+ return nil
+}
+
+// RemovePendingNotification removes a pending notification matching the unique identifier.
+func (dn *darwinNotifier) RemovePendingNotification(identifier string) error {
+ cIdentifier := C.CString(identifier)
+ defer C.free(unsafe.Pointer(cIdentifier))
+ C.removePendingNotification(cIdentifier)
+ return nil
+}
+
+// RemoveAllDeliveredNotifications removes all delivered notifications.
+func (dn *darwinNotifier) RemoveAllDeliveredNotifications() error {
+ C.removeAllDeliveredNotifications()
+ return nil
+}
+
+// RemoveDeliveredNotification removes a delivered notification matching the unique identifier.
+func (dn *darwinNotifier) RemoveDeliveredNotification(identifier string) error {
+ cIdentifier := C.CString(identifier)
+ defer C.free(unsafe.Pointer(cIdentifier))
+ C.removeDeliveredNotification(cIdentifier)
+ return nil
+}
+
+// RemoveNotification is a macOS stub that always returns nil.
+// Use one of the following instead:
+// RemoveAllPendingNotifications
+// RemovePendingNotification
+// RemoveAllDeliveredNotifications
+// RemoveDeliveredNotification
+// (Linux-specific)
+func (dn *darwinNotifier) RemoveNotification(identifier string) error {
+ return nil
+}
+
+//export captureResult
+func captureResult(channelID C.int, success C.bool, errorMsg *C.char) {
+ ns := getNotificationService()
+ if ns == nil {
+ return
+ }
+
+ handler, ok := ns.impl.(ChannelHandler)
+ if !ok {
+ return
+ }
+
+ resultCh, exists := handler.GetChannel(int(channelID))
+ if !exists {
+ return
+ }
+
+ var err error
+ if errorMsg != nil {
+ err = fmt.Errorf("%s", C.GoString(errorMsg))
+ }
+
+ resultCh <- notificationChannel{
+ Success: bool(success),
+ Error: err,
+ }
+
+ close(resultCh)
+}
+
+//export didReceiveNotificationResponse
+func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) {
+ result := NotificationResult{}
+
+ if err != nil {
+ errMsg := C.GoString(err)
+ result.Error = fmt.Errorf("notification response error: %s", errMsg)
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+ return
+ }
+
+ if jsonPayload == nil {
+ result.Error = fmt.Errorf("received nil JSON payload in notification response")
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+ return
+ }
+
+ payload := C.GoString(jsonPayload)
+
+ var response NotificationResponse
+ if err := json.Unmarshal([]byte(payload), &response); err != nil {
+ result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err)
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+ return
+ }
+
+ if response.ActionIdentifier == AppleDefaultActionIdentifier {
+ response.ActionIdentifier = DefaultActionIdentifier
+ }
+
+ result.Response = response
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+}
+
+// Helper methods
+
+func (dn *darwinNotifier) registerChannel() (int, chan notificationChannel) {
+ dn.channelsLock.Lock()
+ defer dn.channelsLock.Unlock()
+
+ id := dn.nextChannelID
+ dn.nextChannelID++
+
+ resultCh := make(chan notificationChannel, 1)
+
+ dn.channels[id] = resultCh
+ return id, resultCh
+}
+
+func (dn *darwinNotifier) GetChannel(id int) (chan notificationChannel, bool) {
+ dn.channelsLock.Lock()
+ defer dn.channelsLock.Unlock()
+
+ ch, exists := dn.channels[id]
+ if exists {
+ delete(dn.channels, id)
+ }
+ return ch, exists
+}
+
+func (dn *darwinNotifier) cleanupChannel(id int) {
+ dn.channelsLock.Lock()
+ defer dn.channelsLock.Unlock()
+
+ if ch, exists := dn.channels[id]; exists {
+ delete(dn.channels, id)
+ close(ch)
+ }
+}
diff --git a/v3/pkg/services/notifications/notifications_darwin.h b/v3/pkg/services/notifications/notifications_darwin.h
new file mode 100644
index 000000000..7cd505240
--- /dev/null
+++ b/v3/pkg/services/notifications/notifications_darwin.h
@@ -0,0 +1,21 @@
+//go:build darwin
+
+#ifndef NOTIFICATIONS_DARWIN_H
+#define NOTIFICATIONS_DARWIN_H
+
+#import
+
+bool checkBundleIdentifier(void);
+bool isNotificationAvailable(void);
+void requestNotificationAuthorization(int channelID);
+void checkNotificationAuthorization(int channelID);
+void sendNotification(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json);
+void sendNotificationWithActions(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json);
+void registerNotificationCategory(int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle);
+void removeNotificationCategory(int channelID, const char *categoryId);
+void removeAllPendingNotifications(void);
+void removePendingNotification(const char *identifier);
+void removeAllDeliveredNotifications(void);
+void removeDeliveredNotification(const char *identifier);
+
+#endif /* NOTIFICATIONS_DARWIN_H */
\ No newline at end of file
diff --git a/v3/pkg/services/notifications/notifications_darwin.m b/v3/pkg/services/notifications/notifications_darwin.m
new file mode 100644
index 000000000..6c2048b74
--- /dev/null
+++ b/v3/pkg/services/notifications/notifications_darwin.m
@@ -0,0 +1,377 @@
+#import "notifications_darwin.h"
+#include
+#import
+
+#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
+#import
+#endif
+
+bool isNotificationAvailable(void) {
+ if (@available(macOS 11.0, *)) {
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+bool checkBundleIdentifier(void) {
+ NSBundle *main = [NSBundle mainBundle];
+ if (main.bundleIdentifier == nil) {
+ return NO;
+ }
+ return YES;
+}
+
+extern void captureResult(int channelID, bool success, const char* error);
+extern void didReceiveNotificationResponse(const char *jsonPayload, const char* error);
+
+@interface NotificationsDelegate : NSObject
+@end
+
+@implementation NotificationsDelegate
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ willPresentNotification:(UNNotification *)notification
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
+ UNNotificationPresentationOptions options = 0;
+
+ if (@available(macOS 11.0, *)) {
+ // These options are only available in macOS 11.0+
+ options = UNNotificationPresentationOptionList |
+ UNNotificationPresentationOptionBanner |
+ UNNotificationPresentationOptionSound;
+ }
+
+ completionHandler(options);
+}
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+didReceiveNotificationResponse:(UNNotificationResponse *)response
+ withCompletionHandler:(void (^)(void))completionHandler {
+
+ NSMutableDictionary *payload = [NSMutableDictionary dictionary];
+
+ [payload setObject:response.notification.request.identifier forKey:@"id"];
+ [payload setObject:response.actionIdentifier forKey:@"actionIdentifier"];
+ [payload setObject:response.notification.request.content.title ?: @"" forKey:@"title"];
+ [payload setObject:response.notification.request.content.body ?: @"" forKey:@"body"];
+
+ if (response.notification.request.content.categoryIdentifier) {
+ [payload setObject:response.notification.request.content.categoryIdentifier forKey:@"categoryIdentifier"];
+ }
+
+ if (response.notification.request.content.subtitle) {
+ [payload setObject:response.notification.request.content.subtitle forKey:@"subtitle"];
+ }
+
+ if (response.notification.request.content.userInfo) {
+ [payload setObject:response.notification.request.content.userInfo forKey:@"userInfo"];
+ }
+
+ if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
+ UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *)response;
+ [payload setObject:textResponse.userText forKey:@"userText"];
+ }
+
+ NSError *error = nil;
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error];
+ if (error) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
+ didReceiveNotificationResponse(NULL, [errorMsg UTF8String]);
+ } else {
+ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+ didReceiveNotificationResponse([jsonString UTF8String], NULL);
+ }
+
+ completionHandler();
+}
+
+@end
+
+static NotificationsDelegate *delegateInstance = nil;
+static dispatch_once_t onceToken;
+
+static BOOL ensureDelegateInitialized(void) {
+ __block BOOL success = YES;
+
+ dispatch_once(&onceToken, ^{
+ delegateInstance = [[NotificationsDelegate alloc] init];
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ center.delegate = delegateInstance;
+ });
+
+ if (!delegateInstance) {
+ success = NO;
+ }
+
+ return success;
+}
+
+void requestNotificationAuthorization(int channelID) {
+ if (!ensureDelegateInitialized()) {
+ NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
+
+ [center requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ if (error) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ } else {
+ captureResult(channelID, granted, NULL);
+ }
+ }];
+}
+
+void checkNotificationAuthorization(int channelID) {
+ if (!ensureDelegateInitialized()) {
+ NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
+ BOOL isAuthorized = (settings.authorizationStatus == UNAuthorizationStatusAuthorized);
+ captureResult(channelID, isAuthorized, NULL);
+ }];
+}
+
+// Helper function to create notification content
+UNMutableNotificationContent* createNotificationContent(const char *title, const char *subtitle,
+ const char *body, const char *data_json, NSError **contentError) {
+ NSString *nsTitle = [NSString stringWithUTF8String:title];
+ NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @"";
+ NSString *nsBody = [NSString stringWithUTF8String:body];
+
+ UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
+ content.title = nsTitle;
+ if (![nsSubtitle isEqualToString:@""]) {
+ content.subtitle = nsSubtitle;
+ }
+ content.body = nsBody;
+ content.sound = [UNNotificationSound defaultSound];
+
+ // Parse JSON data if provided
+ if (data_json) {
+ NSString *dataJsonStr = [NSString stringWithUTF8String:data_json];
+ NSData *jsonData = [dataJsonStr dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error = nil;
+ NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
+ if (!error && parsedData) {
+ content.userInfo = parsedData;
+ } else if (error) {
+ *contentError = error;
+ }
+ }
+
+ return content;
+}
+
+void sendNotification(int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) {
+ if (!ensureDelegateInitialized()) {
+ NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+
+ NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
+
+ NSError *contentError = nil;
+ UNMutableNotificationContent *content = createNotificationContent(title, subtitle, body, data_json, &contentError);
+
+ if (contentError) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ UNTimeIntervalNotificationTrigger *trigger = nil;
+
+ UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger];
+
+ [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
+ if (error) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ } else {
+ captureResult(channelID, true, NULL);
+ }
+ }];
+}
+
+void sendNotificationWithActions(int channelID, const char *identifier, const char *title, const char *subtitle,
+ const char *body, const char *categoryId, const char *data_json) {
+ if (!ensureDelegateInitialized()) {
+ NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+
+ NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
+ NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
+
+ NSError *contentError = nil;
+ UNMutableNotificationContent *content = createNotificationContent(title, subtitle, body, data_json, &contentError);
+
+ if (contentError) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [contentError localizedDescription]];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ content.categoryIdentifier = nsCategoryId;
+
+ UNTimeIntervalNotificationTrigger *trigger = nil;
+
+ UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:nsIdentifier content:content trigger:trigger];
+
+ [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
+ if (error) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ } else {
+ captureResult(channelID, true, NULL);
+ }
+ }];
+}
+
+void registerNotificationCategory(int channelID, const char *categoryId, const char *actions_json, bool hasReplyField,
+ const char *replyPlaceholder, const char *replyButtonTitle) {
+ if (!ensureDelegateInitialized()) {
+ NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service.";
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
+ NSString *actionsJsonStr = actions_json ? [NSString stringWithUTF8String:actions_json] : @"[]";
+
+ NSData *jsonData = [actionsJsonStr dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error = nil;
+ NSArray *actionsArray = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
+
+ if (error) {
+ NSString *errorMsg = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ return;
+ }
+
+ NSMutableArray *actions = [NSMutableArray array];
+
+ for (NSDictionary *actionDict in actionsArray) {
+ NSString *actionId = actionDict[@"id"];
+ NSString *actionTitle = actionDict[@"title"];
+ BOOL destructive = [actionDict[@"destructive"] boolValue];
+
+ if (actionId && actionTitle) {
+ UNNotificationActionOptions options = UNNotificationActionOptionNone;
+ if (destructive) options |= UNNotificationActionOptionDestructive;
+
+ UNNotificationAction *action = [UNNotificationAction
+ actionWithIdentifier:actionId
+ title:actionTitle
+ options:options];
+ [actions addObject:action];
+ }
+ }
+
+ if (hasReplyField && replyPlaceholder && replyButtonTitle) {
+ NSString *placeholder = [NSString stringWithUTF8String:replyPlaceholder];
+ NSString *buttonTitle = [NSString stringWithUTF8String:replyButtonTitle];
+
+ UNTextInputNotificationAction *textAction =
+ [UNTextInputNotificationAction actionWithIdentifier:@"TEXT_REPLY"
+ title:buttonTitle
+ options:UNNotificationActionOptionNone
+ textInputButtonTitle:buttonTitle
+ textInputPlaceholder:placeholder];
+ [actions addObject:textAction];
+ }
+
+ UNNotificationCategory *newCategory = [UNNotificationCategory
+ categoryWithIdentifier:nsCategoryId
+ actions:actions
+ intentIdentifiers:@[]
+ options:UNNotificationCategoryOptionNone];
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) {
+ NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories];
+
+ // Remove existing category with same ID if it exists
+ UNNotificationCategory *existingCategory = nil;
+ for (UNNotificationCategory *category in updatedCategories) {
+ if ([category.identifier isEqualToString:nsCategoryId]) {
+ existingCategory = category;
+ break;
+ }
+ }
+ if (existingCategory) {
+ [updatedCategories removeObject:existingCategory];
+ }
+
+ // Add the new category
+ [updatedCategories addObject:newCategory];
+ [center setNotificationCategories:updatedCategories];
+
+ captureResult(channelID, true, NULL);
+ }];
+}
+
+void removeNotificationCategory(int channelID, const char *categoryId) {
+ NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId];
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+
+ [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) {
+ NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories];
+
+ // Find and remove the category with matching identifier
+ UNNotificationCategory *categoryToRemove = nil;
+ for (UNNotificationCategory *category in updatedCategories) {
+ if ([category.identifier isEqualToString:nsCategoryId]) {
+ categoryToRemove = category;
+ break;
+ }
+ }
+
+ if (categoryToRemove) {
+ [updatedCategories removeObject:categoryToRemove];
+ [center setNotificationCategories:updatedCategories];
+ captureResult(channelID, true, NULL);
+ } else {
+ NSString *errorMsg = [NSString stringWithFormat:@"Category '%@' not found", nsCategoryId];
+ captureResult(channelID, false, [errorMsg UTF8String]);
+ }
+ }];
+}
+
+void removeAllPendingNotifications(void) {
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center removeAllPendingNotificationRequests];
+}
+
+void removePendingNotification(const char *identifier) {
+ NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center removePendingNotificationRequestsWithIdentifiers:@[nsIdentifier]];
+}
+
+void removeAllDeliveredNotifications(void) {
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center removeAllDeliveredNotifications];
+}
+
+void removeDeliveredNotification(const char *identifier) {
+ NSString *nsIdentifier = [NSString stringWithUTF8String:identifier];
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center removeDeliveredNotificationsWithIdentifiers:@[nsIdentifier]];
+}
\ No newline at end of file
diff --git a/v3/pkg/services/notifications/notifications_linux.go b/v3/pkg/services/notifications/notifications_linux.go
new file mode 100644
index 000000000..bdc8312ff
--- /dev/null
+++ b/v3/pkg/services/notifications/notifications_linux.go
@@ -0,0 +1,565 @@
+//go:build linux
+
+package notifications
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/godbus/dbus/v5"
+ "github.com/wailsapp/wails/v3/pkg/application"
+)
+
+type linuxNotifier struct {
+ conn *dbus.Conn
+ categories map[string]NotificationCategory
+ categoriesLock sync.RWMutex
+ notifications map[uint32]*notificationData
+ notificationsLock sync.RWMutex
+ appName string
+ cancel context.CancelFunc
+}
+
+type notificationData struct {
+ ID string
+ Title string
+ Subtitle string
+ Body string
+ CategoryID string
+ Data map[string]interface{}
+ DBusID uint32
+ ActionMap map[string]string
+}
+
+const (
+ dbusNotificationInterface = "org.freedesktop.Notifications"
+ dbusNotificationPath = "/org/freedesktop/Notifications"
+)
+
+// Creates a new Notifications Service.
+func New() *Service {
+ notificationServiceOnce.Do(func() {
+ impl := &linuxNotifier{
+ categories: make(map[string]NotificationCategory),
+ notifications: make(map[uint32]*notificationData),
+ }
+
+ NotificationService = &Service{
+ impl: impl,
+ }
+ })
+
+ return NotificationService
+}
+
+// Startup is called when the service is loaded.
+func (ln *linuxNotifier) Startup(ctx context.Context, options application.ServiceOptions) error {
+ ln.appName = application.Get().Config().Name
+
+ conn, err := dbus.ConnectSessionBus()
+ if err != nil {
+ return fmt.Errorf("failed to connect to session bus: %w", err)
+ }
+ ln.conn = conn
+
+ if err := ln.loadCategories(); err != nil {
+ fmt.Printf("Failed to load notification categories: %v\n", err)
+ }
+
+ var signalCtx context.Context
+ signalCtx, ln.cancel = context.WithCancel(context.Background())
+
+ if err := ln.setupSignalHandling(signalCtx); err != nil {
+ return fmt.Errorf("failed to set up notification signal handling: %w", err)
+ }
+
+ return nil
+}
+
+// Shutdown will save categories and close the D-Bus connection when the service unloads.
+func (ln *linuxNotifier) Shutdown() error {
+ if ln.cancel != nil {
+ ln.cancel()
+ }
+
+ if err := ln.saveCategories(); err != nil {
+ fmt.Printf("Failed to save notification categories: %v\n", err)
+ }
+
+ if ln.conn != nil {
+ return ln.conn.Close()
+ }
+ return nil
+}
+
+// RequestNotificationAuthorization is a Linux stub that always returns true, nil.
+// (authorization is macOS-specific)
+func (ln *linuxNotifier) RequestNotificationAuthorization() (bool, error) {
+ return true, nil
+}
+
+// CheckNotificationAuthorization is a Linux stub that always returns true.
+// (authorization is macOS-specific)
+func (ln *linuxNotifier) CheckNotificationAuthorization() (bool, error) {
+ return true, nil
+}
+
+// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body.
+func (ln *linuxNotifier) SendNotification(options NotificationOptions) error {
+ hints := map[string]dbus.Variant{}
+
+ body := options.Body
+ if options.Subtitle != "" {
+ body = options.Subtitle + "\n" + body
+ }
+
+ defaultActionID := "default"
+ actions := []string{defaultActionID, "Default"}
+
+ actionMap := map[string]string{
+ defaultActionID: DefaultActionIdentifier,
+ }
+
+ hints["x-notification-id"] = dbus.MakeVariant(options.ID)
+
+ if options.Data != nil {
+ userData, err := json.Marshal(options.Data)
+ if err == nil {
+ hints["x-user-data"] = dbus.MakeVariant(string(userData))
+ }
+ }
+
+ // Call the Notify method on the D-Bus interface
+ obj := ln.conn.Object(dbusNotificationInterface, dbusNotificationPath)
+ call := obj.Call(
+ dbusNotificationInterface+".Notify",
+ 0,
+ ln.appName,
+ uint32(0),
+ "", // Icon
+ options.Title,
+ body,
+ actions,
+ hints,
+ int32(-1),
+ )
+
+ if call.Err != nil {
+ return fmt.Errorf("failed to send notification: %w", call.Err)
+ }
+
+ var dbusID uint32
+ if err := call.Store(&dbusID); err != nil {
+ return fmt.Errorf("failed to store notification ID: %w", err)
+ }
+
+ notification := ¬ificationData{
+ ID: options.ID,
+ Title: options.Title,
+ Subtitle: options.Subtitle,
+ Body: options.Body,
+ Data: options.Data,
+ DBusID: dbusID,
+ ActionMap: actionMap,
+ }
+
+ ln.notificationsLock.Lock()
+ ln.notifications[dbusID] = notification
+ ln.notificationsLock.Unlock()
+
+ return nil
+}
+
+// SendNotificationWithActions sends a notification with additional actions.
+func (ln *linuxNotifier) SendNotificationWithActions(options NotificationOptions) error {
+ ln.categoriesLock.RLock()
+ category, exists := ln.categories[options.CategoryID]
+ ln.categoriesLock.RUnlock()
+
+ if options.CategoryID == "" || !exists {
+ // Fall back to basic notification
+ return ln.SendNotification(options)
+ }
+
+ body := options.Body
+ if options.Subtitle != "" {
+ body = options.Subtitle + "\n" + body
+ }
+
+ var actions []string
+ actionMap := make(map[string]string)
+
+ defaultActionID := "default"
+ actions = append(actions, defaultActionID, "Default")
+ actionMap[defaultActionID] = DefaultActionIdentifier
+
+ for _, action := range category.Actions {
+ actions = append(actions, action.ID, action.Title)
+ actionMap[action.ID] = action.ID
+ }
+
+ hints := map[string]dbus.Variant{}
+
+ hints["x-notification-id"] = dbus.MakeVariant(options.ID)
+
+ hints["x-category-id"] = dbus.MakeVariant(options.CategoryID)
+
+ if options.Data != nil {
+ userData, err := json.Marshal(options.Data)
+ if err == nil {
+ hints["x-user-data"] = dbus.MakeVariant(string(userData))
+ }
+ }
+
+ obj := ln.conn.Object(dbusNotificationInterface, dbusNotificationPath)
+ call := obj.Call(
+ dbusNotificationInterface+".Notify",
+ 0,
+ ln.appName,
+ uint32(0),
+ "", // Icon
+ options.Title,
+ body,
+ actions,
+ hints,
+ int32(-1),
+ )
+
+ if call.Err != nil {
+ return fmt.Errorf("failed to send notification: %w", call.Err)
+ }
+
+ var dbusID uint32
+ if err := call.Store(&dbusID); err != nil {
+ return fmt.Errorf("failed to store notification ID: %w", err)
+ }
+
+ notification := ¬ificationData{
+ ID: options.ID,
+ Title: options.Title,
+ Subtitle: options.Subtitle,
+ Body: options.Body,
+ CategoryID: options.CategoryID,
+ Data: options.Data,
+ DBusID: dbusID,
+ ActionMap: actionMap,
+ }
+
+ ln.notificationsLock.Lock()
+ ln.notifications[dbusID] = notification
+ ln.notificationsLock.Unlock()
+
+ return nil
+}
+
+// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
+func (ln *linuxNotifier) RegisterNotificationCategory(category NotificationCategory) error {
+ ln.categoriesLock.Lock()
+ ln.categories[category.ID] = category
+ ln.categoriesLock.Unlock()
+
+ if err := ln.saveCategories(); err != nil {
+ fmt.Printf("Failed to save notification categories: %v\n", err)
+ }
+
+ return nil
+}
+
+// RemoveNotificationCategory removes a previously registered NotificationCategory.
+func (ln *linuxNotifier) RemoveNotificationCategory(categoryId string) error {
+ ln.categoriesLock.Lock()
+ delete(ln.categories, categoryId)
+ ln.categoriesLock.Unlock()
+
+ if err := ln.saveCategories(); err != nil {
+ fmt.Printf("Failed to save notification categories: %v\n", err)
+ }
+
+ return nil
+}
+
+// RemoveAllPendingNotifications attempts to remove all active notifications.
+func (ln *linuxNotifier) RemoveAllPendingNotifications() error {
+ ln.notificationsLock.Lock()
+ dbusIDs := make([]uint32, 0, len(ln.notifications))
+ for id := range ln.notifications {
+ dbusIDs = append(dbusIDs, id)
+ }
+ ln.notificationsLock.Unlock()
+
+ for _, id := range dbusIDs {
+ ln.closeNotification(id)
+ }
+
+ return nil
+}
+
+// RemovePendingNotification removes a pending notification.
+func (ln *linuxNotifier) RemovePendingNotification(identifier string) error {
+ var dbusID uint32
+ found := false
+
+ ln.notificationsLock.Lock()
+ for id, notif := range ln.notifications {
+ if notif.ID == identifier {
+ dbusID = id
+ found = true
+ break
+ }
+ }
+ ln.notificationsLock.Unlock()
+
+ if !found {
+ return nil
+ }
+
+ return ln.closeNotification(dbusID)
+}
+
+// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux.
+func (ln *linuxNotifier) RemoveAllDeliveredNotifications() error {
+ return ln.RemoveAllPendingNotifications()
+}
+
+// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux.
+func (ln *linuxNotifier) RemoveDeliveredNotification(identifier string) error {
+ return ln.RemovePendingNotification(identifier)
+}
+
+// RemoveNotification removes a notification by identifier.
+func (ln *linuxNotifier) RemoveNotification(identifier string) error {
+ return ln.RemovePendingNotification(identifier)
+}
+
+// Helper method to close a notification.
+func (ln *linuxNotifier) closeNotification(id uint32) error {
+ obj := ln.conn.Object(dbusNotificationInterface, dbusNotificationPath)
+ call := obj.Call(dbusNotificationInterface+".CloseNotification", 0, id)
+
+ if call.Err != nil {
+ return fmt.Errorf("failed to close notification: %w", call.Err)
+ }
+
+ return nil
+}
+
+func (ln *linuxNotifier) getConfigDir() (string, error) {
+ configDir, err := os.UserConfigDir()
+ if err != nil {
+ return "", fmt.Errorf("failed to get user config directory: %w", err)
+ }
+
+ appConfigDir := filepath.Join(configDir, ln.appName)
+ if err := os.MkdirAll(appConfigDir, 0755); err != nil {
+ return "", fmt.Errorf("failed to create app config directory: %w", err)
+ }
+
+ return appConfigDir, nil
+}
+
+// Save notification categories.
+func (ln *linuxNotifier) saveCategories() error {
+ configDir, err := ln.getConfigDir()
+ if err != nil {
+ return err
+ }
+
+ categoriesFile := filepath.Join(configDir, "notification-categories.json")
+
+ ln.categoriesLock.RLock()
+ categoriesData, err := json.MarshalIndent(ln.categories, "", " ")
+ ln.categoriesLock.RUnlock()
+
+ if err != nil {
+ return fmt.Errorf("failed to marshal notification categories: %w", err)
+ }
+
+ if err := os.WriteFile(categoriesFile, categoriesData, 0644); err != nil {
+ return fmt.Errorf("failed to write notification categories to disk: %w", err)
+ }
+
+ return nil
+}
+
+// Load notification categories.
+func (ln *linuxNotifier) loadCategories() error {
+ configDir, err := ln.getConfigDir()
+ if err != nil {
+ return err
+ }
+
+ categoriesFile := filepath.Join(configDir, "notification-categories.json")
+
+ if _, err := os.Stat(categoriesFile); os.IsNotExist(err) {
+ return nil
+ }
+
+ categoriesData, err := os.ReadFile(categoriesFile)
+ if err != nil {
+ return fmt.Errorf("failed to read notification categories from disk: %w", err)
+ }
+
+ categories := make(map[string]NotificationCategory)
+ if err := json.Unmarshal(categoriesData, &categories); err != nil {
+ return fmt.Errorf("failed to unmarshal notification categories: %w", err)
+ }
+
+ ln.categoriesLock.Lock()
+ ln.categories = categories
+ ln.categoriesLock.Unlock()
+
+ return nil
+}
+
+// Setup signal handling for notification actions.
+func (ln *linuxNotifier) setupSignalHandling(ctx context.Context) error {
+ if err := ln.conn.AddMatchSignal(
+ dbus.WithMatchInterface(dbusNotificationInterface),
+ dbus.WithMatchMember("ActionInvoked"),
+ ); err != nil {
+ return err
+ }
+
+ if err := ln.conn.AddMatchSignal(
+ dbus.WithMatchInterface(dbusNotificationInterface),
+ dbus.WithMatchMember("NotificationClosed"),
+ ); err != nil {
+ return err
+ }
+
+ c := make(chan *dbus.Signal, 10)
+ ln.conn.Signal(c)
+
+ go ln.handleSignals(ctx, c)
+
+ return nil
+}
+
+// Handle incoming D-Bus signals.
+func (ln *linuxNotifier) handleSignals(ctx context.Context, c chan *dbus.Signal) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case signal, ok := <-c:
+ if !ok {
+ return
+ }
+
+ switch signal.Name {
+ case dbusNotificationInterface + ".ActionInvoked":
+ ln.handleActionInvoked(signal)
+ case dbusNotificationInterface + ".NotificationClosed":
+ ln.handleNotificationClosed(signal)
+ }
+ }
+ }
+}
+
+// Handle ActionInvoked signal.
+func (ln *linuxNotifier) handleActionInvoked(signal *dbus.Signal) {
+ if len(signal.Body) < 2 {
+ return
+ }
+
+ dbusID, ok := signal.Body[0].(uint32)
+ if !ok {
+ return
+ }
+
+ actionID, ok := signal.Body[1].(string)
+ if !ok {
+ return
+ }
+
+ ln.notificationsLock.Lock()
+ notification, exists := ln.notifications[dbusID]
+ if exists {
+ delete(ln.notifications, dbusID)
+ }
+ ln.notificationsLock.Unlock()
+
+ if !exists {
+ return
+ }
+
+ appActionID, ok := notification.ActionMap[actionID]
+ if !ok {
+ appActionID = actionID
+ }
+
+ response := NotificationResponse{
+ ID: notification.ID,
+ ActionIdentifier: appActionID,
+ Title: notification.Title,
+ Subtitle: notification.Subtitle,
+ Body: notification.Body,
+ CategoryID: notification.CategoryID,
+ UserInfo: notification.Data,
+ }
+
+ result := NotificationResult{
+ Response: response,
+ }
+
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+}
+
+// Handle NotificationClosed signal.
+// Reason codes:
+// 1 - expired timeout
+// 2 - dismissed by user (click on X)
+// 3 - closed by CloseNotification call
+// 4 - undefined/reserved
+func (ln *linuxNotifier) handleNotificationClosed(signal *dbus.Signal) {
+ if len(signal.Body) < 2 {
+ return
+ }
+
+ dbusID, ok := signal.Body[0].(uint32)
+ if !ok {
+ return
+ }
+
+ reason, ok := signal.Body[1].(uint32)
+ if !ok {
+ reason = 0 // Unknown reason
+ }
+
+ ln.notificationsLock.Lock()
+ notification, exists := ln.notifications[dbusID]
+ if exists {
+ delete(ln.notifications, dbusID)
+ }
+ ln.notificationsLock.Unlock()
+
+ if !exists {
+ return
+ }
+
+ if reason == 2 {
+ response := NotificationResponse{
+ ID: notification.ID,
+ ActionIdentifier: DefaultActionIdentifier,
+ Title: notification.Title,
+ Subtitle: notification.Subtitle,
+ Body: notification.Body,
+ CategoryID: notification.CategoryID,
+ UserInfo: notification.Data,
+ }
+
+ result := NotificationResult{
+ Response: response,
+ }
+
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+ }
+}
diff --git a/v3/pkg/services/notifications/notifications_windows.go b/v3/pkg/services/notifications/notifications_windows.go
new file mode 100644
index 000000000..b7a16decc
--- /dev/null
+++ b/v3/pkg/services/notifications/notifications_windows.go
@@ -0,0 +1,433 @@
+//go:build windows
+
+package notifications
+
+import (
+ "context"
+ _ "embed"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "git.sr.ht/~jackmordaunt/go-toast/v2"
+ "github.com/google/uuid"
+ "github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/w32"
+ "golang.org/x/sys/windows/registry"
+)
+
+type windowsNotifier struct {
+ categories map[string]NotificationCategory
+ categoriesLock sync.RWMutex
+ appName string
+ appGUID string
+ iconPath string
+}
+
+const (
+ ToastRegistryPath = `Software\Classes\AppUserModelId\`
+ ToastRegistryGuidKey = "CustomActivator"
+ NotificationCategoriesRegistryPath = `SOFTWARE\%s\NotificationCategories`
+ NotificationCategoriesRegistryKey = "Categories"
+)
+
+// NotificationPayload combines the action ID and user data into a single structure
+type NotificationPayload struct {
+ Action string `json:"action"`
+ Options NotificationOptions `json:"payload,omitempty"`
+}
+
+// Creates a new Notifications Service.
+func New() *Service {
+ notificationServiceOnce.Do(func() {
+ impl := &windowsNotifier{
+ categories: make(map[string]NotificationCategory),
+ }
+
+ NotificationService = &Service{
+ impl: impl,
+ }
+ })
+
+ return NotificationService
+}
+
+// Startup is called when the service is loaded
+// Sets an activation callback to emit an event when notifications are interacted with.
+func (wn *windowsNotifier) Startup(ctx context.Context, options application.ServiceOptions) error {
+ wn.categoriesLock.Lock()
+ defer wn.categoriesLock.Unlock()
+
+ wn.appName = application.Get().Config().Name
+
+ guid, err := wn.getGUID()
+ if err != nil {
+ return err
+ }
+ wn.appGUID = guid
+
+ wn.iconPath = filepath.Join(os.TempDir(), wn.appName+wn.appGUID+".png")
+
+ toast.SetAppData(toast.AppData{
+ AppID: wn.appName,
+ GUID: guid,
+ IconPath: wn.iconPath,
+ })
+
+ toast.SetActivationCallback(func(args string, data []toast.UserData) {
+ result := NotificationResult{}
+
+ actionIdentifier, options, err := parseNotificationResponse(args)
+
+ if err != nil {
+ result.Error = err
+
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+ return
+ }
+
+ // Subtitle is retained but was not shown with the notification
+ response := NotificationResponse{
+ ID: options.ID,
+ ActionIdentifier: actionIdentifier,
+ Title: options.Title,
+ Subtitle: options.Subtitle,
+ Body: options.Body,
+ CategoryID: options.CategoryID,
+ UserInfo: options.Data,
+ }
+
+ if userText, found := wn.getUserText(data); found {
+ response.UserText = userText
+ }
+
+ result.Response = response
+ if ns := getNotificationService(); ns != nil {
+ ns.handleNotificationResult(result)
+ }
+ })
+
+ return wn.loadCategoriesFromRegistry()
+}
+
+// Shutdown will attempt to save the categories to the registry when the service unloads
+func (wn *windowsNotifier) Shutdown() error {
+ wn.categoriesLock.Lock()
+ defer wn.categoriesLock.Unlock()
+
+ return wn.saveCategoriesToRegistry()
+}
+
+// RequestNotificationAuthorization is a Windows stub that always returns true, nil.
+// (user authorization is macOS-specific)
+func (wn *windowsNotifier) RequestNotificationAuthorization() (bool, error) {
+ return true, nil
+}
+
+// CheckNotificationAuthorization is a Windows stub that always returns true.
+// (user authorization is macOS-specific)
+func (wn *windowsNotifier) CheckNotificationAuthorization() (bool, error) {
+ return true, nil
+}
+
+// SendNotification sends a basic notification with a name, title, and body. All other options are ignored on Windows.
+// (subtitle is only available on macOS and Linux)
+func (wn *windowsNotifier) SendNotification(options NotificationOptions) error {
+ if err := wn.saveIconToDir(); err != nil {
+ fmt.Printf("Error saving icon: %v\n", err)
+ }
+
+ n := toast.Notification{
+ Title: options.Title,
+ Body: options.Body,
+ ActivationArguments: DefaultActionIdentifier,
+ }
+
+ if options.Data != nil {
+ encodedPayload, err := wn.encodePayload(DefaultActionIdentifier, options)
+ if err != nil {
+ return fmt.Errorf("failed to encode notification payload: %w", err)
+ }
+ n.ActivationArguments = encodedPayload
+ }
+
+ return n.Push()
+}
+
+// SendNotificationWithActions sends a notification with additional actions and inputs.
+// A NotificationCategory must be registered with RegisterNotificationCategory first. The `CategoryID` must match the registered category.
+// If a NotificationCategory is not registered a basic notification will be sent.
+// (subtitle is only available on macOS and Linux)
+func (wn *windowsNotifier) SendNotificationWithActions(options NotificationOptions) error {
+ if err := wn.saveIconToDir(); err != nil {
+ fmt.Printf("Error saving icon: %v\n", err)
+ }
+
+ wn.categoriesLock.RLock()
+ nCategory, categoryExists := wn.categories[options.CategoryID]
+ wn.categoriesLock.RUnlock()
+
+ if options.CategoryID == "" || !categoryExists {
+ fmt.Printf("Category '%s' not found, sending basic notification without actions\n", options.CategoryID)
+ }
+
+ n := toast.Notification{
+ Title: options.Title,
+ Body: options.Body,
+ ActivationArguments: DefaultActionIdentifier,
+ }
+
+ for _, action := range nCategory.Actions {
+ n.Actions = append(n.Actions, toast.Action{
+ Content: action.Title,
+ Arguments: action.ID,
+ })
+ }
+
+ if nCategory.HasReplyField {
+ n.Inputs = append(n.Inputs, toast.Input{
+ ID: "userText",
+ Placeholder: nCategory.ReplyPlaceholder,
+ })
+
+ n.Actions = append(n.Actions, toast.Action{
+ Content: nCategory.ReplyButtonTitle,
+ Arguments: "TEXT_REPLY",
+ InputID: "userText",
+ })
+ }
+
+ if options.Data != nil {
+ encodedPayload, err := wn.encodePayload(n.ActivationArguments, options)
+ if err != nil {
+ return fmt.Errorf("failed to encode notification payload: %w", err)
+ }
+ n.ActivationArguments = encodedPayload
+
+ for index := range n.Actions {
+ encodedPayload, err := wn.encodePayload(n.Actions[index].Arguments, options)
+ if err != nil {
+ return fmt.Errorf("failed to encode notification payload: %w", err)
+ }
+ n.Actions[index].Arguments = encodedPayload
+ }
+ }
+
+ return n.Push()
+}
+
+// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions.
+// Registering a category with the same name as a previously registered NotificationCategory will override it.
+func (wn *windowsNotifier) RegisterNotificationCategory(category NotificationCategory) error {
+ wn.categoriesLock.Lock()
+ defer wn.categoriesLock.Unlock()
+
+ wn.categories[category.ID] = NotificationCategory{
+ ID: category.ID,
+ Actions: category.Actions,
+ HasReplyField: bool(category.HasReplyField),
+ ReplyPlaceholder: category.ReplyPlaceholder,
+ ReplyButtonTitle: category.ReplyButtonTitle,
+ }
+
+ return wn.saveCategoriesToRegistry()
+}
+
+// RemoveNotificationCategory removes a previously registered NotificationCategory.
+func (wn *windowsNotifier) RemoveNotificationCategory(categoryId string) error {
+ wn.categoriesLock.Lock()
+ defer wn.categoriesLock.Unlock()
+
+ delete(wn.categories, categoryId)
+
+ return wn.saveCategoriesToRegistry()
+}
+
+// RemoveAllPendingNotifications is a Windows stub that always returns nil.
+// (macOS and Linux only)
+func (wn *windowsNotifier) RemoveAllPendingNotifications() error {
+ return nil
+}
+
+// RemovePendingNotification is a Windows stub that always returns nil.
+// (macOS and Linux only)
+func (wn *windowsNotifier) RemovePendingNotification(_ string) error {
+ return nil
+}
+
+// RemoveAllDeliveredNotifications is a Windows stub that always returns nil.
+// (macOS and Linux only)
+func (wn *windowsNotifier) RemoveAllDeliveredNotifications() error {
+ return nil
+}
+
+// RemoveDeliveredNotification is a Windows stub that always returns nil.
+// (macOS and Linux only)
+func (wn *windowsNotifier) RemoveDeliveredNotification(_ string) error {
+ return nil
+}
+
+// RemoveNotification is a Windows stub that always returns nil.
+// (Linux-specific)
+func (wn *windowsNotifier) RemoveNotification(identifier string) error {
+ return nil
+}
+
+// encodePayload combines an action ID and user data into a single encoded string
+func (wn *windowsNotifier) encodePayload(actionID string, options NotificationOptions) (string, error) {
+ payload := NotificationPayload{
+ Action: actionID,
+ Options: options,
+ }
+
+ jsonData, err := json.Marshal(payload)
+ if err != nil {
+ return actionID, err
+ }
+
+ encodedPayload := base64.StdEncoding.EncodeToString(jsonData)
+ return encodedPayload, nil
+}
+
+// decodePayload extracts the action ID and user data from an encoded payload
+func decodePayload(encodedString string) (string, NotificationOptions, error) {
+ jsonData, err := base64.StdEncoding.DecodeString(encodedString)
+ if err != nil {
+ return encodedString, NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err)
+ }
+
+ var payload NotificationPayload
+ if err := json.Unmarshal(jsonData, &payload); err != nil {
+ return encodedString, NotificationOptions{}, fmt.Errorf("failed to unmarshal notification payload: %w", err)
+ }
+
+ return payload.Action, payload.Options, nil
+}
+
+// parseNotificationResponse updated to use structured payload decoding
+func parseNotificationResponse(response string) (action string, options NotificationOptions, err error) {
+ actionID, options, err := decodePayload(response)
+
+ if err != nil {
+ fmt.Printf("Warning: Failed to decode notification response: %v\n", err)
+ return response, NotificationOptions{}, err
+ }
+
+ return actionID, options, nil
+}
+
+func (wn *windowsNotifier) saveIconToDir() error {
+ icon, err := application.NewIconFromResource(w32.GetModuleHandle(""), uint16(3))
+ if err != nil {
+ return fmt.Errorf("failed to retrieve application icon: %w", err)
+ }
+
+ return w32.SaveHIconAsPNG(icon, wn.iconPath)
+}
+
+func (wn *windowsNotifier) saveCategoriesToRegistry() error {
+ // We assume lock is held by caller
+
+ registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, wn.appName)
+
+ key, _, err := registry.CreateKey(
+ registry.CURRENT_USER,
+ registryPath,
+ registry.ALL_ACCESS,
+ )
+ if err != nil {
+ return err
+ }
+ defer key.Close()
+
+ data, err := json.Marshal(wn.categories)
+ if err != nil {
+ return err
+ }
+
+ return key.SetStringValue(NotificationCategoriesRegistryKey, string(data))
+}
+
+func (wn *windowsNotifier) loadCategoriesFromRegistry() error {
+ // We assume lock is held by caller
+
+ registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, wn.appName)
+
+ key, err := registry.OpenKey(
+ registry.CURRENT_USER,
+ registryPath,
+ registry.QUERY_VALUE,
+ )
+ if err != nil {
+ if err == registry.ErrNotExist {
+ // Not an error, no saved categories
+ return nil
+ }
+ return fmt.Errorf("failed to open registry key: %w", err)
+ }
+ defer key.Close()
+
+ data, _, err := key.GetStringValue(NotificationCategoriesRegistryKey)
+ if err != nil {
+ if err == registry.ErrNotExist {
+ // No value yet, but key exists
+ return nil
+ }
+ return fmt.Errorf("failed to read categories from registry: %w", err)
+ }
+
+ categories := make(map[string]NotificationCategory)
+ if err := json.Unmarshal([]byte(data), &categories); err != nil {
+ return fmt.Errorf("failed to parse notification categories from registry: %w", err)
+ }
+
+ wn.categories = categories
+
+ return nil
+}
+
+func (wn *windowsNotifier) getUserText(data []toast.UserData) (string, bool) {
+ for _, d := range data {
+ if d.Key == "userText" {
+ return d.Value, true
+ }
+ }
+ return "", false
+}
+
+func (wn *windowsNotifier) getGUID() (string, error) {
+ keyPath := ToastRegistryPath + wn.appName
+
+ k, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.QUERY_VALUE)
+ if err == nil {
+ guid, _, err := k.GetStringValue(ToastRegistryGuidKey)
+ k.Close()
+ if err == nil && guid != "" {
+ return guid, nil
+ }
+ }
+
+ guid := wn.generateGUID()
+
+ k, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.WRITE)
+ if err != nil {
+ return "", fmt.Errorf("failed to create registry key: %w", err)
+ }
+ defer k.Close()
+
+ if err := k.SetStringValue(ToastRegistryGuidKey, guid); err != nil {
+ return "", fmt.Errorf("failed to write GUID to registry: %w", err)
+ }
+
+ return guid, nil
+}
+
+func (wn *windowsNotifier) generateGUID() string {
+ guid := uuid.New()
+ return fmt.Sprintf("{%s}", guid.String())
+}
diff --git a/v3/pkg/w32/icon.go b/v3/pkg/w32/icon.go
index 009479323..97d4ad854 100644
--- a/v3/pkg/w32/icon.go
+++ b/v3/pkg/w32/icon.go
@@ -6,8 +6,11 @@ import (
"bytes"
"fmt"
"image"
+ "image/color"
"image/draw"
"image/png"
+ "os"
+ "syscall"
"unsafe"
)
@@ -90,6 +93,121 @@ func CreateLargeHIconFromImage(fileData []byte) (HICON, error) {
return HICON(icon), err
}
+type ICONINFO struct {
+ FIcon int32
+ XHotspot int32
+ YHotspot int32
+ HbmMask syscall.Handle
+ HbmColor syscall.Handle
+}
+
+func SaveHIconAsPNG(hIcon HICON, filePath string) error {
+ // Load necessary DLLs
+ user32 := syscall.NewLazyDLL("user32.dll")
+ gdi32 := syscall.NewLazyDLL("gdi32.dll")
+
+ // Get procedures
+ getIconInfo := user32.NewProc("GetIconInfo")
+ getObject := gdi32.NewProc("GetObjectW")
+ createCompatibleDC := gdi32.NewProc("CreateCompatibleDC")
+ selectObject := gdi32.NewProc("SelectObject")
+ getDIBits := gdi32.NewProc("GetDIBits")
+ deleteObject := gdi32.NewProc("DeleteObject")
+ deleteDC := gdi32.NewProc("DeleteDC")
+
+ // Get icon info
+ var iconInfo ICONINFO
+ ret, _, err := getIconInfo.Call(
+ uintptr(hIcon),
+ uintptr(unsafe.Pointer(&iconInfo)),
+ )
+ if ret == 0 {
+ return err
+ }
+ defer deleteObject.Call(uintptr(iconInfo.HbmMask))
+ defer deleteObject.Call(uintptr(iconInfo.HbmColor))
+
+ // Get bitmap info
+ var bmp BITMAP
+ ret, _, err = getObject.Call(
+ uintptr(iconInfo.HbmColor),
+ unsafe.Sizeof(bmp),
+ uintptr(unsafe.Pointer(&bmp)),
+ )
+ if ret == 0 {
+ return err
+ }
+
+ // Create DC
+ hdc, _, _ := createCompatibleDC.Call(0)
+ if hdc == 0 {
+ return syscall.EINVAL
+ }
+ defer deleteDC.Call(hdc)
+
+ // Select bitmap into DC
+ oldBitmap, _, _ := selectObject.Call(hdc, uintptr(iconInfo.HbmColor))
+ defer selectObject.Call(hdc, oldBitmap)
+
+ // Prepare bitmap info header
+ var bi BITMAPINFO
+ bi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bi.BmiHeader))
+ bi.BmiHeader.BiWidth = bmp.BmWidth
+ bi.BmiHeader.BiHeight = bmp.BmHeight
+ bi.BmiHeader.BiPlanes = 1
+ bi.BmiHeader.BiBitCount = 32
+ bi.BmiHeader.BiCompression = BI_RGB
+
+ // Allocate memory for bitmap bits
+ width, height := int(bmp.BmWidth), int(bmp.BmHeight)
+ bufferSize := width * height * 4
+ bits := make([]byte, bufferSize)
+
+ // Get bitmap bits
+ ret, _, err = getDIBits.Call(
+ hdc,
+ uintptr(iconInfo.HbmColor),
+ 0,
+ uintptr(bmp.BmHeight),
+ uintptr(unsafe.Pointer(&bits[0])),
+ uintptr(unsafe.Pointer(&bi)),
+ DIB_RGB_COLORS,
+ )
+ if ret == 0 {
+ return err
+ }
+
+ // Create Go image
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
+
+ // Convert DIB to RGBA
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ // DIB is bottom-up, so we need to invert Y
+ dibIndex := ((height-1-y)*width + x) * 4
+
+ // BGRA to RGBA
+ b := bits[dibIndex]
+ g := bits[dibIndex+1]
+ r := bits[dibIndex+2]
+ a := bits[dibIndex+3]
+
+ // Set pixel in the image
+ img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a})
+ }
+ }
+
+ // Create output file
+ outFile, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ defer outFile.Close()
+
+ // Encode and save the image
+ return png.Encode(outFile, img)
+}
+
func SetWindowIcon(hwnd HWND, icon HICON) {
SendMessage(hwnd, WM_SETICON, ICON_SMALL, uintptr(icon))
}
diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx
index d1b405813..c5f51220e 100644
--- a/website/src/pages/changelog.mdx
+++ b/website/src/pages/changelog.mdx
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Fixed
+- Fixed locking issue on Windows when multiselect dialog returns an error. Fixed in [PR](https://github.com/wailsapp/wails/pull/4156) by @johannes-luebke
+
## v2.9.1 - 2024-06-18
### Fixed