Skip to content

Commit

Permalink
Pasting files kind of works
Browse files Browse the repository at this point in the history
Missing highlighting and some CSS (which comes from the Pygments CSS
currently), plus all the diff functionality.
  • Loading branch information
chriskuehl committed Nov 16, 2024
1 parent faf1467 commit eeb52ce
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 49 deletions.
5 changes: 5 additions & 0 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
* Make file size accessible on StoredObject
* Add a way to create sub-loggers to the Logger interface, update uploads/uploads.go to use it
* maybe works based on context instead of actual sub-logger?
* Go through and clean up messy tab/space indentation in templates
* Pastes
* Missing CSS (comes from pygments bundle)
* Syntax highlighting
* Markdown
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/chriskuehl/fluffy

go 1.23.0
go 1.23.3

require (
github.com/BurntSushi/toml v1.4.0
Expand Down
13 changes: 12 additions & 1 deletion server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,18 @@ type Templates struct {
}

func (t *Templates) Must(name string) *template.Template {
return template.Must(template.New("").ParseFS(t.FS, "templates/include/*.html", "templates/"+name))
funcs := template.FuncMap{
"plusOne": func(i int) int {
return i + 1
},
"pluralize": func(singular string, count int) string {
if count == 1 {
return singular
}
return singular + "s"
},
}
return template.Must(template.New("").Funcs(funcs).ParseFS(t.FS, "templates/include/*.html", "templates/"+name))
}

type Config struct {
Expand Down
64 changes: 64 additions & 0 deletions server/highlighting/highlighting.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package highlighting

import (
"html/template"
"strings"
)

var UILanguagesMap = map[string]string{
"bash": "Bash / Shell",
"c": "C",
Expand All @@ -25,3 +30,62 @@ var UILanguagesMap = map[string]string{
"swift": "Swift",
"yaml": "YAML",
}

type StyleCategory struct {
Name string
Styles []Style
}

type Style struct {
Name string
}

var Styles = []StyleCategory{
{
Name: "Light",
Styles: []Style{
{Name: "default"},
{Name: "pastie"},
},
},
{
Name: "Dark",
Styles: []Style{
{Name: "monokai"},
{Name: "solarized-dark"},
},
},
}

type Highlighter interface {
Name() string
IsDiff() bool
Highlight(text *Text) (template.HTML, error)
}

type PlainTextHighlighter struct{}

func (p *PlainTextHighlighter) Name() string {
return "Plain Text"
}

func (p *PlainTextHighlighter) IsDiff() bool {
return false
}

func (p *PlainTextHighlighter) Highlight(text *Text) (template.HTML, error) {
var html strings.Builder
for _, line := range strings.Split(text.Text, "\n") {
html.WriteString(template.HTMLEscapeString(line))
html.WriteString("<br />")
}
return template.HTML(html.String()), nil
}

type Text struct {
Text string
// Array index corresponds to zero-indexed line number in this text,
// and the value is the array of zero-indexed line numbers that line
// corresponds to in the original text.
LineNumberMapping [][]int
}
69 changes: 29 additions & 40 deletions server/templates/paste.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,13 @@

{{define "extraToolbar"}}
<select id="style">
{% for category, styles in styles.items()|sort %}
<optgroup label="{{category}}">
{% for style in styles %}
<option
value="{{style.name}}"
{% if style.name == defaultStyle %}
selected="selected"
{% endif %}
>{{style.name}}</option>
{% endfor %}
{{range $category := .Styles}}
<optgroup label="{{$category.Name}}">
{{range $style := $category.Styles}}
<option value="{{$style.Name}}" {{if eq $style.Name $.DefaultStyle}}selected="selected"{{end}}>{{$style}}</option>
{{end}}
</optgroup>
{% endfor %}
{{end}}
</select>

<script>
Expand All @@ -33,16 +28,17 @@
}
</script>

{% if highlighter.is_diff %}
{{if .Highlighter.IsDiff}}
<div class="pill-buttons" id="diff-setting">
<div class="option selected" data-value="side-by-side">Side-by-Side</div>
<div class="option not-selected" data-value="unified">Unified</div>
</div>

{#
{{/*
This is an inline script so that it can apply the right CSS before
rendering the diff and avoid flashing the wrong diff style on load.
#}
*/}}

<script>
const PREFERRED_DIFF_SETTING = 'preferredDiffSetting';

Expand Down Expand Up @@ -77,38 +73,31 @@
child.onclick = () => updateDiffSetting(child.getAttribute('data-value'))
}
</script>
{% endif %}
{{end}}
{{end}}

{{define "text"}}
{{/*
{% for text in texts %}
<div class="text-container">
<div class="line-numbers">
{% for i in range(1, num_lines(text.text) + 1) %}
{% if text.line_number_mapping %}
{% set line_numbers = text.line_number_mapping[i] %}
{% else %}
{% set line_numbers = [i] %}
{% endif %}
<a class="LL{{line_numbers|join(' LL')}}">{{i}}</a>
{% endfor %}
</div>
<div class="text" contenteditable="true" spellcheck="false">
{{highlighter.highlight(text.text, text.line_number_mapping)|safe}}
</div>
</div>
{% endfor %}
*/}}
{{range $text := .Texts}}
<div class="text-container">
<div class="line-numbers">
{{range $i, $numbers := $text.LineNumberMapping}}
<a class="{{range $numbers}}LL{{plusOne .}} {{end}}">{{plusOne $i}}</a>
{{end}}
</div>
<div class="text" contenteditable="true" spellcheck="false">
{{$.Highlighter.Highlight $text}}
</div>
</div>
{{end}}
{{end}}

{{define "info"}}
{{/*
{% if texts|length == 1 %}
{{num_lines(texts[0].text)}} {{'line'|pluralize(num_lines(texts[0].text))}} of
{% endif %}
{{highlighter.name}}
*/}}
{{if eq (len .Texts) 1}}
{{plusOne (len (index .Texts 0).LineNumberMapping)}}
{{pluralize "line" (len (index .Texts 0).LineNumberMapping)}}
of
{{end}}
{{.Highlighter.Name}}
{{end}}

{{template "text.html" .}}
45 changes: 38 additions & 7 deletions server/views/uploads.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/chriskuehl/fluffy/server/config"
"github.com/chriskuehl/fluffy/server/highlighting"
"github.com/chriskuehl/fluffy/server/logging"
"github.com/chriskuehl/fluffy/server/meta"
"github.com/chriskuehl/fluffy/server/storage"
Expand Down Expand Up @@ -262,28 +263,41 @@ func HandleUpload(conf *config.Config, logger logging.Logger) http.HandlerFunc {
}

func normalizeFormText(text string) string {
return strings.Replace(text, "\r\n", "\n", -1)
return strings.ReplaceAll(text, "\r\n", "\n")
}

func HandlePaste(conf *config.Config, logger logging.Logger) http.HandlerFunc {
pasteTmpl := conf.Templates.Must("paste.html")

return func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
logger.Error(r.Context(), "parsing multipart form", "error", err)
userError{http.StatusBadRequest, "Could not parse multipart form."}.output(w)
return
if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") {
// cli versions 2.0.0 through 2.2.0 send multipart/form-data, so we need to support it forever.
err := r.ParseMultipartForm(conf.MaxMultipartMemoryBytes)
if err != nil {
logger.Error(r.Context(), "parsing multipart form", "error", err)
userError{http.StatusBadRequest, "Could not parse multipart form."}.output(w)
return
}
} else {
err := r.ParseForm()
if err != nil {
logger.Error(r.Context(), "parsing form", "error", err)
userError{http.StatusBadRequest, "Could not parse form."}.output(w)
return
}
}

fmt.Printf("r.Headers: %v\n", r.Header["Content-Type"])
fmt.Printf("r.Form: %v\n", r.Form)
fmt.Printf("r.PostForm: %v\n", r.PostForm)

_, jsonResponse := r.URL.Query()["json"]
if _, ok := r.Form["json"]; ok {
jsonResponse = true
}
fmt.Printf("jsonResponse: %v\n", jsonResponse)

text := normalizeFormText(r.Form.Get("text"))
fmt.Printf("text: %q\n", text)

// Raw paste
rawKey, err := uploads.GenUniqueObjectKey()
Expand Down Expand Up @@ -319,6 +333,12 @@ func HandlePaste(conf *config.Config, logger logging.Logger) http.HandlerFunc {

var paste bytes.Buffer

// TODO: calculate this properly
mapping := make([][]int, 0)
for i := 0; i < len(strings.Split(text, "\n")); i++ {
mapping = append(mapping, []int{i})
}

pasteData := struct {
Meta *meta.Meta
// localStorage variable name for preferred style (either "preferredStyle" or
Expand All @@ -328,13 +348,24 @@ func HandlePaste(conf *config.Config, logger logging.Logger) http.HandlerFunc {
DefaultStyle string
CopyAndEditText string
RawURL string
Styles []highlighting.StyleCategory
Highlighter highlighting.Highlighter
Texts []*highlighting.Text
}{
Meta: pasteMeta,
PreferredStyleVar: "preferredStyle",
DefaultStyle: "default",
// TODO: does this need any transformation?
CopyAndEditText: text,
RawURL: conf.FileURL(rawFile.Key()).String(),
Styles: highlighting.Styles,
Highlighter: &highlighting.PlainTextHighlighter{},
Texts: []*highlighting.Text{
{
Text: text,
LineNumberMapping: mapping,
},
},
}

// Terminal output gets its own preferred theme setting since many people
Expand Down

0 comments on commit eeb52ce

Please sign in to comment.