diff --git a/history/information.go b/history/information.go index bb20e6a..03d6aae 100644 --- a/history/information.go +++ b/history/information.go @@ -43,7 +43,9 @@ func Revisions(hyphaName string) ([]Revision, error) { ) if err == nil { for _, line := range strings.Split(out.String(), "\n") { - revs = append(revs, parseRevisionLine(line)) + if line != "" { + revs = append(revs, parseRevisionLine(line)) + } } } return revs, err diff --git a/history/operations.go b/history/operations.go index 3567d78..fecee81 100644 --- a/history/operations.go +++ b/history/operations.go @@ -15,6 +15,8 @@ const ( TypeNone OpType = iota TypeEditText TypeEditBinary + TypeDeleteHypha + TypeRenameHypha ) // HistoryOp is an object representing a history operation. @@ -46,6 +48,17 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp { return hop } +// WithFilesRemoved git-rm-s all passed `paths`. Paths can be rooted or not. Paths that are empty strings are ignored. +func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp { + args := []string{"rm", "--quiet", "--"} + for _, path := range paths { + if path != "" { + args = append(args, path) + } + } + return hop.gitop(args...) +} + // WithFiles stages all passed `paths`. Paths can be rooted or not. func (hop *HistoryOp) WithFiles(paths ...string) *HistoryOp { for i, path := range paths { diff --git a/http_mutators.go b/http_mutators.go index e3cd397..3557a87 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -17,6 +17,42 @@ func init() { http.HandleFunc("/upload-binary/", handlerUploadBinary) http.HandleFunc("/upload-text/", handlerUploadText) http.HandleFunc("/edit/", handlerEdit) + http.HandleFunc("/delete-ask/", handlerDeleteAsk) + http.HandleFunc("/delete-confirm/", handlerDeleteConfirm) +} + +// handlerDeleteAsk shows a delete dialog. +func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "delete-ask") + _, isOld = HyphaStorage[hyphaName] + ) + util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(hyphaName, isOld))) +} + +// handlerDeleteConfirm deletes a hypha for sure +func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + hyphaName = HyphaNameFromRq(rq, "delete-confirm") + hyphaData, isOld = HyphaStorage[hyphaName] + ) + if isOld { + // If deleted successfully + if hop := hyphaData.DeleteHypha(hyphaName); len(hop.Errs) == 0 { + http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) + } else { + HttpErr(w, http.StatusInternalServerError, hyphaName, + "Error: could not delete hypha", + fmt.Sprintf("Could not delete this hypha due to an internal error. Server errors: %v", hop.Errs)) + } + } else { + // The precondition is to have the hypha in the first place. + HttpErr(w, http.StatusPreconditionFailed, hyphaName, + "Error: no such hypha", + "Could not delete this hypha because it does not exist.") + } } // handlerEdit shows the edit form. It doesn't edit anything actually. diff --git a/http_readers.go b/http_readers.go index f288210..5843e4d 100644 --- a/http_readers.go +++ b/http_readers.go @@ -6,7 +6,6 @@ import ( "log" "net/http" "os" - "path" "strings" "github.com/bouncepaw/mycorrhiza/gemtext" @@ -29,8 +28,9 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var ( shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/") - revHash = path.Dir(shorterUrl) - hyphaName = CanonicalName(strings.TrimPrefix(shorterUrl, revHash+"/")) + firstSlashIndex = strings.IndexRune(shorterUrl, '/') + revHash = shorterUrl[:firstSlashIndex] + hyphaName = CanonicalName(shorterUrl[firstSlashIndex+1:]) contents = fmt.Sprintf(`

This hypha had no text at this revision.

`) textPath = hyphaName + "&.gmi" textContents, err = history.FileAtRevision(textPath, revHash) @@ -55,15 +55,15 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) hyphaName := HyphaNameFromRq(rq, "history") var tbody string - if _, ok := HyphaStorage[hyphaName]; ok { - revs, err := history.Revisions(hyphaName) - if err == nil { - for _, rev := range revs { - tbody += rev.AsHtmlTableRow(hyphaName) - } + + // History can be found for files that do not exist anymore. + revs, err := history.Revisions(hyphaName) + if err == nil { + for _, rev := range revs { + tbody += rev.AsHtmlTableRow(hyphaName) } - log.Println(revs) } + log.Println("Found", len(revs), "revisions for", hyphaName) util.HTTP200Page(w, base(hyphaName, templates.HistoryHTML(hyphaName, tbody))) diff --git a/hypha.go b/hypha.go index d3be116..7a37cb9 100644 --- a/hypha.go +++ b/hypha.go @@ -9,6 +9,7 @@ import ( "path/filepath" "github.com/bouncepaw/mycorrhiza/gemtext" + "github.com/bouncepaw/mycorrhiza/history" ) func init() { @@ -37,6 +38,15 @@ type HyphaData struct { binaryType BinaryType } +// DeleteHypha deletes hypha and makes a history record about that. +func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp { + return history.Operation(history.TypeDeleteHypha). + WithFilesRemoved(hd.textPath, hd.binaryPath). + WithMsg(fmt.Sprintf("Delete ā€˜%sā€™", hyphaName)). + WithSignature("anon"). + Apply() +} + // binaryHtmlBlock creates an html block for binary part of the hypha. func binaryHtmlBlock(hyphaName string, d *HyphaData) string { switch d.binaryType { diff --git a/main.go b/main.go index 5ef555d..981ab4b 100644 --- a/main.go +++ b/main.go @@ -120,7 +120,7 @@ func main() { http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static")))) // See http_readers.go for /page/, /text/, /binary/, /history/. - // See http_mutators.go for /upload-binary/, /upload-text/, /edit/. + // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/. http.HandleFunc("/list", handlerList) http.HandleFunc("/reindex", handlerReindex) http.HandleFunc("/random", handlerRandom) diff --git a/metarrhiza b/metarrhiza index 2c0e431..ecaa76f 160000 --- a/metarrhiza +++ b/metarrhiza @@ -1 +1 @@ -Subproject commit 2c0e43199ed28f7022a38463a0eec3af3ecb03c9 +Subproject commit ecaa76f841afcb3514b7061eb6708092bc17ee08 diff --git a/templates/http_delete.qtpl b/templates/http_delete.qtpl new file mode 100644 index 0000000..4044c09 --- /dev/null +++ b/templates/http_delete.qtpl @@ -0,0 +1,33 @@ +This dialog is to be shown to a user when they try to delete a hypha. +{% func DeleteAskHTML(hyphaName string, isOld bool) %} +
+ +{% if isOld %} +
+

Delete {%s hyphaName %}?

+

Do you really want to delete hypha {%s hyphaName %}?

+

In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.

+

Confirm

+

Cancel

+
+{% else %} + {%= cannotDeleteDueToNonExistence(hyphaName) %} +{% endif %} +
+{% endfunc %} + +{% func cannotDeleteDueToNonExistence(hyphaName string) %} +
+

Cannot delete {%s hyphaName %}

+

This hypha does not exist.

+

Go back

+
+{% endfunc %} diff --git a/templates/http_delete.qtpl.go b/templates/http_delete.qtpl.go new file mode 100644 index 0000000..297b5ec --- /dev/null +++ b/templates/http_delete.qtpl.go @@ -0,0 +1,171 @@ +// Code generated by qtc from "http_delete.qtpl". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +// This dialog is to be shown to a user when they try to delete a hypha. + +//line templates/http_delete.qtpl:2 +package templates + +//line templates/http_delete.qtpl:2 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line templates/http_delete.qtpl:2 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line templates/http_delete.qtpl:2 +func StreamDeleteAskHTML(qw422016 *qt422016.Writer, hyphaName string, isOld bool) { +//line templates/http_delete.qtpl:2 + qw422016.N().S(` +
+ +`) +//line templates/http_delete.qtpl:13 + if isOld { +//line templates/http_delete.qtpl:13 + qw422016.N().S(` +
+

Delete `) +//line templates/http_delete.qtpl:15 + qw422016.E().S(hyphaName) +//line templates/http_delete.qtpl:15 + qw422016.N().S(`?

+

Do you really want to delete hypha `) +//line templates/http_delete.qtpl:16 + qw422016.E().S(hyphaName) +//line templates/http_delete.qtpl:16 + qw422016.N().S(`?

+

In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.

+

Confirm

+

Cancel

+
+`) +//line templates/http_delete.qtpl:21 + } else { +//line templates/http_delete.qtpl:21 + qw422016.N().S(` + `) +//line templates/http_delete.qtpl:22 + streamcannotDeleteDueToNonExistence(qw422016, hyphaName) +//line templates/http_delete.qtpl:22 + qw422016.N().S(` +`) +//line templates/http_delete.qtpl:23 + } +//line templates/http_delete.qtpl:23 + qw422016.N().S(` +
+`) +//line templates/http_delete.qtpl:25 +} + +//line templates/http_delete.qtpl:25 +func WriteDeleteAskHTML(qq422016 qtio422016.Writer, hyphaName string, isOld bool) { +//line templates/http_delete.qtpl:25 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/http_delete.qtpl:25 + StreamDeleteAskHTML(qw422016, hyphaName, isOld) +//line templates/http_delete.qtpl:25 + qt422016.ReleaseWriter(qw422016) +//line templates/http_delete.qtpl:25 +} + +//line templates/http_delete.qtpl:25 +func DeleteAskHTML(hyphaName string, isOld bool) string { +//line templates/http_delete.qtpl:25 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/http_delete.qtpl:25 + WriteDeleteAskHTML(qb422016, hyphaName, isOld) +//line templates/http_delete.qtpl:25 + qs422016 := string(qb422016.B) +//line templates/http_delete.qtpl:25 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/http_delete.qtpl:25 + return qs422016 +//line templates/http_delete.qtpl:25 +} + +//line templates/http_delete.qtpl:27 +func streamcannotDeleteDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) { +//line templates/http_delete.qtpl:27 + qw422016.N().S(` +
+

Cannot delete `) +//line templates/http_delete.qtpl:29 + qw422016.E().S(hyphaName) +//line templates/http_delete.qtpl:29 + qw422016.N().S(`

+

This hypha does not exist.

+

Go back

+
+`) +//line templates/http_delete.qtpl:33 +} + +//line templates/http_delete.qtpl:33 +func writecannotDeleteDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) { +//line templates/http_delete.qtpl:33 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/http_delete.qtpl:33 + streamcannotDeleteDueToNonExistence(qw422016, hyphaName) +//line templates/http_delete.qtpl:33 + qt422016.ReleaseWriter(qw422016) +//line templates/http_delete.qtpl:33 +} + +//line templates/http_delete.qtpl:33 +func cannotDeleteDueToNonExistence(hyphaName string) string { +//line templates/http_delete.qtpl:33 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/http_delete.qtpl:33 + writecannotDeleteDueToNonExistence(qb422016, hyphaName) +//line templates/http_delete.qtpl:33 + qs422016 := string(qb422016.B) +//line templates/http_delete.qtpl:33 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/http_delete.qtpl:33 + return qs422016 +//line templates/http_delete.qtpl:33 +} diff --git a/templates/http_readers.qtpl b/templates/http_readers.qtpl index 0c3b4ea..cf85467 100644 --- a/templates/http_readers.qtpl +++ b/templates/http_readers.qtpl @@ -6,6 +6,7 @@
  • Edit
  • Raw text
  • History
  • +
  • Delete
  • @@ -32,6 +33,7 @@
  • Raw text
  • History
  • {%s revHash %}
  • +
  • Delete
  • @@ -55,6 +57,7 @@ If `contents` == "", a helpful message is shown instead.
  • Edit
  • Raw text
  • History
  • +
  • Delete
  • diff --git a/templates/http_readers.qtpl.go b/templates/http_readers.qtpl.go index 97d4a0b..fdccdf4 100644 --- a/templates/http_readers.qtpl.go +++ b/templates/http_readers.qtpl.go @@ -40,6 +40,11 @@ func StreamHistoryHTML(qw422016 *qt422016.Writer, hyphaName, tbody string) { //line templates/http_readers.qtpl:7 qw422016.N().S(`">Raw text
  • History
  • +
  • Delete
  • @@ -52,193 +57,203 @@ func StreamHistoryHTML(qw422016 *qt422016.Writer, hyphaName, tbody string) { `) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qw422016.N().S(tbody) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 } -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 func WriteHistoryHTML(qq422016 qtio422016.Writer, hyphaName, tbody string) { -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 StreamHistoryHTML(qw422016, hyphaName, tbody) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 } -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 func HistoryHTML(hyphaName, tbody string) string { -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 WriteHistoryHTML(qb422016, hyphaName, tbody) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 return qs422016 -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 } -//line templates/http_readers.qtpl:26 +//line templates/http_readers.qtpl:27 func StreamRevisionHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, tree, revHash string) { -//line templates/http_readers.qtpl:26 +//line templates/http_readers.qtpl:27 qw422016.N().S(`

    Please note that viewing binary parts of hyphae is not supported in history for now.

    `) -//line templates/http_readers.qtpl:39 +//line templates/http_readers.qtpl:41 qw422016.N().S(naviTitle) -//line templates/http_readers.qtpl:39 +//line templates/http_readers.qtpl:41 qw422016.N().S(` `) -//line templates/http_readers.qtpl:40 +//line templates/http_readers.qtpl:42 qw422016.N().S(contents) -//line templates/http_readers.qtpl:40 +//line templates/http_readers.qtpl:42 qw422016.N().S(`

    `) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 } -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 func WriteRevisionHTML(qq422016 qtio422016.Writer, hyphaName, naviTitle, contents, tree, revHash string) { -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 StreamRevisionHTML(qw422016, hyphaName, naviTitle, contents, tree, revHash) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 } -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 func RevisionHTML(hyphaName, naviTitle, contents, tree, revHash string) string { -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 WriteRevisionHTML(qb422016, hyphaName, naviTitle, contents, tree, revHash) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 return qs422016 -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:49 } // If `contents` == "", a helpful message is shown instead. -//line templates/http_readers.qtpl:50 +//line templates/http_readers.qtpl:52 func StreamPageHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, tree string) { -//line templates/http_readers.qtpl:50 +//line templates/http_readers.qtpl:52 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:61 +//line templates/http_readers.qtpl:64 qw422016.N().S(naviTitle) -//line templates/http_readers.qtpl:61 +//line templates/http_readers.qtpl:64 qw422016.N().S(` `) -//line templates/http_readers.qtpl:62 +//line templates/http_readers.qtpl:65 if contents == "" { -//line templates/http_readers.qtpl:62 +//line templates/http_readers.qtpl:65 qw422016.N().S(`

    This hypha has no text. Why not create it?

    `) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 } else { -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:67 qw422016.N().S(` `) -//line templates/http_readers.qtpl:65 +//line templates/http_readers.qtpl:68 qw422016.N().S(contents) -//line templates/http_readers.qtpl:65 +//line templates/http_readers.qtpl:68 qw422016.N().S(` `) -//line templates/http_readers.qtpl:66 +//line templates/http_readers.qtpl:69 } -//line templates/http_readers.qtpl:66 +//line templates/http_readers.qtpl:69 qw422016.N().S(`

    @@ -249,38 +264,38 @@ func StreamPageHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, t
    `) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 } -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 func WritePageHTML(qq422016 qtio422016.Writer, hyphaName, naviTitle, contents, tree string) { -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 StreamPageHTML(qw422016, hyphaName, naviTitle, contents, tree) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 } -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 func PageHTML(hyphaName, naviTitle, contents, tree string) string { -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 WritePageHTML(qb422016, hyphaName, naviTitle, contents, tree) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 return qs422016 -//line templates/http_readers.qtpl:81 +//line templates/http_readers.qtpl:84 }