Compare commits
No commits in common. "b4010a5a24b51554434f599bee4a110e79b8bd23" and "ee503c338f3efbe4e8fcca72bd6343c06dab24d0" have entirely different histories.
b4010a5a24
...
ee503c338f
|
|
@ -1,383 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
.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}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"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/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() {
|
|
||||||
vaultPath := "."
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
vaultPath = os.Args[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
abs, err := filepath.Abs(vaultPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbPath := filepath.Join(abs, ".verstak", "index.db")
|
|
||||||
db, err := storage.Open(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Open vault: %v", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Init core services
|
|
||||||
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 {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
32
docs/PLAN.md
32
docs/PLAN.md
|
|
@ -21,8 +21,8 @@
|
||||||
| 8 | FTS5 Search | ✅ выполнен |
|
| 8 | FTS5 Search | ✅ выполнен |
|
||||||
| 9 | Section assignment + Sidebar filtering | ✅ выполнен |
|
| 9 | Section assignment + Sidebar filtering | ✅ выполнен |
|
||||||
| 10 | Plugin Manager (discovery + templates) | ✅ выполнен |
|
| 10 | Plugin Manager (discovery + templates) | ✅ выполнен |
|
||||||
| 11 | **Wails Desktop GUI** | 🔄 Wails v2 vertical MVP |
|
| 11 | **Wails Desktop GUI** | 🔄 в процессе — Wails v2 stable |
|
||||||
| 12 | **Files/Folders full workflow** | ⬜ следующий этап после vertical MVP |
|
| 12 | **Files/Folders full workflow** | ⬜ не начат |
|
||||||
| 13 | **Drag-and-drop** | ⬜ не начат |
|
| 13 | **Drag-and-drop** | ⬜ не начат |
|
||||||
| 14 | **MVP stabilization** | ⬜ не начат |
|
| 14 | **MVP stabilization** | ⬜ не начат |
|
||||||
| 15 | Sync Server Skeleton | 🔒 PAUSED |
|
| 15 | Sync Server Skeleton | 🔒 PAUSED |
|
||||||
|
|
@ -47,38 +47,10 @@ go build -tags "gui production webkit2_41" -o verstak-gui ./cmd/verstak-gui
|
||||||
./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)
|
Или для dev режима: `wails dev` (требует Wails v2 CLI)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Текущий этап: Wails v2 Vertical MVP
|
|
||||||
|
|
||||||
**Цель:** базовый рабочий desktop GUI для разделов → дел → заметок.
|
|
||||||
|
|
||||||
**Прогресс:**
|
|
||||||
- ✅ Wails v2 shell (window opens, no SIGSEGV)
|
|
||||||
- ✅ Layout fix (full viewport, dark theme, sidebar+main)
|
|
||||||
- 🔄 Notes bindings + UI
|
|
||||||
- 🔄 Tabs (Overview/Notes/Files/Actions/Worklog/Activity)
|
|
||||||
- 🔄 Node creation
|
|
||||||
- 🔄 Section filtering
|
|
||||||
|
|
||||||
**Пауза (не начинать до завершения vertical MVP):**
|
|
||||||
- Файлы/папки workflow
|
|
||||||
- Drag-and-drop
|
|
||||||
- Sync, plugins, Lua, browser extension, TUI
|
|
||||||
- Новые шаблоны, DokuWiki importer
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Выполненные шаги (1-10)
|
## Выполненные шаги (1-10)
|
||||||
|
|
||||||
### ШАГ 1 — Git Init + Skeleton
|
### ШАГ 1 — Git Init + Skeleton
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
// 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')
|
|
||||||
Loading…
Reference in New Issue