Skip to content

Commit

Permalink
Add the option to tag a project to a post.
Browse files Browse the repository at this point in the history
This commit adds a command-line flag `projects_dir` which should point
to a folder containing project logs. When you select a project log from
the dropdown list on the edit page, the post is added to that project
log in addition to the full journal.

The full journal will show a `@project` tag at the start of the post.
  • Loading branch information
thijzert committed Feb 16, 2023
1 parent f5a6d1f commit 2502854
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 9 deletions.
10 changes: 7 additions & 3 deletions bin/journal-server/assets/js/autosave-draft.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const AUTOSAVE_INTERVAL_MS = 2500;
}

const ipt_body = editform.querySelector("textarea");
const ipt_project = editform.querySelector("#ipt-project") || document.createElement("input");
const ipt_draft_id = editform.querySelector("input[type=hidden][name=draft_id]");
if ( !ipt_body || !ipt_draft_id ) {
return;
Expand Down Expand Up @@ -34,6 +35,7 @@ const AUTOSAVE_INTERVAL_MS = 2500;
let pb = new FormData();
pb.set("draft_id", draft_id);
pb.set("body", ipt_body.value);
pb.set("project", ipt_project.value);
let draft_emptied = (ipt_body.value.trim() === '');
let q = await fetch(draft_url, {method: "POST", body: pb});
q = await q.json();
Expand All @@ -58,14 +60,16 @@ const AUTOSAVE_INTERVAL_MS = 2500;
};

let current_body = ipt_body.value;
let current_project = ipt_project.value;
window.setInterval(async () => {
if ( ipt_body.value == current_body ) {
if ( ipt_body.value == current_body && ipt_project.value == current_project ) {
return;
}

try {
await save_draft();
current_body = ipt_body.value;
await save_draft();
current_body = ipt_body.value;
current_project = ipt_project.value;
} catch ( e ) {
console.error("automatic save failed")
}
Expand Down
4 changes: 2 additions & 2 deletions bin/journal-server/assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ main

.journal-entry
{
input[type=text], textarea, input[type=submit], button {
input[type=text], textarea, input[type=submit], button, select {
width: 100%;
&:focus-visible {
outline: none;
Expand All @@ -61,7 +61,7 @@ main
padding-bottom: 120px;
}

input[type=text], input[type=submit] {
input[type=text], input[type=submit], select {
padding: 10px 20px;
}
}
Expand Down
10 changes: 10 additions & 0 deletions bin/journal-server/assets/templates/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@
<input type="hidden" id="ipt-draft-id" name="draft_id" />
<input type="submit" value="Save" />
</p>
{{if .Projects}}
<p>
<select type="text" id="ipt-project" name="project" placeholder="Project">
<option value="">Tag project...</option>
{{range .Projects}}
<option value="{{.}}">{{. | ProjectName}}</option>
{{end}}
</select>
</p>
{{end}}
{{if .CanAttachFiles}}
<ul id="list-of-attached-files"></ul>
<p>
Expand Down
58 changes: 54 additions & 4 deletions bin/journal-server/journal-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"os"
"os/signal"
"path"
"sort"
"strings"
"sync"
"time"
Expand All @@ -29,6 +30,7 @@ var (
password_file = flag.String("password_file", ".htpasswd", "File containing passwords")
secret_parameter = flag.String("secret_parameter", "apikey", "Parameter name containing the API key")
attachments_dir = flag.String("attachments_dir", "", "Directory for storing attached files")
projects_dir = flag.String("projects_dir", "", "Directory with project log files")
)

// DraftTimeout measures how long it takes for an unsaved draft to get added to the journal.
Expand All @@ -41,6 +43,7 @@ type draftEntry struct {
LastEdit time.Time
Expires time.Time
Body string
Project string
}

var (
Expand Down Expand Up @@ -138,7 +141,7 @@ func onShutdown() {
draftsMutex.Lock()
for draft_id, entry := range drafts {
log.Printf("Add draft ID %s to journal: last saved at %s", draft_id, entry.LastEdit)
err := saveJournalEntry(entry.LastEdit, entry.Body, false)
err := saveJournalEntry(entry.LastEdit, entry.Body, entry.Project, false)
if err != nil {
log.Printf("Error saving journal entry: %v", err)
}
Expand All @@ -165,7 +168,7 @@ func autoAddDrafts(ctx context.Context) {
}

log.Printf("Draft ID %s expired at %s; saving it to journal", draft_id, entry.Expires)
err := saveJournalEntry(entry.LastEdit, entry.Body, false)
err := saveJournalEntry(entry.LastEdit, entry.Body, entry.Project, false)
if err != nil {
log.Printf("Error saving journal entry: %v", err)
} else {
Expand Down Expand Up @@ -213,21 +216,53 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
executeTemplate(index, indexData, w, r)
}

func listProjects(ctx context.Context) ([]string, error) {
if *projects_dir == "" {
return nil, nil
}

d, err := os.Open(*projects_dir)
if err != nil {
return nil, err
}

fis, err := d.ReadDir(-1)
if err != nil {
return nil, err
}

var rv []string
for _, fi := range fis {
if fi.IsDir() {
continue
}
rv = append(rv, fi.Name())
}

sort.Strings(rv)

return rv, nil
}

func WriterHandler(w http.ResponseWriter, r *http.Request) {
getv := r.URL.Query()

getv.Del("success")
getv.Del("failure")

projects, _ := listProjects(r.Context())

pageData := struct {
Success, Failure bool
Callback string
CanAttachFiles bool
Projects []string
}{
r.URL.Query().Get("success") != "",
r.URL.Query().Get("failure") != "",
"journal?" + getv.Encode(),
*attachments_dir != "",
projects,
}

executeTemplate(editor, pageData, w, r)
Expand All @@ -251,7 +286,19 @@ func DailyHandler(w http.ResponseWriter, r *http.Request) {
executeTemplate(daily, pageData, w, r)
}

func saveJournalEntry(timestamp time.Time, contents string, starred bool) error {
func saveJournalEntry(timestamp time.Time, contents string, project string, starred bool) error {
if project != "" && *projects_dir != "" {
prf := path.Join(*projects_dir, strings.Replace(strings.Replace(project, "/", "", -1), "\\", "", -1))
//log.Printf("Also adding post to project %s → %s", project, prf)
if f, err := os.OpenFile(prf, os.O_APPEND|os.O_WRONLY, 0600); err == nil {
fmt.Fprintf(f, "\n=== %s ===\n%s\n", timestamp.Format("2006-01-02"), contents)
f.Close()
}
}
if project != "" {
contents = "@project " + formatProjectName(project) + "\n" + contents
}

e := &journal.Entry{
Date: timestamp,
Starred: starred,
Expand All @@ -265,6 +312,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
timestamp := journal.SmartTime(r.PostFormValue("ts"))
starred := r.PostFormValue("star") != ""
body := r.PostFormValue("body")
project := r.PostFormValue("project")

// Remove carriage returns entirely. Why? Because it fits my use case, and because sod MS-DOS.
body = strings.Replace(body, "\r", "", -1)
Expand Down Expand Up @@ -298,7 +346,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
}
}

err := saveJournalEntry(timestamp, body, starred)
err := saveJournalEntry(timestamp, body, project, starred)
if err != nil {
errorHandler(err, w, r)
return
Expand Down Expand Up @@ -337,6 +385,7 @@ func SaveDraftHandler(w http.ResponseWriter, r *http.Request) {
}

post_body := r.PostFormValue("body")
project := r.PostFormValue("project")

draftsMutex.Lock()
defer draftsMutex.Unlock()
Expand All @@ -347,6 +396,7 @@ func SaveDraftHandler(w http.ResponseWriter, r *http.Request) {
LastEdit: time.Now(),
Expires: time.Now().Add(DraftTimeout),
Body: post_body,
Project: project,
}
}

Expand Down
14 changes: 14 additions & 0 deletions bin/journal-server/plumbing.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"html/template"
"log"
"net/http"
"strings"

"github.com/gorilla/context"
)
Expand All @@ -16,10 +17,23 @@ var daily *template.Template
var bwvlist *template.Template
var tie *template.Template

func formatProjectName(name string) string {
if len(name) > 4 && name[len(name)-4:] == ".txt" {
name = name[:len(name)-5]
} else if len(name) > 5 && name[len(name)-5:] == ".wiki" {
name = name[:len(name)-5]
}

name = strings.Replace(name, "_", " ", -1)

return name
}

func init() {
flag.Parse()

funcs := template.FuncMap{}
funcs["ProjectName"] = formatProjectName

b, err := Asset("assets/templates/editor.html")
if err != nil {
Expand Down

0 comments on commit 2502854

Please sign in to comment.