Add standalone Go build alongside VSCode extension
Ports all five tools to a single 7.5 MB Windows exe with an embedded WebView2 window, file-based config, single-instance focus, and clean shutdown. Adds build.ps1 to build both targets from one command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
131
standalone/daily.go
Normal file
131
standalone/daily.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DailyPage(w http.ResponseWriter, r *http.Request) {
|
||||
cfg := LoadConfig()
|
||||
today := time.Now().Format("2006-01-02")
|
||||
lastProject := html.EscapeString(cfg.DailyUpdate.LastProject)
|
||||
|
||||
body := `
|
||||
<h2>Daily Update</h2>
|
||||
<div class="hint" style="margin-bottom:12px;">Submit copies the formatted text to your clipboard.</div>
|
||||
|
||||
<div style="max-width:520px;">
|
||||
<label style="display:block;margin-top:8px;font-size:12px;opacity:0.85;">Date</label>
|
||||
<input type="date" id="date" value="` + today + `" style="width:100%;" />
|
||||
|
||||
<label style="display:block;margin-top:12px;font-size:12px;opacity:0.85;">Status</label>
|
||||
<select id="status" style="width:100%;">
|
||||
<option>Started</option><option>Progress</option><option>Finished</option>
|
||||
<option>Client Feedback</option><option>For OT</option>
|
||||
</select>
|
||||
|
||||
<label style="display:block;margin-top:12px;font-size:12px;opacity:0.85;">Project</label>
|
||||
<input type="text" id="project" placeholder="Project name" value="` + lastProject + `" style="width:100%;" />
|
||||
|
||||
<label style="display:block;margin-top:12px;font-size:12px;opacity:0.85;">Remarks</label>
|
||||
<textarea id="remarks" placeholder="What did you do?" style="width:100%;min-height:96px;"></textarea>
|
||||
|
||||
<button id="submit" style="margin-top:16px;">Submit</button>
|
||||
<div id="status-msg" style="margin-top:12px;min-height:18px;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('submit').addEventListener('click', async () => {
|
||||
const payload = {
|
||||
date: document.getElementById('date').value,
|
||||
status: document.getElementById('status').value,
|
||||
project: document.getElementById('project').value.trim(),
|
||||
remarks: document.getElementById('remarks').value.trim(),
|
||||
};
|
||||
const statusEl = document.getElementById('status-msg');
|
||||
statusEl.textContent = 'Copying...';
|
||||
try {
|
||||
const res = await fetch('/api/daily/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const j = await res.json();
|
||||
if (j.ok) {
|
||||
statusEl.innerHTML = '<span class="ok">Copied to clipboard.</span>';
|
||||
} else {
|
||||
statusEl.innerHTML = '<span class="err">' + (j.error || 'Failed') + '</span>';
|
||||
}
|
||||
} catch (e) {
|
||||
statusEl.innerHTML = '<span class="err">' + (e.message || e) + '</span>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
`
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write([]byte(Page("/daily", "Daily Update", body)))
|
||||
}
|
||||
|
||||
type dailySubmitReq struct {
|
||||
Date string `json:"date"`
|
||||
Status string `json:"status"`
|
||||
Project string `json:"project"`
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
|
||||
func DailySubmit(w http.ResponseWriter, r *http.Request) {
|
||||
var req dailySubmitReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
text := formatDailyMessage(req)
|
||||
if err := CopyToClipboard(text); err != nil {
|
||||
writeJSON(w, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
cfg := LoadConfig()
|
||||
cfg.DailyUpdate.LastProject = req.Project
|
||||
_ = SaveConfig(cfg)
|
||||
writeJSON(w, map[string]any{"ok": true, "text": text})
|
||||
}
|
||||
|
||||
func formatDailyMessage(p dailySubmitReq) string {
|
||||
bullets := []string{}
|
||||
for _, line := range strings.Split(p.Remarks, "\n") {
|
||||
t := strings.TrimRight(line, "\r")
|
||||
t = regexp.MustCompile(`^\s*[-*]\s*`).ReplaceAllString(t, "")
|
||||
t = strings.TrimSpace(t)
|
||||
if t != "" {
|
||||
bullets = append(bullets, "- "+t)
|
||||
}
|
||||
}
|
||||
lines := []string{
|
||||
"Date: " + formatDailyDate(p.Date),
|
||||
"Status: " + p.Status,
|
||||
"Project: " + p.Project,
|
||||
"Remarks:",
|
||||
strings.Join(bullets, "\n"),
|
||||
}
|
||||
if p.Status == "For OT" {
|
||||
lines = append(lines, "@Joyce Lanot , @Reland Pigte, @Anthony Castor, @Angela Grace")
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func formatDailyDate(iso string) string {
|
||||
t, err := time.Parse("2006-01-02", iso)
|
||||
if err != nil {
|
||||
return iso
|
||||
}
|
||||
return t.Format("01/02/2006")
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
Reference in New Issue
Block a user