Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4010a5a24 | ||
|
|
4a8f4e3319 | ||
|
|
ee503c338f | ||
|
|
3e07e611dd | ||
|
|
77a7918569 | ||
|
|
c65187f656 | ||
|
|
2e50e95b68 | ||
|
|
ae970e5bca | ||
|
|
600b67bc1e | ||
|
|
537e8a126e |
@@ -21,6 +21,9 @@ go.work
|
|||||||
# Wails
|
# Wails
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
frontend/node_modules/
|
frontend/node_modules/
|
||||||
|
frontend/bindings/
|
||||||
|
verstak-gui
|
||||||
|
verstak-cli
|
||||||
|
|
||||||
# VS Code
|
# VS Code
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,355 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
go:mod:tidy:
|
||||||
|
summary: Runs `go mod tidy`
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- go mod tidy
|
||||||
|
|
||||||
|
install:frontend:deps:
|
||||||
|
summary: Install frontend dependencies
|
||||||
|
cmds:
|
||||||
|
- task: install:frontend:deps:{{.PACKAGE_MANAGER}}
|
||||||
|
|
||||||
|
install:frontend:deps:npm:
|
||||||
|
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
|
||||||
|
|
||||||
|
install:frontend:deps:bun:
|
||||||
|
dir: frontend
|
||||||
|
sources:
|
||||||
|
- package.json
|
||||||
|
- bun.lock
|
||||||
|
- bun.lockb
|
||||||
|
generates:
|
||||||
|
- node_modules
|
||||||
|
preconditions:
|
||||||
|
- sh: bun --version
|
||||||
|
msg: "bun not found"
|
||||||
|
cmds:
|
||||||
|
- bun install
|
||||||
|
|
||||||
|
install:frontend:deps:pnpm:
|
||||||
|
dir: frontend
|
||||||
|
sources:
|
||||||
|
- package.json
|
||||||
|
- pnpm-lock.yaml
|
||||||
|
generates:
|
||||||
|
- node_modules
|
||||||
|
preconditions:
|
||||||
|
- sh: pnpm --version
|
||||||
|
msg: "pnpm not found"
|
||||||
|
cmds:
|
||||||
|
- pnpm install
|
||||||
|
|
||||||
|
install:frontend:deps:yarn:
|
||||||
|
dir: frontend
|
||||||
|
sources:
|
||||||
|
- package.json
|
||||||
|
- yarn.lock
|
||||||
|
status:
|
||||||
|
- test -d node_modules || test -f .pnp.cjs
|
||||||
|
preconditions:
|
||||||
|
- sh: yarn --version
|
||||||
|
msg: "yarn not found"
|
||||||
|
cmds:
|
||||||
|
- yarn install
|
||||||
|
|
||||||
|
build:frontend:
|
||||||
|
label: build:frontend (DEV={{.DEV}} RUNNER={{.PACKAGE_MANAGER}})
|
||||||
|
summary: Build the frontend project
|
||||||
|
dir: frontend
|
||||||
|
sources:
|
||||||
|
- "**/*"
|
||||||
|
- exclude: node_modules/**/*
|
||||||
|
generates:
|
||||||
|
- dist/**/*
|
||||||
|
deps:
|
||||||
|
- task: install:frontend:deps
|
||||||
|
- task: generate:bindings
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
OBFUSCATED:
|
||||||
|
ref: .OBFUSCATED
|
||||||
|
cmds:
|
||||||
|
- task: frontend:run
|
||||||
|
vars:
|
||||||
|
SCRIPT: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}'
|
||||||
|
env:
|
||||||
|
PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}'
|
||||||
|
|
||||||
|
frontend:run:
|
||||||
|
summary: Run a frontend script with selected runner
|
||||||
|
cmds:
|
||||||
|
- task: frontend:run:{{.PACKAGE_MANAGER}}
|
||||||
|
vars:
|
||||||
|
SCRIPT: "{{.SCRIPT}}"
|
||||||
|
vars:
|
||||||
|
SCRIPT: "{{.SCRIPT}}"
|
||||||
|
|
||||||
|
frontend:run:npm:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- npm run {{.SCRIPT}} -q
|
||||||
|
vars:
|
||||||
|
SCRIPT: "{{.SCRIPT}}"
|
||||||
|
|
||||||
|
frontend:run:yarn:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn {{.SCRIPT}}
|
||||||
|
vars:
|
||||||
|
SCRIPT: "{{.SCRIPT}}"
|
||||||
|
|
||||||
|
frontend:run:pnpm:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- pnpm run {{.SCRIPT}}
|
||||||
|
vars:
|
||||||
|
SCRIPT: "{{.SCRIPT}}"
|
||||||
|
|
||||||
|
frontend:run:bun:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- bun run {{.SCRIPT}}
|
||||||
|
vars:
|
||||||
|
SCRIPT: "{{.SCRIPT}}"
|
||||||
|
|
||||||
|
frontend:vendor:puppertino:
|
||||||
|
summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling
|
||||||
|
sources:
|
||||||
|
- frontend/public/puppertino/puppertino.css
|
||||||
|
generates:
|
||||||
|
- frontend/public/puppertino/puppertino.css
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p frontend/public/puppertino
|
||||||
|
# If bundled Puppertino exists, prefer it. Otherwise, try to fetch, but don't fail build on error.
|
||||||
|
if [ ! -f frontend/public/puppertino/puppertino.css ]; then
|
||||||
|
echo "No bundled Puppertino found. Attempting to fetch from GitHub..."
|
||||||
|
if curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css; then
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE || true
|
||||||
|
echo "Puppertino CSS downloaded to frontend/public/puppertino/puppertino.css"
|
||||||
|
else
|
||||||
|
echo "Warning: Could not fetch Puppertino CSS. Proceeding without download since template may bundle it."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Using bundled Puppertino at frontend/public/puppertino/puppertino.css"
|
||||||
|
fi
|
||||||
|
# Ensure index.html includes Puppertino CSS and button classes
|
||||||
|
INDEX_HTML=frontend/index.html
|
||||||
|
if [ -f "$INDEX_HTML" ]; then
|
||||||
|
if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then
|
||||||
|
# Insert Puppertino link tag after style.css link
|
||||||
|
awk '
|
||||||
|
/href="\/style.css"\/?/ && !x { print; print " <link rel=\"stylesheet\" href=\"/puppertino/puppertino.css\"/>"; x=1; next }1
|
||||||
|
' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML"
|
||||||
|
fi
|
||||||
|
# Replace default .btn with Puppertino primary button classes if present
|
||||||
|
sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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{{if eq .OBFUSCATED "true"}} -obfuscated{{end}}
|
||||||
|
|
||||||
|
generate:icons:
|
||||||
|
summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms).
|
||||||
|
dir: build
|
||||||
|
sources:
|
||||||
|
- "appicon.png"
|
||||||
|
- "appicon.icon"
|
||||||
|
generates:
|
||||||
|
- "darwin/icons.icns"
|
||||||
|
- "windows/icon.ico"
|
||||||
|
cmds:
|
||||||
|
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin
|
||||||
|
|
||||||
|
dev:frontend:
|
||||||
|
summary: Runs the frontend in development mode
|
||||||
|
deps:
|
||||||
|
- task: install:frontend:deps
|
||||||
|
cmds:
|
||||||
|
- task: frontend:dev:{{.PACKAGE_MANAGER}}
|
||||||
|
|
||||||
|
frontend:dev:npm:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- npm run dev -- --port {{.VITE_PORT}} --strictPort
|
||||||
|
|
||||||
|
frontend:dev:yarn:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- yarn dev --port {{.VITE_PORT}} --strictPort
|
||||||
|
|
||||||
|
frontend:dev:pnpm:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- pnpm dev --port {{.VITE_PORT}} --strictPort
|
||||||
|
|
||||||
|
frontend:dev:bun:
|
||||||
|
dir: frontend
|
||||||
|
cmds:
|
||||||
|
- bun 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 .
|
||||||
|
|
||||||
|
build:server:
|
||||||
|
summary: Builds the application in server mode (no GUI, HTTP server only)
|
||||||
|
desc: |
|
||||||
|
Builds the application with the server build tag enabled.
|
||||||
|
Server mode runs as a pure HTTP server without native GUI dependencies.
|
||||||
|
Usage: task build:server
|
||||||
|
deps:
|
||||||
|
- task: build:frontend
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
cmds:
|
||||||
|
- go build -tags server {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS: "{{.BUILD_FLAGS}}"
|
||||||
|
|
||||||
|
run:server:
|
||||||
|
summary: Builds and runs the application in server mode
|
||||||
|
deps:
|
||||||
|
- task: build:server
|
||||||
|
cmds:
|
||||||
|
- ./{{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
|
||||||
|
|
||||||
|
build:docker:
|
||||||
|
summary: Builds a Docker image for server mode deployment
|
||||||
|
desc: |
|
||||||
|
Creates a minimal Docker image containing the server mode binary.
|
||||||
|
The image is based on distroless for security and small size.
|
||||||
|
Usage: task build:docker [TAG=myapp:latest]
|
||||||
|
cmds:
|
||||||
|
- docker build -t {{.TAG | default (printf "%s:latest" .APP_NAME)}} -f build/docker/Dockerfile.server .
|
||||||
|
vars:
|
||||||
|
TAG: "{{.TAG}}"
|
||||||
|
preconditions:
|
||||||
|
- sh: docker info > /dev/null 2>&1
|
||||||
|
msg: "Docker is required. Please install Docker first."
|
||||||
|
- sh: test -f build/docker/Dockerfile.server
|
||||||
|
msg: "Dockerfile.server not found. Run 'wails3 update build-assets' to generate it."
|
||||||
|
|
||||||
|
run:docker:
|
||||||
|
summary: Builds and runs the Docker image
|
||||||
|
desc: |
|
||||||
|
Builds the Docker image and runs it, exposing port 8080.
|
||||||
|
Usage: task run:docker [TAG=myapp:latest] [PORT=8080]
|
||||||
|
Note: The internal container port is always 8080. The PORT variable
|
||||||
|
only changes the host port mapping. Ensure your app uses port 8080
|
||||||
|
or modify the Dockerfile to match your ServerOptions.Port setting.
|
||||||
|
deps:
|
||||||
|
- task: build:docker
|
||||||
|
vars:
|
||||||
|
TAG:
|
||||||
|
ref: .TAG
|
||||||
|
cmds:
|
||||||
|
- docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default (printf "%s:latest" .APP_NAME)}}
|
||||||
|
vars:
|
||||||
|
TAG: "{{.TAG}}"
|
||||||
|
PORT: "{{.PORT}}"
|
||||||
|
|
||||||
|
setup:docker:
|
||||||
|
summary: Builds Docker image for cross-compilation (~800MB download)
|
||||||
|
desc: |
|
||||||
|
Builds the Docker image needed for cross-compiling to any platform.
|
||||||
|
Run this once to enable cross-platform builds from any OS.
|
||||||
|
cmds:
|
||||||
|
- docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/
|
||||||
|
preconditions:
|
||||||
|
- sh: docker info > /dev/null 2>&1
|
||||||
|
msg: "Docker is required. Please install Docker first."
|
||||||
|
|
||||||
|
ios:device:list:
|
||||||
|
summary: Lists connected iOS devices (UDIDs)
|
||||||
|
cmds:
|
||||||
|
- xcrun xcdevice list
|
||||||
|
|
||||||
|
ios:run:device:
|
||||||
|
summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl)
|
||||||
|
vars:
|
||||||
|
PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/<YourProject>.xcodeproj
|
||||||
|
SCHEME: '{{.SCHEME}}' # e.g., ios.dev
|
||||||
|
CONFIG: '{{.CONFIG | default "Debug"}}'
|
||||||
|
DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}'
|
||||||
|
UDID: '{{.UDID}}' # from `task ios:device:list`
|
||||||
|
BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev
|
||||||
|
TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing
|
||||||
|
preconditions:
|
||||||
|
- sh: xcrun -f xcodebuild
|
||||||
|
msg: "xcodebuild not found. Please install Xcode."
|
||||||
|
- sh: xcrun -f devicectl
|
||||||
|
msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)."
|
||||||
|
- sh: test -n '{{.PROJECT}}'
|
||||||
|
msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)."
|
||||||
|
- sh: test -n '{{.SCHEME}}'
|
||||||
|
msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)."
|
||||||
|
- sh: test -n '{{.UDID}}'
|
||||||
|
msg: "Set UDID to your device UDID (see: task ios:device:list)."
|
||||||
|
- sh: test -n '{{.BUNDLE_ID}}'
|
||||||
|
msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)."
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}"
|
||||||
|
XCB_ARGS=(
|
||||||
|
-project "{{.PROJECT}}"
|
||||||
|
-scheme "{{.SCHEME}}"
|
||||||
|
-configuration "{{.CONFIG}}"
|
||||||
|
-destination "id={{.UDID}}"
|
||||||
|
-derivedDataPath "{{.DERIVED}}"
|
||||||
|
-allowProvisioningUpdates
|
||||||
|
-allowProvisioningDeviceRegistration
|
||||||
|
)
|
||||||
|
# Optionally inject signing identifiers if provided
|
||||||
|
if [ -n '{{.TEAM_ID}}' ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi
|
||||||
|
if [ -n '{{.BUNDLE_ID}}' ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi
|
||||||
|
xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true
|
||||||
|
# If xcpretty isn't installed, run without it
|
||||||
|
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
|
||||||
|
xcodebuild "${XCB_ARGS[@]}" build
|
||||||
|
fi
|
||||||
|
# Find built .app
|
||||||
|
APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1)
|
||||||
|
if [ -z "$APP_PATH" ]; then
|
||||||
|
echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Installing: $APP_PATH"
|
||||||
|
xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH"
|
||||||
|
echo "Launching: {{.BUNDLE_ID}}"
|
||||||
|
xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 583 533" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-246,-251)">
|
||||||
|
<g id="Ebene1">
|
||||||
|
<path d="M246,251L265,784L401,784L506,450L507,450L505,784L641,784L829,251L682,251L596,567L595,567L596,251L478,251L378,568L391,251L246,251Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 698 B |
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"fill" : {
|
||||||
|
"automatic-gradient" : "extended-gray:1.00000,1.00000"
|
||||||
|
},
|
||||||
|
"groups" : [
|
||||||
|
{
|
||||||
|
"layers" : [
|
||||||
|
{
|
||||||
|
"fill-specializations" : [
|
||||||
|
{
|
||||||
|
"appearance" : "dark",
|
||||||
|
"value" : {
|
||||||
|
"solid" : "srgb:0.92143,0.92145,0.92144,1.00000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearance" : "tinted",
|
||||||
|
"value" : {
|
||||||
|
"solid" : "srgb:0.83742,0.83744,0.83743,1.00000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image-name" : "wails_icon_vector.svg",
|
||||||
|
"name" : "wails_icon_vector",
|
||||||
|
"position" : {
|
||||||
|
"scale" : 1.25,
|
||||||
|
"translation-in-points" : [
|
||||||
|
36.890625,
|
||||||
|
4.96875
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shadow" : {
|
||||||
|
"kind" : "neutral",
|
||||||
|
"opacity" : 0.5
|
||||||
|
},
|
||||||
|
"specular" : true,
|
||||||
|
"translucency" : {
|
||||||
|
"enabled" : true,
|
||||||
|
"value" : 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"supported-platforms" : {
|
||||||
|
"circles" : [
|
||||||
|
"watchOS"
|
||||||
|
],
|
||||||
|
"squares" : "shared"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,79 @@
|
|||||||
|
# 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: "0.0.1" # The application version
|
||||||
|
# cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional)
|
||||||
|
# # Should match the name of your .icon file without the extension
|
||||||
|
# # If not set and Assets.car exists, defaults to "appicon"
|
||||||
|
|
||||||
|
# iOS build configuration (uncomment to customise iOS project generation)
|
||||||
|
# Note: Keys under `ios` OVERRIDE values under `info` when set.
|
||||||
|
# ios:
|
||||||
|
# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier)
|
||||||
|
# bundleID: "com.mycompany.myproduct"
|
||||||
|
# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName)
|
||||||
|
# displayName: "My Product"
|
||||||
|
# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion)
|
||||||
|
# version: "0.0.1"
|
||||||
|
# # The company/organisation name for templates and project settings
|
||||||
|
# company: "My Company"
|
||||||
|
# # Additional comments to embed in Info.plist metadata
|
||||||
|
# comments: "Some Product Comments"
|
||||||
|
|
||||||
|
# Dev mode configuration
|
||||||
|
dev_mode:
|
||||||
|
root_path: .
|
||||||
|
log_level: warn
|
||||||
|
debounce: 1000
|
||||||
|
ignore:
|
||||||
|
dir:
|
||||||
|
- .git
|
||||||
|
- node_modules
|
||||||
|
- frontend
|
||||||
|
- bin
|
||||||
|
file:
|
||||||
|
- .DS_Store
|
||||||
|
- .gitignore
|
||||||
|
- .gitkeep
|
||||||
|
- "*_test.go"
|
||||||
|
watched_extension:
|
||||||
|
- "*.go"
|
||||||
|
- "*.js" # Watch for changes to JS/TS files included using the //wails:include directive.
|
||||||
|
- "*.ts" # The frontend directory will be excluded entirely by the setting above.
|
||||||
|
git_ignore: true
|
||||||
|
executes:
|
||||||
|
- cmd: wails3 build DEV=true
|
||||||
|
type: blocking
|
||||||
|
- cmd: wails3 task common:dev:frontend
|
||||||
|
type: background
|
||||||
|
- 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
|
||||||
|
# mimeType: image/jpeg # (optional)
|
||||||
|
|
||||||
|
# Other data
|
||||||
|
other:
|
||||||
|
- name: My Other Data
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# Cross-compile Wails v3 apps to any platform
|
||||||
|
#
|
||||||
|
# Darwin: Zig + macOS SDK
|
||||||
|
# Linux: Native GCC when host matches target, Zig for cross-arch
|
||||||
|
# Windows: Zig + bundled mingw
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker build -t wails-cross -f Dockerfile.cross .
|
||||||
|
# docker run --rm -v $(pwd):/app wails-cross darwin arm64
|
||||||
|
# docker run --rm -v $(pwd):/app wails-cross darwin amd64
|
||||||
|
# docker run --rm -v $(pwd):/app wails-cross linux amd64
|
||||||
|
# docker run --rm -v $(pwd):/app wails-cross linux arm64
|
||||||
|
# docker run --rm -v $(pwd):/app wails-cross windows amd64
|
||||||
|
# docker run --rm -v $(pwd):/app wails-cross windows arm64
|
||||||
|
|
||||||
|
FROM golang:1.26-bookworm
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG GARBLE_VERSION=v0.16.0
|
||||||
|
|
||||||
|
# Install base tools, GCC, and GTK/WebKit dev packages
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl xz-utils nodejs npm pkg-config gcc libc6-dev \
|
||||||
|
libgtk-3-dev libwebkit2gtk-4.1-dev \
|
||||||
|
libgtk-4-dev libwebkitgtk-6.0-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN go install mvdan.cc/garble@${GARBLE_VERSION}
|
||||||
|
|
||||||
|
# Install Zig - automatically selects correct binary for host architecture
|
||||||
|
ARG ZIG_VERSION=0.14.0
|
||||||
|
RUN ZIG_ARCH=$(case "${TARGETARCH}" in arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
||||||
|
curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}.tar.xz" \
|
||||||
|
| tar -xJ -C /opt \
|
||||||
|
&& ln -s /opt/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}/zig /usr/local/bin/zig
|
||||||
|
|
||||||
|
# Download macOS SDK (required for darwin targets)
|
||||||
|
ARG MACOS_SDK_VERSION=14.5
|
||||||
|
RUN curl -L "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz" \
|
||||||
|
| tar -xJ -C /opt \
|
||||||
|
&& mv /opt/MacOSX${MACOS_SDK_VERSION}.sdk /opt/macos-sdk
|
||||||
|
|
||||||
|
ENV MACOS_SDK_PATH=/opt/macos-sdk
|
||||||
|
|
||||||
|
# Create Zig CC wrappers for cross-compilation targets
|
||||||
|
# Darwin and Windows use Zig; Linux uses native GCC (run with --platform for cross-arch)
|
||||||
|
|
||||||
|
# Darwin arm64
|
||||||
|
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64
|
||||||
|
#!/bin/sh
|
||||||
|
ARGS=""
|
||||||
|
SKIP_NEXT=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ $SKIP_NEXT -eq 1 ]; then
|
||||||
|
SKIP_NEXT=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$arg" in
|
||||||
|
-target) SKIP_NEXT=1 ;;
|
||||||
|
-mmacosx-version-min=*) ;;
|
||||||
|
*) ARGS="$ARGS $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
exec zig cc -fno-sanitize=all -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
|
||||||
|
ZIGWRAP
|
||||||
|
RUN chmod +x /usr/local/bin/zcc-darwin-arm64
|
||||||
|
|
||||||
|
# Darwin amd64
|
||||||
|
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64
|
||||||
|
#!/bin/sh
|
||||||
|
ARGS=""
|
||||||
|
SKIP_NEXT=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ $SKIP_NEXT -eq 1 ]; then
|
||||||
|
SKIP_NEXT=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$arg" in
|
||||||
|
-target) SKIP_NEXT=1 ;;
|
||||||
|
-mmacosx-version-min=*) ;;
|
||||||
|
*) ARGS="$ARGS $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
|
||||||
|
ZIGWRAP
|
||||||
|
RUN chmod +x /usr/local/bin/zcc-darwin-amd64
|
||||||
|
|
||||||
|
# Windows amd64 - uses Zig's bundled mingw
|
||||||
|
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64
|
||||||
|
#!/bin/sh
|
||||||
|
ARGS=""
|
||||||
|
SKIP_NEXT=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ $SKIP_NEXT -eq 1 ]; then
|
||||||
|
SKIP_NEXT=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$arg" in
|
||||||
|
-target) SKIP_NEXT=1 ;;
|
||||||
|
-Wl,*) ;;
|
||||||
|
*) ARGS="$ARGS $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
exec zig cc -target x86_64-windows-gnu $ARGS
|
||||||
|
ZIGWRAP
|
||||||
|
RUN chmod +x /usr/local/bin/zcc-windows-amd64
|
||||||
|
|
||||||
|
# Windows arm64 - uses Zig's bundled mingw
|
||||||
|
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64
|
||||||
|
#!/bin/sh
|
||||||
|
ARGS=""
|
||||||
|
SKIP_NEXT=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ $SKIP_NEXT -eq 1 ]; then
|
||||||
|
SKIP_NEXT=0
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$arg" in
|
||||||
|
-target) SKIP_NEXT=1 ;;
|
||||||
|
-Wl,*) ;;
|
||||||
|
*) ARGS="$ARGS $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
exec zig cc -target aarch64-windows-gnu $ARGS
|
||||||
|
ZIGWRAP
|
||||||
|
RUN chmod +x /usr/local/bin/zcc-windows-arm64
|
||||||
|
|
||||||
|
# Build script
|
||||||
|
COPY <<'SCRIPT' /usr/local/bin/build.sh
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
OS=${1:-darwin}
|
||||||
|
ARCH=${2:-arm64}
|
||||||
|
|
||||||
|
case "${OS}-${ARCH}" in
|
||||||
|
darwin-arm64|darwin-aarch64)
|
||||||
|
export CC=zcc-darwin-arm64
|
||||||
|
export GOARCH=arm64
|
||||||
|
export GOOS=darwin
|
||||||
|
;;
|
||||||
|
darwin-amd64|darwin-x86_64)
|
||||||
|
export CC=zcc-darwin-amd64
|
||||||
|
export GOARCH=amd64
|
||||||
|
export GOOS=darwin
|
||||||
|
;;
|
||||||
|
linux-arm64|linux-aarch64)
|
||||||
|
export CC=gcc
|
||||||
|
export GOARCH=arm64
|
||||||
|
export GOOS=linux
|
||||||
|
;;
|
||||||
|
linux-amd64|linux-x86_64)
|
||||||
|
export CC=gcc
|
||||||
|
export GOARCH=amd64
|
||||||
|
export GOOS=linux
|
||||||
|
;;
|
||||||
|
windows-arm64|windows-aarch64)
|
||||||
|
export CC=zcc-windows-arm64
|
||||||
|
export GOARCH=arm64
|
||||||
|
export GOOS=windows
|
||||||
|
;;
|
||||||
|
windows-amd64|windows-x86_64)
|
||||||
|
export CC=zcc-windows-amd64
|
||||||
|
export GOARCH=amd64
|
||||||
|
export GOOS=windows
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: <os> <arch>"
|
||||||
|
echo " os: darwin, linux, windows"
|
||||||
|
echo " arch: amd64, arm64"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export CGO_CFLAGS="-w"
|
||||||
|
|
||||||
|
# Build frontend if exists and not already built (host may have built it)
|
||||||
|
if [ -d "frontend" ] && [ -f "frontend/package.json" ] && [ ! -d "frontend/dist" ]; then
|
||||||
|
(cd frontend && npm install --silent && npm run build --silent)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build
|
||||||
|
APP=${APP_NAME:-$(basename $(pwd))}
|
||||||
|
mkdir -p bin
|
||||||
|
|
||||||
|
EXT=""
|
||||||
|
LDFLAGS="-s -w"
|
||||||
|
if [ "$GOOS" = "windows" ]; then
|
||||||
|
EXT=".exe"
|
||||||
|
LDFLAGS="-s -w -H windowsgui"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TAGS="production"
|
||||||
|
if [ -n "$EXTRA_TAGS" ]; then
|
||||||
|
TAGS="${TAGS},${EXTRA_TAGS}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMPILER="go build"
|
||||||
|
if [ "$OBFUSCATED" = "true" ]; then
|
||||||
|
COMPILER="garble ${GARBLE_ARGS} build"
|
||||||
|
TAGS="${TAGS},wails_obfuscated"
|
||||||
|
fi
|
||||||
|
|
||||||
|
${COMPILER} -tags "$TAGS" -trimpath -buildvcs=false -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
|
||||||
|
echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}"
|
||||||
|
SCRIPT
|
||||||
|
RUN chmod +x /usr/local/bin/build.sh
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ENTRYPOINT ["/usr/local/bin/build.sh"]
|
||||||
|
CMD ["darwin", "arm64"]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Wails Server Mode Dockerfile
|
||||||
|
# Multi-stage build for minimal image size
|
||||||
|
|
||||||
|
# Build stage
|
||||||
|
FROM golang:alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Remove local replace directive if present (for production builds)
|
||||||
|
RUN sed -i '/^replace/d' go.mod || true
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod tidy
|
||||||
|
|
||||||
|
# Build the server binary
|
||||||
|
RUN go build -tags server -ldflags="-s -w" -o server .
|
||||||
|
|
||||||
|
# Runtime stage - minimal image
|
||||||
|
FROM gcr.io/distroless/static-debian12
|
||||||
|
|
||||||
|
# Copy the binary
|
||||||
|
COPY --from=builder /app/server /server
|
||||||
|
|
||||||
|
# Copy frontend assets
|
||||||
|
COPY --from=builder /app/frontend/dist /frontend/dist
|
||||||
|
|
||||||
|
# Expose the default port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Bind to all interfaces (required for Docker)
|
||||||
|
# Can be overridden at runtime with -e WAILS_SERVER_HOST=...
|
||||||
|
ENV WAILS_SERVER_HOST=0.0.0.0
|
||||||
|
|
||||||
|
# Run the server
|
||||||
|
ENTRYPOINT ["/server"]
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
common: ../Taskfile.yml
|
||||||
|
|
||||||
|
vars:
|
||||||
|
# Signing configuration - edit these values for your project
|
||||||
|
# PGP_KEY: "path/to/signing-key.asc"
|
||||||
|
# SIGN_ROLE: "builder" # Options: origin, maint, archive, builder
|
||||||
|
#
|
||||||
|
# Password is stored securely in system keychain. Run: wails3 setup signing
|
||||||
|
|
||||||
|
# Docker image for cross-compilation (used when building on non-Linux or no CC available)
|
||||||
|
CROSS_IMAGE: wails-cross
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
summary: Builds the application for Linux
|
||||||
|
cmds:
|
||||||
|
# Linux requires CGO - use Docker when:
|
||||||
|
# 1. Cross-compiling from non-Linux, OR
|
||||||
|
# 2. No C compiler is available, OR
|
||||||
|
# 3. Target architecture differs from host architecture (cross-arch compilation)
|
||||||
|
- task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}'
|
||||||
|
vars:
|
||||||
|
ARCH: '{{.ARCH}}'
|
||||||
|
DEV: '{{.DEV}}'
|
||||||
|
OUTPUT: '{{.OUTPUT}}'
|
||||||
|
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
||||||
|
OBFUSCATED: '{{.OBFUSCATED}}'
|
||||||
|
GARBLE_ARGS: '{{.GARBLE_ARGS}}'
|
||||||
|
vars:
|
||||||
|
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||||
|
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||||
|
# Determine target architecture (defaults to host ARCH if not specified)
|
||||||
|
TARGET_ARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
# Check if a C compiler is available (gcc or clang) — cross-platform via wails3 tool
|
||||||
|
HAS_CC:
|
||||||
|
sh: 'wails3 tool has-cc'
|
||||||
|
|
||||||
|
build:native:
|
||||||
|
summary: Builds the application natively on Linux
|
||||||
|
internal: true
|
||||||
|
deps:
|
||||||
|
- task: common:go:mod:tidy
|
||||||
|
- task: common:build:frontend
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
OBFUSCATED:
|
||||||
|
ref: .OBFUSCATED
|
||||||
|
DEV:
|
||||||
|
ref: .DEV
|
||||||
|
- task: common:generate:icons
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
preconditions:
|
||||||
|
- sh: '{{if eq .OBFUSCATED "true"}}command -v garble >/dev/null 2>&1{{else}}true{{end}}'
|
||||||
|
msg: "garble is required for obfuscated builds. Install it with: go install mvdan.cc/garble@v0.16.0 (requires Go 1.24+). See https://github.com/burrowers/garble/releases for version/toolchain compatibility."
|
||||||
|
cmds:
|
||||||
|
- '{{if eq .OBFUSCATED "true"}}garble {{.GARBLE_ARGS}} build{{else}}go build{{end}} {{.BUILD_FLAGS}} -o {{.OUTPUT}}'
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if or .EXTRA_TAGS (eq .OBFUSCATED "true")}}-tags {{if eq .OBFUSCATED "true"}}wails_obfuscated{{if .EXTRA_TAGS}},{{end}}{{end}}{{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if eq .OBFUSCATED "true"}},wails_obfuscated{{end}}{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
||||||
|
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||||
|
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||||
|
env:
|
||||||
|
GOOS: linux
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GOARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
|
||||||
|
build:docker:
|
||||||
|
summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available)
|
||||||
|
internal: true
|
||||||
|
deps:
|
||||||
|
- task: common:build:frontend
|
||||||
|
vars:
|
||||||
|
OBFUSCATED:
|
||||||
|
ref: .OBFUSCATED
|
||||||
|
- task: common:generate:icons
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
preconditions:
|
||||||
|
- sh: docker info > /dev/null 2>&1
|
||||||
|
msg: "Docker is required for cross-compilation to Linux. Please install Docker."
|
||||||
|
- sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1
|
||||||
|
msg: |
|
||||||
|
Docker image '{{.CROSS_IMAGE}}' not found.
|
||||||
|
Build it first: wails3 task setup:docker
|
||||||
|
cmds:
|
||||||
|
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.DOCKER_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{if eq .OBFUSCATED "true"}}-e OBFUSCATED=true{{end}} {{if .GARBLE_ARGS}}-e GARBLE_ARGS="{{.GARBLE_ARGS}}"{{end}} "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
|
||||||
|
- cmd: docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
- mkdir -p {{.BIN_DIR}}
|
||||||
|
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
||||||
|
vars:
|
||||||
|
DOCKER_ARCH: '{{.ARCH | default "amd64"}}'
|
||||||
|
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||||
|
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||||
|
# Generate Docker volume mounts: Go module cache + go.mod replace directives
|
||||||
|
# Uses wails3 tool docker-mounts for cross-platform compatibility (Windows/Linux/macOS)
|
||||||
|
DOCKER_MOUNTS:
|
||||||
|
sh: 'wails3 tool docker-mounts'
|
||||||
|
|
||||||
|
package:
|
||||||
|
summary: Packages the application for Linux
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
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
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
cmds:
|
||||||
|
- cp "{{.APP_BINARY}}" "{{.APP_NAME}}"
|
||||||
|
- cp ../../appicon.png "{{.APP_NAME}}.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: '{{.APP_NAME}}.png'
|
||||||
|
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
|
||||||
|
OUTPUT_DIR: '../../../bin'
|
||||||
|
|
||||||
|
create:deb:
|
||||||
|
summary: Creates a deb package
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
cmds:
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
- task: generate:deb
|
||||||
|
|
||||||
|
create:rpm:
|
||||||
|
summary: Creates a rpm package
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
cmds:
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
- task: generate:rpm
|
||||||
|
|
||||||
|
create:aur:
|
||||||
|
summary: Creates a arch linux packager package
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
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: '{{.APP_NAME}}'
|
||||||
|
CATEGORIES: 'Development;'
|
||||||
|
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
|
||||||
|
|
||||||
|
run:
|
||||||
|
cmds:
|
||||||
|
- '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||||
|
|
||||||
|
sign:deb:
|
||||||
|
summary: Signs the DEB package
|
||||||
|
desc: |
|
||||||
|
Signs the .deb package with a PGP key.
|
||||||
|
Configure PGP_KEY in the vars section at the top of this file.
|
||||||
|
Password is retrieved from system keychain (run: wails3 setup signing)
|
||||||
|
deps:
|
||||||
|
- task: create:deb
|
||||||
|
cmds:
|
||||||
|
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.deb" --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}}
|
||||||
|
preconditions:
|
||||||
|
- sh: '[ -n "{{.PGP_KEY}}" ]'
|
||||||
|
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
|
||||||
|
|
||||||
|
sign:rpm:
|
||||||
|
summary: Signs the RPM package
|
||||||
|
desc: |
|
||||||
|
Signs the .rpm package with a PGP key.
|
||||||
|
Configure PGP_KEY in the vars section at the top of this file.
|
||||||
|
Password is retrieved from system keychain (run: wails3 setup signing)
|
||||||
|
deps:
|
||||||
|
- task: create:rpm
|
||||||
|
cmds:
|
||||||
|
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.rpm" --pgp-key {{.PGP_KEY}}
|
||||||
|
preconditions:
|
||||||
|
- sh: '[ -n "{{.PGP_KEY}}" ]'
|
||||||
|
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
|
||||||
|
|
||||||
|
sign:packages:
|
||||||
|
summary: Signs all Linux packages (DEB and RPM)
|
||||||
|
desc: |
|
||||||
|
Signs both .deb and .rpm packages with a PGP key.
|
||||||
|
Configure PGP_KEY in the vars section at the top of this file.
|
||||||
|
Password is retrieved from system keychain (run: wails3 setup signing)
|
||||||
|
cmds:
|
||||||
|
- task: sign:deb
|
||||||
|
- task: sign:rpm
|
||||||
|
preconditions:
|
||||||
|
- sh: '[ -n "{{.PGP_KEY}}" ]'
|
||||||
|
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Name=My Product
|
||||||
|
Comment=A verstak application
|
||||||
|
# The Exec line includes %u to pass the URL to the application
|
||||||
|
Exec=/usr/local/bin/verstak %u
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Icon=verstak
|
||||||
|
Categories=Utility;
|
||||||
|
StartupWMClass=verstak
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# 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: "verstak"
|
||||||
|
arch: ${GOARCH}
|
||||||
|
platform: "linux"
|
||||||
|
version: "0.1.0"
|
||||||
|
section: "default"
|
||||||
|
priority: "extra"
|
||||||
|
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||||
|
description: "A verstak application"
|
||||||
|
vendor: "My Company"
|
||||||
|
homepage: "https://wails.io"
|
||||||
|
license: "MIT"
|
||||||
|
release: "1"
|
||||||
|
|
||||||
|
contents:
|
||||||
|
- src: "./bin/verstak"
|
||||||
|
dst: "/usr/local/bin/verstak"
|
||||||
|
- src: "./build/appicon.png"
|
||||||
|
dst: "/usr/share/icons/hicolor/128x128/apps/verstak.png"
|
||||||
|
- src: "./build/linux/verstak.desktop"
|
||||||
|
dst: "/usr/share/applications/verstak.desktop"
|
||||||
|
|
||||||
|
# Default dependencies for the GTK4 + WebKitGTK 6.0 stack (Ubuntu 24.04+ / Debian 13+)
|
||||||
|
depends:
|
||||||
|
- libgtk-4-1
|
||||||
|
- libwebkitgtk-6.0-4
|
||||||
|
|
||||||
|
# Distribution-specific overrides for different package formats
|
||||||
|
overrides:
|
||||||
|
# RPM packages for Fedora / RHEL / AlmaLinux / Rocky Linux
|
||||||
|
rpm:
|
||||||
|
depends:
|
||||||
|
- gtk4
|
||||||
|
- webkitgtk6.0
|
||||||
|
|
||||||
|
# Arch Linux packages
|
||||||
|
archlinux:
|
||||||
|
depends:
|
||||||
|
- gtk4
|
||||||
|
- webkitgtk-6.0
|
||||||
|
|
||||||
|
# scripts section to ensure desktop database is updated after install
|
||||||
|
scripts:
|
||||||
|
postinstall: "./build/linux/nfpm/scripts/postinstall.sh"
|
||||||
|
# You can also add preremove, postremove if needed
|
||||||
|
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
|
||||||
|
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
|
||||||
|
|
||||||
|
# If you build your app with -tags gtk3 (legacy WebKit2GTK 4.1 stack — supported through v3.0.x, removed in v3.1),
|
||||||
|
# replace the depends/overrides above with these:
|
||||||
|
#
|
||||||
|
# depends:
|
||||||
|
# - libgtk-3-0
|
||||||
|
# - libwebkit2gtk-4.1-0
|
||||||
|
# overrides:
|
||||||
|
# rpm:
|
||||||
|
# depends:
|
||||||
|
# - gtk3
|
||||||
|
# - webkit2gtk4.1
|
||||||
|
# archlinux:
|
||||||
|
# depends:
|
||||||
|
# - gtk3
|
||||||
|
# - webkit2gtk-4.1
|
||||||
|
#
|
||||||
|
# replaces:
|
||||||
|
# - foobar
|
||||||
|
# provides:
|
||||||
|
# - bar
|
||||||
|
# recommends:
|
||||||
|
# - whatever
|
||||||
|
# suggests:
|
||||||
|
# - something-else
|
||||||
|
# conflicts:
|
||||||
|
# - not-foo
|
||||||
|
# - not-bar
|
||||||
|
# changelog: "changelog.yaml"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Update desktop database for .desktop file changes
|
||||||
|
# This makes the application appear in application menus and registers its capabilities.
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
|
echo "Updating desktop database..."
|
||||||
|
update-desktop-database -q /usr/share/applications
|
||||||
|
else
|
||||||
|
echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update MIME database for custom URL schemes (x-scheme-handler)
|
||||||
|
# This ensures the system knows how to handle your custom protocols.
|
||||||
|
if command -v update-mime-database >/dev/null 2>&1; then
|
||||||
|
echo "Updating MIME database..."
|
||||||
|
update-mime-database -n /usr/share/mime
|
||||||
|
else
|
||||||
|
echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -0,0 +1,383 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
|
||||||
|
"verstak/internal/core/actions"
|
||||||
|
"verstak/internal/core/files"
|
||||||
|
"verstak/internal/core/notes"
|
||||||
|
"verstak/internal/core/nodes"
|
||||||
|
"verstak/internal/core/storage"
|
||||||
|
"verstak/internal/core/worklog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is the Wails v2 application adapter. It wraps core services.
|
||||||
|
type App struct {
|
||||||
|
ctx context.Context
|
||||||
|
db *storage.DB
|
||||||
|
nodes *nodes.Repository
|
||||||
|
files *files.Service
|
||||||
|
notes *notes.Service
|
||||||
|
actions *actions.Service
|
||||||
|
worklog *worklog.Service
|
||||||
|
vault string
|
||||||
|
}
|
||||||
|
|
||||||
|
// startup is called when the app starts. Store context.
|
||||||
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
a.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// DTOs
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
type NodeDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ParentID string `json:"parentId"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Section string `json:"section"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SectionDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoteDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
NodeID string `json:"nodeId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Mime string `json:"mime"`
|
||||||
|
IsDir bool `json:"isDir"`
|
||||||
|
Missing bool `json:"missing"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
NodeID string `json:"nodeId"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorklogDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
NodeID string `json:"nodeId"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
Minutes int `json:"minutes"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchResultDTO struct {
|
||||||
|
NodeID string `json:"nodeId"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Snippet string `json:"snippet"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Sections
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) ListSections() []SectionDTO {
|
||||||
|
return []SectionDTO{
|
||||||
|
{ID: "today", Label: "Сегодня"},
|
||||||
|
{ID: "inbox", Label: "Неразобранное"},
|
||||||
|
{ID: "clients", Label: "Клиенты"},
|
||||||
|
{ID: "projects", Label: "Проекты"},
|
||||||
|
{ID: "recipes", Label: "Рецепты"},
|
||||||
|
{ID: "documents", Label: "Документы"},
|
||||||
|
{ID: "archive", Label: "Архив"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Nodes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) ListNodesBySection(section string) ([]NodeDTO, error) {
|
||||||
|
list, err := a.nodes.ListRoots(false, section)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toNodeDTOs(list), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) ListChildren(parentID string) ([]NodeDTO, error) {
|
||||||
|
list, err := a.nodes.ListChildren(parentID, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toNodeDTOs(list), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetNodeDetail(nodeID string) (*NodeDTO, error) {
|
||||||
|
n, err := a.nodes.GetActive(nodeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto := toNodeDTO(n)
|
||||||
|
return &dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) CreateNode(parentID, nodeType, title, section string) (*NodeDTO, error) {
|
||||||
|
n, err := a.nodes.Create(parentID, nodeType, title, section)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto := toNodeDTO(n)
|
||||||
|
return &dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteNode(id string) error {
|
||||||
|
return a.nodes.SoftDelete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Notes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// ListNotes returns note-type children of a node.
|
||||||
|
func (a *App) ListNotes(nodeID string) ([]NodeDTO, error) {
|
||||||
|
children, err := a.nodes.ListChildren(nodeID, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result []NodeDTO
|
||||||
|
for i := range children {
|
||||||
|
if children[i].Type == nodes.TypeNote {
|
||||||
|
result = append(result, toNodeDTO(&children[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNote creates a note under a parent node.
|
||||||
|
func (a *App) CreateNote(parentID, title string) (*NodeDTO, error) {
|
||||||
|
node, _, err := a.notes.Create(parentID, title, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto := toNodeDTO(node)
|
||||||
|
return &dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadNote reads note content.
|
||||||
|
func (a *App) ReadNote(noteID string) (string, error) {
|
||||||
|
return a.notes.Read(noteID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveNote saves note content.
|
||||||
|
func (a *App) SaveNote(noteID, content string) error {
|
||||||
|
return a.notes.Save(noteID, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Files
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) ListFiles(nodeID string) ([]FileDTO, error) {
|
||||||
|
records, err := a.files.ListByNode(nodeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]FileDTO, len(records))
|
||||||
|
for i := range records {
|
||||||
|
isDir := records[i].MIME == "inode/directory"
|
||||||
|
missing := false
|
||||||
|
result[i] = FileDTO{
|
||||||
|
ID: records[i].ID,
|
||||||
|
NodeID: records[i].NodeID,
|
||||||
|
Name: records[i].Filename,
|
||||||
|
Path: records[i].Path,
|
||||||
|
Size: records[i].Size,
|
||||||
|
Mime: records[i].MIME,
|
||||||
|
IsDir: isDir,
|
||||||
|
Missing: missing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Actions
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) ListActions(nodeID string) ([]ActionDTO, error) {
|
||||||
|
list, err := a.actions.ListByNode(nodeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]ActionDTO, len(list))
|
||||||
|
for i := range list {
|
||||||
|
data := list[i].Command
|
||||||
|
if list[i].URL != "" {
|
||||||
|
data = list[i].URL
|
||||||
|
}
|
||||||
|
result[i] = ActionDTO{
|
||||||
|
ID: list[i].ID,
|
||||||
|
NodeID: list[i].NodeID,
|
||||||
|
Title: list[i].Title,
|
||||||
|
Type: list[i].Kind,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RunAction(id string) error {
|
||||||
|
_, err := a.actions.Run(id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Worklog
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) ListWorklog(nodeID string) ([]WorklogDTO, error) {
|
||||||
|
list, err := a.worklog.ListByNode(nodeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]WorklogDTO, len(list))
|
||||||
|
for i := range list {
|
||||||
|
mins := 0
|
||||||
|
if list[i].Minutes != nil {
|
||||||
|
mins = *list[i].Minutes
|
||||||
|
}
|
||||||
|
result[i] = WorklogDTO{
|
||||||
|
ID: list[i].ID,
|
||||||
|
NodeID: list[i].NodeID,
|
||||||
|
Summary: list[i].Summary,
|
||||||
|
Minutes: mins,
|
||||||
|
CreatedAt: list[i].CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) CreateWorklog(nodeID, summary string, minutes int) (*WorklogDTO, error) {
|
||||||
|
entry, err := a.worklog.Add(nodeID, summary, "", minutes, false, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mins := 0
|
||||||
|
if entry.Minutes != nil {
|
||||||
|
mins = *entry.Minutes
|
||||||
|
}
|
||||||
|
dto := &WorklogDTO{
|
||||||
|
ID: entry.ID,
|
||||||
|
NodeID: entry.NodeID,
|
||||||
|
Summary: entry.Summary,
|
||||||
|
Minutes: mins,
|
||||||
|
CreatedAt: entry.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
}
|
||||||
|
return dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Search (stubs — search service not wired yet)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) Search(query string) ([]SearchResultDTO, error) {
|
||||||
|
if strings.TrimSpace(query) == "" {
|
||||||
|
return []SearchResultDTO{}, nil
|
||||||
|
}
|
||||||
|
return []SearchResultDTO{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// File Dialogs (Wails v2 Runtime)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) PickFile() (string, error) {
|
||||||
|
return wailsruntime.OpenFileDialog(a.ctx, wailsruntime.OpenDialogOptions{
|
||||||
|
Title: "Выберите файл",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) PickFiles() ([]string, error) {
|
||||||
|
return wailsruntime.OpenMultipleFilesDialog(a.ctx, wailsruntime.OpenDialogOptions{
|
||||||
|
Title: "Выберите файлы",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) PickDirectory() (string, error) {
|
||||||
|
return wailsruntime.OpenDirectoryDialog(a.ctx, wailsruntime.OpenDialogOptions{
|
||||||
|
Title: "Выберите папку",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// System helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func (a *App) OpenFile(fileID string) error {
|
||||||
|
return fmt.Errorf("not implemented: %s", fileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) OpenFolder(nodeID string) error {
|
||||||
|
cmd := exec.Command("xdg-open", a.vault)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) VerstakVersion() string {
|
||||||
|
return "verstak-gui/v2"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Helpers
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
func toNodeDTO(n *nodes.Node) NodeDTO {
|
||||||
|
parentID := ""
|
||||||
|
if n.ParentID != nil {
|
||||||
|
parentID = *n.ParentID
|
||||||
|
}
|
||||||
|
path := ""
|
||||||
|
if n.Path != nil {
|
||||||
|
path = *n.Path
|
||||||
|
}
|
||||||
|
return NodeDTO{
|
||||||
|
ID: n.ID,
|
||||||
|
ParentID: parentID,
|
||||||
|
Title: n.Title,
|
||||||
|
Type: n.Type,
|
||||||
|
Section: n.Section,
|
||||||
|
Path: path,
|
||||||
|
CreatedAt: n.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNodeDTOs(list []nodes.Node) []NodeDTO {
|
||||||
|
result := make([]NodeDTO, len(list))
|
||||||
|
for i := range list {
|
||||||
|
result[i] = toNodeDTO(&list[i])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = os.Getenv
|
||||||
|
_ = exec.Command
|
||||||
|
)
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
.svelte-14ysusk.svelte-14ysusk,.svelte-14ysusk.svelte-14ysusk:before,.svelte-14ysusk.svelte-14ysusk:after{box-sizing:border-box;margin:0;padding:0}.app.svelte-14ysusk.svelte-14ysusk{display:flex;width:100vw;height:100vh;overflow:hidden;background:#13131f;color:#e4e4ef;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px}.sidebar.svelte-14ysusk.svelte-14ysusk{width:260px;min-width:200px;height:100vh;display:flex;flex-direction:column;background:#1a1a28;border-right:1px solid #2a2a3c;flex-shrink:0;overflow:hidden}.sidebar-top.svelte-14ysusk.svelte-14ysusk{padding:16px 20px;display:flex;align-items:center;gap:10px;border-bottom:1px solid #2a2a3c;flex-shrink:0}.logo.svelte-14ysusk.svelte-14ysusk{font-size:20px;line-height:1}.app-name.svelte-14ysusk.svelte-14ysusk{font-size:16px;font-weight:600;color:#e4e4ef}.sidebar-nav.svelte-14ysusk.svelte-14ysusk{flex:1;overflow-y:auto;padding:12px 0}.nav-group.svelte-14ysusk.svelte-14ysusk{margin-bottom:16px}.nav-label.svelte-14ysusk.svelte-14ysusk{font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:#666;padding:4px 20px;margin-bottom:4px}.nav-item.svelte-14ysusk.svelte-14ysusk{display:block;width:100%;padding:8px 20px;border:none;background:none;color:#ccc;font-size:13px;text-align:left;cursor:pointer;border-radius:0;font-family:inherit}.nav-item.svelte-14ysusk.svelte-14ysusk:hover{background:#223}.nav-item.selected.svelte-14ysusk.svelte-14ysusk{background:#2a2a4a;color:#fff;font-weight:500}.nav-empty.svelte-14ysusk.svelte-14ysusk{padding:8px 20px;color:#555;font-size:12px}.sidebar-bottom.svelte-14ysusk.svelte-14ysusk{padding:12px 20px;border-top:1px solid #2a2a3c;flex-shrink:0}.version.svelte-14ysusk.svelte-14ysusk{font-size:11px;color:#555}.main.svelte-14ysusk.svelte-14ysusk{flex:1;display:flex;flex-direction:column;height:100vh;min-width:0;overflow:hidden;background:#13131f}.header.svelte-14ysusk.svelte-14ysusk{padding:12px 24px;border-bottom:1px solid #2a2a3c;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;min-height:48px}.crumb.svelte-14ysusk.svelte-14ysusk{font-size:14px;font-weight:500;color:#e4e4ef}.crumb.placeholder.svelte-14ysusk.svelte-14ysusk{color:#666}.search-hint.svelte-14ysusk.svelte-14ysusk{padding:6px 12px;background:#1e1e2e;border:1px solid #2a2a3c;border-radius:4px;color:#666;font-size:12px;cursor:text}.error-banner.svelte-14ysusk.svelte-14ysusk{background:#3a2222;color:#f88;padding:8px 24px;font-size:12px;border-bottom:1px solid #4a2222;flex-shrink:0}.content.svelte-14ysusk.svelte-14ysusk{flex:1;overflow-y:auto;padding:24px}.welcome.svelte-14ysusk h2.svelte-14ysusk{font-size:28px;font-weight:300;margin-bottom:12px;color:#8888a4}.welcome.svelte-14ysusk p.svelte-14ysusk{color:#666;font-size:13px;margin-bottom:4px}.error-text.svelte-14ysusk.svelte-14ysusk{color:#f88;margin-top:12px}.loading.svelte-14ysusk.svelte-14ysusk{color:#666}.node-view.svelte-14ysusk h2.svelte-14ysusk{font-size:24px;margin-bottom:16px}.node-meta.svelte-14ysusk.svelte-14ysusk{display:flex;gap:16px;color:#666;font-size:12px}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/wails.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Верстак</title>
|
||||||
|
<style>
|
||||||
|
/* Critical reset — no white borders, full viewport */
|
||||||
|
html, body, #app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #13131f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="module" crossorigin src="/assets/index-COs6tJEl.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-ClxkTvdE.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
: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");
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 3em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: none;
|
||||||
|
margin: 0 0 0 20px;
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 1rem;
|
||||||
|
align-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-box .btn:hover {
|
||||||
|
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box .input {
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: black;
|
||||||
|
background-color: rgba(240, 240, 240, 1);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box .input:hover {
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box .input:focus {
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
+50
-40
@@ -1,22 +1,28 @@
|
|||||||
//go:build gui
|
|
||||||
// +build gui
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"embed"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
gui "verstak/internal/gui"
|
"verstak/internal/core/actions"
|
||||||
|
"verstak/internal/core/files"
|
||||||
|
"verstak/internal/core/notes"
|
||||||
|
"verstak/internal/core/nodes"
|
||||||
|
"verstak/internal/core/plugins"
|
||||||
|
"verstak/internal/core/search"
|
||||||
"verstak/internal/core/storage"
|
"verstak/internal/core/storage"
|
||||||
|
"verstak/internal/core/worklog"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed all:frontend-dist
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
vaultPath := "."
|
vaultPath := "."
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
@@ -35,37 +41,41 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
srv := gui.NewServer(db, abs)
|
// Init core services
|
||||||
addr, err := srv.Start()
|
nodeRepo := nodes.NewRepository(db)
|
||||||
|
fileSvc := files.NewService(db, abs)
|
||||||
|
noteSvc := notes.NewService(db, abs, nodeRepo, fileSvc)
|
||||||
|
actionSvc := actions.NewService(db)
|
||||||
|
worklogSvc := worklog.NewService(db)
|
||||||
|
searchSvc := search.NewService(db)
|
||||||
|
plugins.NewManager(abs).Discover()
|
||||||
|
_ = searchSvc
|
||||||
|
|
||||||
|
app := &App{
|
||||||
|
db: db,
|
||||||
|
nodes: nodeRepo,
|
||||||
|
files: fileSvc,
|
||||||
|
notes: noteSvc,
|
||||||
|
actions: actionSvc,
|
||||||
|
worklog: worklogSvc,
|
||||||
|
vault: abs,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wails.Run(&options.App{
|
||||||
|
Title: "Верстак",
|
||||||
|
Width: 1280,
|
||||||
|
Height: 800,
|
||||||
|
MinWidth: 800,
|
||||||
|
MinHeight: 600,
|
||||||
|
BackgroundColour: &options.RGBA{R: 19, G: 19, B: 31, A: 1},
|
||||||
|
AssetServer: &assetserver.Options{
|
||||||
|
Assets: assets,
|
||||||
|
},
|
||||||
|
OnStartup: app.startup,
|
||||||
|
Bind: []interface{}{app},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Start GUI: %v", err)
|
log.Fatal(err)
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Верстак GUI:", addr)
|
|
||||||
openBrowser(addr)
|
|
||||||
|
|
||||||
// Wait for interrupt.
|
|
||||||
sig := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
<-sig
|
|
||||||
|
|
||||||
srv.Stop()
|
|
||||||
deferFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func openBrowser(url string) {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
cmd = exec.Command("xdg-open", url)
|
|
||||||
case "darwin":
|
|
||||||
cmd = exec.Command("open", url)
|
|
||||||
case "windows":
|
|
||||||
cmd = exec.Command("cmd", "/c", "start", "", url)
|
|
||||||
}
|
|
||||||
if cmd != nil {
|
|
||||||
go cmd.Start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deferFunc() {}
|
|
||||||
|
|||||||
+270
-334
@@ -1,4 +1,4 @@
|
|||||||
# Верстак — Пошаговый план реализации
|
# Верстак — Пошаговый план разработки
|
||||||
|
|
||||||
## Принципы работы
|
## Принципы работы
|
||||||
|
|
||||||
@@ -16,359 +16,288 @@
|
|||||||
| 3 | Nodes Repository + CRUD + CLI Node | ✅ выполнен |
|
| 3 | Nodes Repository + CRUD + CLI Node | ✅ выполнен |
|
||||||
| 4 | Vault Files: Trash + File Service + CLI File | ✅ выполнен |
|
| 4 | Vault Files: Trash + File Service + CLI File | ✅ выполнен |
|
||||||
| 5 | Markdown Notes: Create/Read/Save + CLI Note | ✅ выполнен |
|
| 5 | Markdown Notes: Create/Read/Save + CLI Note | ✅ выполнен |
|
||||||
| 6 | Wails GUI MVP: Sidebar + Main Panel | ✅ выполнен (Go HTTP SPA) |
|
| 6 | GUI (browser prototype): Sidebar + Main Panel | ✅ выполнен |
|
||||||
| 7 | Actions: Run URL/File/Command + GUI Tab | ✅ выполнен |
|
| 7 | Actions + Worklog | ✅ выполнен |
|
||||||
| 8 | Worklog: Entries + Report + GUI Tab | ✅ выполнен |
|
| 8 | FTS5 Search | ✅ выполнен |
|
||||||
| 9 | FTS5 Search: Rebuild Index + GUI Search Bar | ✅ выполнен |
|
| 9 | Section assignment + Sidebar filtering | ✅ выполнен |
|
||||||
| 10 | Plugins System (Lua + Templates) | ✅ выполнен |
|
| 10 | Plugin Manager (discovery + templates) | ✅ выполнен |
|
||||||
| 11 | Sync Server Skeleton | ⬜ не начат |
|
| 11 | **Wails Desktop GUI** | 🔄 Wails v2 vertical MVP |
|
||||||
| 12 | Sync Client MVP | ⬜ не начат |
|
| 12 | **Files/Folders full workflow** | ⬜ следующий этап после vertical MVP |
|
||||||
| 13 | Activity + File Scanner/Watcher | ⬜ не начат |
|
| 13 | **Drag-and-drop** | ⬜ не начат |
|
||||||
| 14 | TUI MVP (Bubble Tea) | ⬜ не начат |
|
| 14 | **MVP stabilization** | ⬜ не начат |
|
||||||
| 15 | Integrity Check + Repair + Vault Restore | ⬜ не начат |
|
| 15 | Sync Server Skeleton | 🔒 PAUSED |
|
||||||
| 16 | Plugins System (Lua + Templates) | ⬜ не начат |
|
| 16 | Sync Client MVP | 🔒 PAUSED |
|
||||||
|
| 17 | Activity + File Scanner/Watcher | 🔒 PAUSED |
|
||||||
|
| 18 | TUI MVP (Bubble Tea) | 🔒 PAUSED |
|
||||||
|
| 19 | Integrity Check + Repair | 🔒 PAUSED |
|
||||||
|
| 20 | Plugins: Lua runtime | 🔒 PAUSED |
|
||||||
|
| 21 | DokuWiki Importer (plugin) | 🔒 PAUSED |
|
||||||
|
| 22 | Calendar/Kanban | 🔒 PAUSED |
|
||||||
|
| 23 | New templates/integrations | 🔒 PAUSED |
|
||||||
|
|
||||||
|
> 🔒 = **PAUSED** — не начинать до завершения шага 14 (MVP stabilization).
|
||||||
|
|
||||||
|
> **Wails v3 → v2 migration:** Wails v3 alpha.96 показал SIGSEGV на Linux desktop (GTK/X11). Wails v2 stable выбран как GUI base для MVP. Миграция в процессе (ветка `gui/migrate-wails-v2`).
|
||||||
|
|
||||||
|
**GUI Build (Wails v2):**
|
||||||
|
```bash
|
||||||
|
cd frontend && npm run build && cd ..
|
||||||
|
rm -rf cmd/verstak-gui/frontend-dist && cp -r frontend/dist cmd/verstak-gui/frontend-dist
|
||||||
|
go build -tags "gui production webkit2_41" -o verstak-gui ./cmd/verstak-gui
|
||||||
|
./verstak-gui
|
||||||
|
```
|
||||||
|
|
||||||
|
**GUI Build (Wails v2):**
|
||||||
|
```bash
|
||||||
|
cd frontend && npm run build && cd ..
|
||||||
|
rm -rf cmd/verstak-gui/frontend-dist && cp -r frontend/dist cmd/verstak-gui/frontend-dist
|
||||||
|
go build -tags "gui production webkit2_41" -o verstak-gui ./cmd/verstak-gui
|
||||||
|
./verstak-gui
|
||||||
|
```
|
||||||
|
|
||||||
|
Или для dev режима: `wails dev` (требует Wails v2 CLI)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ШАГ 1 — Git Init + Skeleton
|
## Текущий этап: Wails v2 Vertical MVP
|
||||||
|
|
||||||
**Цель:** репозиторий создан, пустая структура, "hello world" билдится.
|
**Цель:** базовый рабочий desktop GUI для разделов → дел → заметок.
|
||||||
|
|
||||||
**Acceptance:**
|
**Прогресс:**
|
||||||
- `go build ./...` проходит
|
- ✅ Wails v2 shell (window opens, no SIGSEGV)
|
||||||
- `go test ./...` проходит
|
- ✅ Layout fix (full viewport, dark theme, sidebar+main)
|
||||||
- `verstak --version` выводит версию
|
- 🔄 Notes bindings + UI
|
||||||
- Повторный init безопасен
|
- 🔄 Tabs (Overview/Notes/Files/Actions/Worklog/Activity)
|
||||||
|
- 🔄 Node creation
|
||||||
|
- 🔄 Section filtering
|
||||||
|
|
||||||
**Действия:**
|
**Пауза (не начинать до завершения vertical MVP):**
|
||||||
- git init, .gitignore (Go, Wails)
|
- Файлы/папки workflow
|
||||||
- `go mod init verstak`
|
- Drag-and-drop
|
||||||
- Структура: `cmd/verstak/`, `internal/core/`, `migrations/`
|
- Sync, plugins, Lua, browser extension, TUI
|
||||||
- `cmd/verstak/main.go`: --version flag
|
- Новые шаблоны, DokuWiki importer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Выполненные шаги (1-10)
|
||||||
|
|
||||||
|
### ШАГ 1 — Git Init + Skeleton
|
||||||
|
- go module `verstak`, структура cmd/internal/migrations
|
||||||
|
- CLI `verstak --version`
|
||||||
- README.md
|
- README.md
|
||||||
|
|
||||||
**Commit:** `step 1: skeleton`
|
### ШАГ 2 — Init + SQLite
|
||||||
|
- storage.go: DB wrapper, migration runner
|
||||||
|
- vault.go: Init() создаёт .verstak/ + index.db
|
||||||
|
- config.go: YAML config
|
||||||
|
|
||||||
|
### ШАГ 3 — Nodes Repository
|
||||||
|
- Node struct + CRUD (Create, Get, ListChildren, ListRoots, UpdateTitle, Move, SoftDelete)
|
||||||
|
- Meta KV + tests
|
||||||
|
|
||||||
|
### ШАГ 4 — Files
|
||||||
|
- FileService: AddExternal, CopyIntoVault, Get, ListByNode, MarkMissing, DeleteToTrash, Open
|
||||||
|
- file_test.go: 5 tests
|
||||||
|
|
||||||
|
### ШАГ 5 — Notes
|
||||||
|
- NoteService: Create, Read, Save (с backup), Delete
|
||||||
|
- note_test.go: 3 tests
|
||||||
|
|
||||||
|
### ШАГ 6 — GUI (browser prototype)
|
||||||
|
- Go HTTP SPA на случайном порту
|
||||||
|
- Sidebar tree + разделы (Сегодня, Неразобранное, Клиенты, Проекты...)
|
||||||
|
- Dashboard дела + вкладки (Обзор, Заметки, Файлы, Действия, Журнал, Активность)
|
||||||
|
- Модальное окно "+ Добавить" с выбором типа
|
||||||
|
- Поиск по корневым нодам
|
||||||
|
- **Это legacy prototype — не развивать как основной GUI**
|
||||||
|
|
||||||
|
### ШАГ 7 — Actions + Worklog
|
||||||
|
- ActionService: Create, Get, ListByNode, Delete, Run (open_url/file/folder, run_command)
|
||||||
|
- WorklogService: Add, Update, ListByNode, Delete, SumMinutes, Report
|
||||||
|
- CLI: `action add/list/run/delete`, `log add/list/report`
|
||||||
|
- GUI вкладки с кнопками действий и журналом работ
|
||||||
|
|
||||||
|
### ШАГ 8 — FTS5 Search
|
||||||
|
- SearchService: Index, Remove, Rebuild, Search (FTS5 MATCH)
|
||||||
|
- FTS5 virtual table создаётся лениво (работает с/без FTS5)
|
||||||
|
- Fallback на LIKE по заголовкам нод
|
||||||
|
- CLI: `verstak index rebuild`
|
||||||
|
- GUI search bar
|
||||||
|
|
||||||
|
### ШАГ 9 — Section assignment
|
||||||
|
- Колонка `section` в nodes (clients/projects/recipes/documents/archive/inbox)
|
||||||
|
- Фильтрация разделов по `?section=` в API
|
||||||
|
- Root-ноды без section → inbox
|
||||||
|
|
||||||
|
### ШАГ 10 — Plugin Manager
|
||||||
|
- Discovery: `.verstak/plugins/<name>/plugin.json`
|
||||||
|
- Enable/disable, templates → pre-filled node trees
|
||||||
|
- Built-in шаблон "Клиент" (Overview + Документы/Переписка/Скриншоты)
|
||||||
|
- Template selector в модалке создания дела
|
||||||
|
- POST /api/nodes/from-template
|
||||||
|
- CLI: `plugin list/enable/disable/templates`
|
||||||
|
- Lua runtime — stub (placeholder)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ШАГ 2 — Init + SQLite + First Migration
|
## Текущий этап: ШАГ 11 — Wails Desktop GUI
|
||||||
|
|
||||||
**Цель:** `verstak init --vault ./test` создаёт vault с index.db.
|
**Целевой commit:** `gui/wails-file-workflow`
|
||||||
|
|
||||||
**Acceptance:**
|
Архитектура:
|
||||||
- `go test ./...` проходит
|
|
||||||
- init создаёт `.verstak/index.db`
|
|
||||||
- повторный init безопасен
|
|
||||||
|
|
||||||
**Действия:**
|
```
|
||||||
- migration runner (cmd + SQL migrations/)
|
┌─────────────────────────────────────────────────┐
|
||||||
- миграция 001_init.sql (таблица nodes)
|
│ Frontend (Wails) │
|
||||||
- `_ "github.com/mattn/go-sqlite3"` или modernc driver
|
│ frontend/src/ │
|
||||||
- CLI `init`: vault dir + `.verstak/` + `index.db`
|
│ App.svelte │
|
||||||
|
│ components/Sidebar, TopBar, CaseView, ... │
|
||||||
|
│ stores/selection, nodes, files, ui │
|
||||||
|
│ styles/theme.css │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│ Wails bindings
|
||||||
|
┌──────────────────▼──────────────────────────────┐
|
||||||
|
│ Go Core (internal/core/) │
|
||||||
|
│ nodes, vault, storage, notes, files, │
|
||||||
|
│ actions, worklog, search, plugins │
|
||||||
|
└──────────────────┬──────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌──────────────────▼──────────────────────────────┐
|
||||||
|
│ Vault filesystem + SQLite │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
**Commit:** `step 2: init + sqlite + first migration`
|
### Действия
|
||||||
|
|
||||||
|
1. Создать Wails app skeleton (`wails init`)
|
||||||
|
2. Структура frontend/ — Svelte/Vue/vanilla TS
|
||||||
|
3. Backend bindings — методы Wails над core services
|
||||||
|
4. Перенести текущий UI shell из inline HTML в frontend
|
||||||
|
5. Оставить текущий `internal/gui/` как legacy (не удалять, но не развивать)
|
||||||
|
|
||||||
|
### Backend bindings (минимум)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Nodes
|
||||||
|
ListSections() ([]SectionDTO, error)
|
||||||
|
ListNodes(section string) ([]NodeDTO, error)
|
||||||
|
GetNodeDetail(nodeID string) (NodeDetailDTO, error)
|
||||||
|
CreateNode(parentID, section, typ, title string) (NodeDTO, error)
|
||||||
|
FromTemplate(parentID, section, typ, title, template string) (NodeDTO, error)
|
||||||
|
DeleteNode(id string) error
|
||||||
|
MoveNode(id, parentID string) error
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
CreateNote(parentID, title string) (NodeDTO, error)
|
||||||
|
ReadNote(noteID string) (string, error)
|
||||||
|
SaveNote(noteID, content string) error
|
||||||
|
|
||||||
|
// Files
|
||||||
|
ListFiles(nodeID string) ([]FileDTO, error)
|
||||||
|
AddPathCopy(nodeID, sourcePath string) ([]NodeDTO, error) // файл или папка
|
||||||
|
AddPathLink(nodeID, sourcePath string) ([]NodeDTO, error)
|
||||||
|
DeleteFileOrFolder(id string) error
|
||||||
|
OpenFile(id string) error
|
||||||
|
OpenFolder(id string) error
|
||||||
|
PickFile() (string, error)
|
||||||
|
PickDirectory() (string, error)
|
||||||
|
|
||||||
|
// Actions/Worklog/Search
|
||||||
|
ListActions(nodeID string) ([]ActionDTO, error)
|
||||||
|
RunAction(id string) error
|
||||||
|
CreateWorklog(nodeID, summary string, minutes int) (WorklogDTO, error)
|
||||||
|
Search(query string) ([]SearchResultDTO, error)
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ШАГ 3 — Nodes Repository + CRUD + CLI Node
|
## ШАГ 12 — Files/Folders full workflow
|
||||||
|
|
||||||
**Цель:** можно создать/прочитать/переместить/удалить дело через CLI.
|
### Core service extensions
|
||||||
|
|
||||||
**Acceptance:**
|
Расширить `files.Service`:
|
||||||
- nodes + node_meta таблицы
|
|
||||||
- NodeRepository: Create, Get, ListChildren, UpdateTitle, Move, SoftDelete
|
|
||||||
- CLI: `node create`, `node list`, `node move`, `node delete`
|
|
||||||
- unit tests проходят
|
|
||||||
|
|
||||||
**Действия:**
|
```go
|
||||||
- Полная схема nodes (id, parent_id, type, title, slug, path, sort_order, created_at, updated_at, deleted_at, revision, device_id)
|
AddPathCopy(nodeID string, sourcePath string) ([]Node, error)
|
||||||
- node_meta (node_id, key, value)
|
AddPathLink(nodeID string, sourcePath string) ([]Node, error)
|
||||||
- Node struct + Repository
|
```
|
||||||
- UUID вместо auto-increment
|
|
||||||
- Soft delete (deleted_at)
|
|
||||||
- безопасный slug для path
|
|
||||||
- Tests: in-memory SQLite
|
|
||||||
|
|
||||||
**Commit:** `step 3: nodes repository + CRUD`
|
Логика:
|
||||||
|
- `os.Stat(sourcePath)` → если директория → рекурсивный обход
|
||||||
|
- Каждый файл → File node + file record
|
||||||
|
- Каждая папка → Folder node
|
||||||
|
- Структура сохраняется через parent_id
|
||||||
|
|
||||||
|
### Folder model
|
||||||
|
|
||||||
|
Папка = node type `folder` (не file record с mime=directory).
|
||||||
|
|
||||||
|
При импорте `romashka-docs/`:
|
||||||
|
```
|
||||||
|
Folder node: romashka-docs (type=folder)
|
||||||
|
File node: dogovor.docx (type=file)
|
||||||
|
Folder node: screenshots (type=folder)
|
||||||
|
File node: error.png (type=file)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Name conflict resolution
|
||||||
|
|
||||||
|
Если в target уже есть `docs`:
|
||||||
|
```
|
||||||
|
docs
|
||||||
|
docs (2)
|
||||||
|
docs (3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safety checks
|
||||||
|
|
||||||
|
При добавлении папки показать summary:
|
||||||
|
- количество файлов/папок
|
||||||
|
- общий размер
|
||||||
|
- предупреждение если > 1000 файлов, > 1 GB, содержит `.git`/`node_modules`/`.cache`
|
||||||
|
|
||||||
|
### Trash
|
||||||
|
|
||||||
|
- Soft delete node + children
|
||||||
|
- Vault files → `.verstak/trash`
|
||||||
|
- External files — только удалить связь
|
||||||
|
- Не `rm -rf`
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
1. Copy single file → vault, record created, source intact
|
||||||
|
2. Link single file → no copy, external path saved
|
||||||
|
3. Copy folder → tree created, files in vault
|
||||||
|
4. Link folder → node created, no content copied
|
||||||
|
5. Delete vault file → soft-deleted, file in trash
|
||||||
|
6. Delete vault folder → children soft-deleted
|
||||||
|
7. Name conflict → no overwrite, safe suffix
|
||||||
|
8. Open file → mocked opener (no real app launch)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ШАГ 4 — Vault Files: Trash + File Service + CLI File
|
## ШАГ 13 — Drag-and-drop
|
||||||
|
|
||||||
**Цель:** можно добавить файл в дело, открыть системным приложением, удалить в trash.
|
### External D&D
|
||||||
|
|
||||||
**Acceptance:**
|
Drop target: активное дело / вкладка Файлы / Неразобранное.
|
||||||
- `.verstak/trash/` создаётся при init
|
|
||||||
- copy file into vault работает
|
|
||||||
- open with system app работает
|
|
||||||
- delete-to-trash работает
|
|
||||||
- тесты проходят
|
|
||||||
|
|
||||||
**Действия:**
|
После drop → диалог:
|
||||||
- Таблица files (id, node_id, filename, path, storage_mode, size, sha256, mime, ...)
|
```
|
||||||
- VaultService: CopyFile, LinkExternal, OpenFile, DeleteToTrash, RestoreFromTrash
|
Добавить в "ООО Ромашка / Сайт"
|
||||||
- CLI: `file add`, `file list`, `file open`, `file trash`
|
Файлов: 3, Папок: 1, 240 MB
|
||||||
|
[Скопировать] [Переместить] [Привязать] [Отмена]
|
||||||
**Commit:** `step 4: vault files + trash + CLI`
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ШАГ 5 — Markdown Notes: Create/Read/Save + CLI Note
|
## ШАГ 14 — MVP stabilization
|
||||||
|
|
||||||
**Цель:** можно создать заметку, писать в неё, читать обратно.
|
- Smoke tests базовых сценариев
|
||||||
|
- Проверка: дело → заметка → файл → папка → trash → перезапуск
|
||||||
**Acceptance:**
|
- go test ./... pass
|
||||||
- type "note" для nodes
|
- Обновление документации
|
||||||
- создать .md файл в vault
|
- Остановка перед следующими фичами
|
||||||
- save делает backup старой версии
|
|
||||||
- тесты проходят
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Таблица notes (node_id, file_id, format, original_format, encrypted)
|
|
||||||
- NoteService: CreateNote, ReadNote, SaveNote (с backup)
|
|
||||||
- CLI: `note create`, `note read`, `note write`
|
|
||||||
- Backup старой версии перед перезаписью
|
|
||||||
|
|
||||||
**Commit:** `step 5: markdown notes`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 6 — Wails GUI MVP
|
|
||||||
|
|
||||||
**Цель:** GUI запускается, видно дерево дел, можно создать дело и заметку.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- sidebar tree показывает дела
|
|
||||||
- create node работает
|
|
||||||
- markdown textarea editor с save
|
|
||||||
- file list + add file + open file
|
|
||||||
- главный пользовательский поток работает
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Wails app init (Go backend + Svelte/Vue)
|
|
||||||
- Backend bindings: NodeService, VaultService, NoteService
|
|
||||||
- Frontend: sidebar tree, main panel, modals, markdown editor
|
|
||||||
- Поток: дело → заметка → файл → открыть файл
|
|
||||||
|
|
||||||
**Commit:** `step 6: Wails GUI MVP`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 7 — Actions
|
|
||||||
|
|
||||||
**Цель:** можно создать кнопку "Открыть сайт", нажать, сайт открылся.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- open_url, open_file, open_folder, run_command
|
|
||||||
- confirm_required диалог
|
|
||||||
- action log
|
|
||||||
- GUI вкладка "Действия"
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Таблица actions
|
|
||||||
- ActionService: Run с confirm, exec.Command БЕЗ shell, args массивом
|
|
||||||
- CLI: `action add`, `action list`, `action run`
|
|
||||||
- GUI: вкладка с кнопками
|
|
||||||
|
|
||||||
**Commit:** `step 7: actions`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 8 — Worklog
|
|
||||||
|
|
||||||
**Цель:** можно записать "3ч обновил витрину", скопировать отчёт.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- add/edit/delete entry
|
|
||||||
- approximate minutes + billable flag
|
|
||||||
- copy report копирует в буфер
|
|
||||||
- GUI вкладка "Журнал"
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Таблица worklog_entries
|
|
||||||
- WorklogService: Add, Edit, Delete, CopyReport
|
|
||||||
- CLI: `worklog add`, `worklog list`, `worklog report`
|
|
||||||
- GUI: вкладка журнал + кнопка copy report
|
|
||||||
|
|
||||||
**Commit:** `step 8: worklog`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 9 — FTS5 Search
|
|
||||||
|
|
||||||
**Цель:** можно найти "витрину" по заметкам, файлам, журналу.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- `verstak index rebuild` перестраивает индекс
|
|
||||||
- поиск по node titles, note content, filenames, worklog summaries
|
|
||||||
- GUI search bar + результаты с type/path
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Таблица search_index (FTS5): node_id, title, content, path, tags, type
|
|
||||||
- Триггеры для автоматического обновления или manual rebuild
|
|
||||||
- SearchService: RebuildIndex, Search(query)
|
|
||||||
- CLI: `index rebuild`
|
|
||||||
- GUI: search bar в header
|
|
||||||
|
|
||||||
**Commit:** `step 9: FTS5 search`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 10 — Система плагинов (Lua + шаблоны дел)
|
|
||||||
|
|
||||||
**Цель:** можно положить Lua-скрипт в `.verstak/plugins/` — и он работает.
|
|
||||||
Без перекомпиляции программы.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- `.verstak/plugins/<name>/plugin.json` — мета
|
|
||||||
- `main.lua` — загрузка через gopher-lua
|
|
||||||
- `on_init`, `on_vault_open`, `on_node_create` хуки
|
|
||||||
- `verstak.node.register_type()` — новые типы дел
|
|
||||||
- `verstak.http.route()` — API для GUI
|
|
||||||
- шаблоны дела (JSON) → предзаполненное дерево
|
|
||||||
- песочница: нет io/os.execute, только API
|
|
||||||
- CLI: `verstak plugin list / install / enable`
|
|
||||||
- DokuWiki импортер — пример плагина в `contrib/plugins/importer-dokuwiki/`
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- `internal/core/plugins/manager.go` — сканирование, загрузка, валидация
|
|
||||||
- Lua runtime (gopher-lua) с песочницей
|
|
||||||
- Plugin API: node, config, activity, http, ui, vault
|
|
||||||
- Миграции плагинов (SQL)
|
|
||||||
- Реестр типов дел → GUI рендерит разные карточки
|
|
||||||
- CLI: plugin list/install/enable
|
|
||||||
- Базовый шаблон дела (client.json)
|
|
||||||
- Пример плагина: DokuWiki importerв `contrib/`
|
|
||||||
|
|
||||||
**Commit:** `step 10: plugins system`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 11 — Sync Server Skeleton
|
|
||||||
|
|
||||||
**Цель:** verstak-server отвечает на /health, /sync/push, /sync/pull, /blobs.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- HTTP server на отдельном порту
|
|
||||||
- API key auth
|
|
||||||
- blob storage by sha256
|
|
||||||
- GET /health, POST /sync/push, POST /sync/pull, POST /blobs/upload, GET /blobs/{sha256}
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- `cmd/verstak-server/main.go`
|
|
||||||
- SQLite server db
|
|
||||||
- Push/pull operations endpoints
|
|
||||||
- Blob upload/download with sha256 naming
|
|
||||||
|
|
||||||
**Commit:** `step 11: sync server skeleton`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 12 — Sync Client MVP
|
|
||||||
|
|
||||||
**Цель:** `verstak sync` отправляет локальные операции на сервер и получает обратно.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- sync_ops таблица
|
|
||||||
- операции создаются при каждом изменении
|
|
||||||
- push local ops + pull remote ops
|
|
||||||
- upload/download blobs
|
|
||||||
- conflict copy при неуверенности
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Таблица sync_ops (опционально добавить триггеры в repository)
|
|
||||||
- SyncClient: Push, Pull, UploadBlob, DownloadBlob
|
|
||||||
- CLI: `verstak sync`
|
|
||||||
- Server URL + API key в .verstak/config
|
|
||||||
|
|
||||||
**Commit:** `step 12: sync client MVP`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 13 — Activity + File Scanner/Watcher
|
|
||||||
|
|
||||||
**Цель:** фиксируется открытие/редактирование, scanner видит новые файлы.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- activity_events таблица
|
|
||||||
- scanner сравнивает реальность с SQLite
|
|
||||||
- watcher (fsnotify) ускоряет обнаружение
|
|
||||||
- экран "Активность" с группировкой по делу
|
|
||||||
- можно создать worklog из events
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- Таблица activity_events
|
|
||||||
- Запись событий из nodes/notes/files/actions
|
|
||||||
- Snapshot scanner (источник правды)
|
|
||||||
- fsnotify watcher (ускоритель)
|
|
||||||
- CLI: `scan`, `activity list`
|
|
||||||
- GUI: экран "Активность"
|
|
||||||
|
|
||||||
**Commit:** `step 13: activity + scanner/watcher`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 14 — TUI MVP (Bubble Tea)
|
|
||||||
|
|
||||||
**Цель:** быстрый поиск дела, добавление worklog, запуск action.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- fuzzy search tree
|
|
||||||
- open node
|
|
||||||
- add worklog
|
|
||||||
- run action
|
|
||||||
- sync now
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- `cmd/verstak-tui/main.go` с Bubble Tea
|
|
||||||
- Модели: search, node view, worklog form, action runner
|
|
||||||
- Не повторяет весь GUI — только быстрые действия
|
|
||||||
|
|
||||||
**Commit:** `step 14: TUI MVP`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 15 — Integrity Check + Repair + Vault Restore
|
|
||||||
|
|
||||||
**Цель:** `verstak vault check` находит проблемы, repair чинит, restore восстанавливает.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- check: missing files, orphan files, SQLite references, hash mismatch
|
|
||||||
- repair: устраняет найденные проблемы
|
|
||||||
- restore с сервера восстанавливает vault
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- CLI: `vault check` — сканирует и отчитывается
|
|
||||||
- CLI: `vault repair` — чинит найденное
|
|
||||||
- CLI: `restore --server <url> --api-key <key> --target <path>`
|
|
||||||
|
|
||||||
**Commit:** `step 15: integrity + restore`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ШАГ 16 — Система плагинов (Lua + шаблоны дел)
|
|
||||||
|
|
||||||
**Цель:** можно положить Lua-скрипт в `.verstak/plugins/` — и он работает.
|
|
||||||
|
|
||||||
**Acceptance:**
|
|
||||||
- `.verstak/plugins/<name>/plugin.json` — мета
|
|
||||||
- `main.lua` — загрузка через gopher-lua
|
|
||||||
- `on_init`, `on_vault_open`, `on_node_create` хуки
|
|
||||||
- `verstak.node.register_type()` — новые типы дел
|
|
||||||
- `verstak.http.route()` — API для GUI
|
|
||||||
- шаблоны дела (JSON) → предзаполненное дерево
|
|
||||||
- CLI: `verstak plugin list / install / enable`
|
|
||||||
|
|
||||||
**Действия:**
|
|
||||||
- `internal/core/plugins/manager.go` — сканирование, загрузка, валидация
|
|
||||||
- Lua runtime (gopher-lua) с песочницей
|
|
||||||
- Plugin API: node, config, activity, http, ui, vault
|
|
||||||
- Миграции плагинов (SQL)
|
|
||||||
- Реестр типов дел → GUI рендерит разные карточки
|
|
||||||
- CLI: plugin list/install/enable
|
|
||||||
- Базовый шаблон дела (client.json)
|
|
||||||
|
|
||||||
**Commit:** `step 10: plugins system`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -382,10 +311,19 @@ verstak/
|
|||||||
|
|
||||||
cmd/
|
cmd/
|
||||||
verstak/ # CLI
|
verstak/ # CLI
|
||||||
verstak-gui/ # Wails GUI
|
verstak-gui/ # Wails GUI main
|
||||||
verstak-tui/ # Bubble Tea TUI
|
verstak-tui/ # Bubble Tea TUI
|
||||||
verstak-server/ # Sync server
|
verstak-server/ # Sync server
|
||||||
|
|
||||||
|
frontend/ # Wails frontend
|
||||||
|
package.json
|
||||||
|
wails.json
|
||||||
|
src/
|
||||||
|
App.svelte
|
||||||
|
components/
|
||||||
|
stores/
|
||||||
|
styles/
|
||||||
|
|
||||||
internal/
|
internal/
|
||||||
core/
|
core/
|
||||||
nodes/
|
nodes/
|
||||||
@@ -400,9 +338,11 @@ verstak/
|
|||||||
sync/
|
sync/
|
||||||
security/
|
security/
|
||||||
config/
|
config/
|
||||||
plugins/
|
plugins/ # manager, lua (stub)
|
||||||
|
|
||||||
frontend/ # Wails frontend (Svelte/Vue)
|
contrib/
|
||||||
|
plugins/
|
||||||
|
importer-dokuwiki/
|
||||||
|
|
||||||
migrations/
|
migrations/
|
||||||
001_init.sql
|
001_init.sql
|
||||||
@@ -411,15 +351,11 @@ verstak/
|
|||||||
004_add_notes.sql
|
004_add_notes.sql
|
||||||
005_add_actions.sql
|
005_add_actions.sql
|
||||||
006_add_worklog.sql
|
006_add_worklog.sql
|
||||||
007_add_activity.sql
|
|
||||||
008_add_fts.sql
|
|
||||||
009_add_sync.sql
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## RAID (Risks, Assumptions, Issues, Dependencies)
|
## RAID
|
||||||
|
|
||||||
- **Критично:** Wails v3 может быть нестабилен — проверить перед шагом 6
|
- **Критично:** Wails требует Node.js для frontend-сборки
|
||||||
- **Критично:** go-sqlite3 нужен cgo; modernc — чистый Go, выбрать до шага 2
|
- **Критично:** go-sqlite3 + cgo; gcc уже установлен
|
||||||
- **Зависимость:** Шаги 12 (sync client) зависят от 11 (server)
|
- **Зависимость:** Steps 15+ ждут завершения step 14
|
||||||
- **Зависимость:** Шаг 6 (GUI) лучше откладывать до стабильности core
|
- **Риск:** Wails v3 может быть нестабилен — проверить перед шагом 11
|
||||||
- **Риск:** Svelte/Vue фронтенд потребует node/npm — подготовить
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//@ts-check
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
Object.freeze($Create.Events);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/wails.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Верстак</title>
|
||||||
|
<style>
|
||||||
|
/* Critical reset — no white borders, full viewport */
|
||||||
|
html, body, #app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #13131f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+1306
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build --mode production",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||||
|
"svelte": "^4.2.19",
|
||||||
|
"vite": "^5.4.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,157 @@
|
|||||||
|
: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");
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 3em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: none;
|
||||||
|
margin: 0 0 0 20px;
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 1rem;
|
||||||
|
align-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-box .btn:hover {
|
||||||
|
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box .input {
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: black;
|
||||||
|
background-color: rgba(240, 240, 240, 1);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box .input:hover {
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box .input:focus {
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,325 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
let sections = []
|
||||||
|
let nodes = []
|
||||||
|
let version = ''
|
||||||
|
let error = ''
|
||||||
|
let selectedSection = ''
|
||||||
|
let selectedNode = null
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
version = 'verstak-gui'
|
||||||
|
if (window.go && window.go.main && window.go.main.App) {
|
||||||
|
version = await window.go.main.App.VerstakVersion()
|
||||||
|
sections = await window.go.main.App.ListSections()
|
||||||
|
nodes = await window.go.main.App.ListRootNodes()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectSection(id) {
|
||||||
|
selectedSection = id
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectNode(node) {
|
||||||
|
selectedNode = node
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="app">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="sidebar-top">
|
||||||
|
<span class="logo">⚒</span>
|
||||||
|
<span class="app-name">Верстак</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="sidebar-nav">
|
||||||
|
<div class="nav-group">
|
||||||
|
<div class="nav-label">Разделы</div>
|
||||||
|
{#each sections as section}
|
||||||
|
<button class="nav-item {selectedSection === section.id ? 'selected' : ''}"
|
||||||
|
on:click={() => selectSection(section.id)}>
|
||||||
|
{section.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-group">
|
||||||
|
<div class="nav-label">Корневые дела</div>
|
||||||
|
{#each nodes as node}
|
||||||
|
<button class="nav-item {selectedNode && selectedNode.id === node.id ? 'selected' : ''}"
|
||||||
|
on:click={() => selectNode(node)}>
|
||||||
|
{node.title}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{#if nodes.length === 0 && sections.length > 0}
|
||||||
|
<div class="nav-empty">Нет дел</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="sidebar-bottom">
|
||||||
|
<span class="version">{version}</span>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main area -->
|
||||||
|
<main class="main">
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="header">
|
||||||
|
<div class="header-left">
|
||||||
|
{#if selectedNode}
|
||||||
|
<span class="crumb">{selectedNode.title}</span>
|
||||||
|
{:else if selectedSection}
|
||||||
|
<span class="crumb">{#each sections as section}{section.id === selectedSection ? section.label : ''}{/each}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="crumb placeholder">Выберите раздел или дело</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<!-- Search placeholder -->
|
||||||
|
<div class="search-hint">Поиск...</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Error banner -->
|
||||||
|
{#if error}
|
||||||
|
<div class="error-banner">
|
||||||
|
Wails bindings: {error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="content">
|
||||||
|
{#if selectedNode}
|
||||||
|
<div class="node-view">
|
||||||
|
<h2>{selectedNode.title}</h2>
|
||||||
|
<div class="node-meta">
|
||||||
|
<span>ID: {selectedNode.id}</span>
|
||||||
|
<span>Type: {selectedNode.type}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else if sections.length > 0}
|
||||||
|
<div class="welcome">
|
||||||
|
<h2>Верстак</h2>
|
||||||
|
<p>Разделы: {sections.length} · Дел: {nodes.length}</p>
|
||||||
|
{#if error}
|
||||||
|
<p class="error-text">Go bindings не подключены: {error}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="loading">Загрузка...</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Reset */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App shell — full viewport */
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #13131f;
|
||||||
|
color: #e4e4ef;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SIDEBAR ===== */
|
||||||
|
.sidebar {
|
||||||
|
width: 260px;
|
||||||
|
min-width: 200px;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #1a1a28;
|
||||||
|
border-right: 1px solid #2a2a3c;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-top {
|
||||||
|
padding: 16px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-bottom: 1px solid #2a2a3c;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e4e4ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-label {
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: #666;
|
||||||
|
padding: 4px 20px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: #222233;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.selected {
|
||||||
|
background: #2a2a4a;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-empty {
|
||||||
|
padding: 8px 20px;
|
||||||
|
color: #555;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-bottom {
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-top: 1px solid #2a2a3c;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== MAIN AREA ===== */
|
||||||
|
.main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #13131f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-bottom: 1px solid #2a2a3c;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crumb {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #e4e4ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crumb.placeholder {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-hint {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: 1px solid #2a2a3c;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-banner {
|
||||||
|
background: #3a2222;
|
||||||
|
color: #ff8888;
|
||||||
|
padding: 8px 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid #4a2222;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CONTENT ===== */
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome h2 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #8888a4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #ff8888;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-view h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Wails v2 API wrapper — single frontend access point to Go backend
|
||||||
|
|
||||||
|
function wailsCall(method, ...args) {
|
||||||
|
if (window.go && window.go.main && window.go.main.App) {
|
||||||
|
return window.go.main.App[method](...args)
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error('Wails bindings not loaded'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
export const listSections = () => wailsCall('ListSections')
|
||||||
|
|
||||||
|
// Nodes
|
||||||
|
export const listNodesBySection = (section) => wailsCall('ListNodesBySection', section)
|
||||||
|
export const listChildren = (parentID) => wailsCall('ListChildren', parentID)
|
||||||
|
export const getNodeDetail = (id) => wailsCall('GetNodeDetail', id)
|
||||||
|
export const createNode = (parentID, type, title, section) =>
|
||||||
|
wailsCall('CreateNode', parentID, type, title, section)
|
||||||
|
export const deleteNode = (id) => wailsCall('DeleteNode', id)
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
export const listNotes = (nodeID) => wailsCall('ListNotes', nodeID)
|
||||||
|
export const createNote = (parentID, title) => wailsCall('CreateNote', parentID, title)
|
||||||
|
export const readNote = (noteID) => wailsCall('ReadNote', noteID)
|
||||||
|
export const saveNote = (noteID, content) => wailsCall('SaveNote', noteID, content)
|
||||||
|
|
||||||
|
// Files
|
||||||
|
export const listFiles = (nodeID) => wailsCall('ListFiles', nodeID)
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
export const listActions = (nodeID) => wailsCall('ListActions', nodeID)
|
||||||
|
export const runAction = (id) => wailsCall('RunAction', id)
|
||||||
|
|
||||||
|
// Worklog
|
||||||
|
export const listWorklog = (nodeID) => wailsCall('ListWorklog', nodeID)
|
||||||
|
export const createWorklog = (nodeID, summary, minutes) =>
|
||||||
|
wailsCall('CreateWorklog', nodeID, summary, minutes)
|
||||||
|
|
||||||
|
// Search
|
||||||
|
export const search = (query) => wailsCall('Search', query)
|
||||||
|
|
||||||
|
// System
|
||||||
|
export const verstakVersion = () => wailsCall('VerstakVersion')
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import App from './App.svelte'
|
||||||
|
|
||||||
|
new App({
|
||||||
|
target: document.getElementById('app')
|
||||||
|
})
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* This file tells your IDE where the root of your JavaScript project is, and sets some
|
||||||
|
* options that it can use to provide autocompletion and other features.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
/**
|
||||||
|
* The target and module can be set to ESNext to allow writing modern JavaScript,
|
||||||
|
* and Vite will compile down to the level of "build.target" specified in the vite config file.
|
||||||
|
* Builds will error if you use a feature that cannot be compiled down to the target level.
|
||||||
|
*/
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
* Enable checkJs if you'd like type checking in `.svelte` and `.js` files.
|
||||||
|
*/
|
||||||
|
"checkJs": false,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "bindings"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 3001,
|
||||||
|
strictPort: true,
|
||||||
|
},
|
||||||
|
plugins: [svelte()],
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,8 +1,40 @@
|
|||||||
module verstak
|
module verstak
|
||||||
|
|
||||||
go 1.22
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.44 // indirect
|
github.com/mattn/go-sqlite3 v1.14.44
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
github.com/wailsapp/wails/v2 v2.12.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
|
||||||
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||||
|
github.com/labstack/echo/v4 v4.13.3 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
|
github.com/leaanthony/gosod v1.0.4 // indirect
|
||||||
|
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||||
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // 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/tkrajina/go-reflector v0.5.8 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.22 // indirect
|
||||||
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,92 @@
|
|||||||
|
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/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||||
|
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||||
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
|
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
|
||||||
|
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||||
|
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
|
||||||
|
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
|
||||||
|
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
|
||||||
|
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
|
||||||
|
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||||
|
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||||
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
|
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
|
||||||
|
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.22/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/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c=
|
||||||
|
github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg=
|
||||||
|
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/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user