Skip to content

Commit eeb52ce

Browse files
committed
Pasting files kind of works
Missing highlighting and some CSS (which comes from the Pygments CSS currently), plus all the diff functionality.
1 parent faf1467 commit eeb52ce

File tree

6 files changed

+149
-49
lines changed

6 files changed

+149
-49
lines changed

TODO

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@
88
* Make file size accessible on StoredObject
99
* Add a way to create sub-loggers to the Logger interface, update uploads/uploads.go to use it
1010
* maybe works based on context instead of actual sub-logger?
11+
* Go through and clean up messy tab/space indentation in templates
12+
* Pastes
13+
* Missing CSS (comes from pygments bundle)
14+
* Syntax highlighting
15+
* Markdown

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/chriskuehl/fluffy
22

3-
go 1.23.0
3+
go 1.23.3
44

55
require (
66
github.com/BurntSushi/toml v1.4.0

server/config/config.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,18 @@ type Templates struct {
6161
}
6262

6363
func (t *Templates) Must(name string) *template.Template {
64-
return template.Must(template.New("").ParseFS(t.FS, "templates/include/*.html", "templates/"+name))
64+
funcs := template.FuncMap{
65+
"plusOne": func(i int) int {
66+
return i + 1
67+
},
68+
"pluralize": func(singular string, count int) string {
69+
if count == 1 {
70+
return singular
71+
}
72+
return singular + "s"
73+
},
74+
}
75+
return template.Must(template.New("").Funcs(funcs).ParseFS(t.FS, "templates/include/*.html", "templates/"+name))
6576
}
6677

6778
type Config struct {

server/highlighting/highlighting.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package highlighting
22

3+
import (
4+
"html/template"
5+
"strings"
6+
)
7+
38
var UILanguagesMap = map[string]string{
49
"bash": "Bash / Shell",
510
"c": "C",
@@ -25,3 +30,62 @@ var UILanguagesMap = map[string]string{
2530
"swift": "Swift",
2631
"yaml": "YAML",
2732
}
33+
34+
type StyleCategory struct {
35+
Name string
36+
Styles []Style
37+
}
38+
39+
type Style struct {
40+
Name string
41+
}
42+
43+
var Styles = []StyleCategory{
44+
{
45+
Name: "Light",
46+
Styles: []Style{
47+
{Name: "default"},
48+
{Name: "pastie"},
49+
},
50+
},
51+
{
52+
Name: "Dark",
53+
Styles: []Style{
54+
{Name: "monokai"},
55+
{Name: "solarized-dark"},
56+
},
57+
},
58+
}
59+
60+
type Highlighter interface {
61+
Name() string
62+
IsDiff() bool
63+
Highlight(text *Text) (template.HTML, error)
64+
}
65+
66+
type PlainTextHighlighter struct{}
67+
68+
func (p *PlainTextHighlighter) Name() string {
69+
return "Plain Text"
70+
}
71+
72+
func (p *PlainTextHighlighter) IsDiff() bool {
73+
return false
74+
}
75+
76+
func (p *PlainTextHighlighter) Highlight(text *Text) (template.HTML, error) {
77+
var html strings.Builder
78+
for _, line := range strings.Split(text.Text, "\n") {
79+
html.WriteString(template.HTMLEscapeString(line))
80+
html.WriteString("<br />")
81+
}
82+
return template.HTML(html.String()), nil
83+
}
84+
85+
type Text struct {
86+
Text string
87+
// Array index corresponds to zero-indexed line number in this text,
88+
// and the value is the array of zero-indexed line numbers that line
89+
// corresponds to in the original text.
90+
LineNumberMapping [][]int
91+
}

server/templates/paste.html

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,13 @@
1313

1414
{{define "extraToolbar"}}
1515
<select id="style">
16-
{% for category, styles in styles.items()|sort %}
17-
<optgroup label="{{category}}">
18-
{% for style in styles %}
19-
<option
20-
value="{{style.name}}"
21-
{% if style.name == defaultStyle %}
22-
selected="selected"
23-
{% endif %}
24-
>{{style.name}}</option>
25-
{% endfor %}
16+
{{range $category := .Styles}}
17+
<optgroup label="{{$category.Name}}">
18+
{{range $style := $category.Styles}}
19+
<option value="{{$style.Name}}" {{if eq $style.Name $.DefaultStyle}}selected="selected"{{end}}>{{$style}}</option>
20+
{{end}}
2621
</optgroup>
27-
{% endfor %}
22+
{{end}}
2823
</select>
2924

3025
<script>
@@ -33,16 +28,17 @@
3328
}
3429
</script>
3530

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

42-
{#
37+
{{/*
4338
This is an inline script so that it can apply the right CSS before
4439
rendering the diff and avoid flashing the wrong diff style on load.
45-
#}
40+
*/}}
41+
4642
<script>
4743
const PREFERRED_DIFF_SETTING = 'preferredDiffSetting';
4844

@@ -77,38 +73,31 @@
7773
child.onclick = () => updateDiffSetting(child.getAttribute('data-value'))
7874
}
7975
</script>
80-
{% endif %}
76+
{{end}}
8177
{{end}}
8278

8379
{{define "text"}}
84-
{{/*
85-
{% for text in texts %}
86-
<div class="text-container">
87-
<div class="line-numbers">
88-
{% for i in range(1, num_lines(text.text) + 1) %}
89-
{% if text.line_number_mapping %}
90-
{% set line_numbers = text.line_number_mapping[i] %}
91-
{% else %}
92-
{% set line_numbers = [i] %}
93-
{% endif %}
94-
<a class="LL{{line_numbers|join(' LL')}}">{{i}}</a>
95-
{% endfor %}
96-
</div>
97-
<div class="text" contenteditable="true" spellcheck="false">
98-
{{highlighter.highlight(text.text, text.line_number_mapping)|safe}}
99-
</div>
100-
</div>
101-
{% endfor %}
102-
*/}}
80+
{{range $text := .Texts}}
81+
<div class="text-container">
82+
<div class="line-numbers">
83+
{{range $i, $numbers := $text.LineNumberMapping}}
84+
<a class="{{range $numbers}}LL{{plusOne .}} {{end}}">{{plusOne $i}}</a>
85+
{{end}}
86+
</div>
87+
<div class="text" contenteditable="true" spellcheck="false">
88+
{{$.Highlighter.Highlight $text}}
89+
</div>
90+
</div>
91+
{{end}}
10392
{{end}}
10493

10594
{{define "info"}}
106-
{{/*
107-
{% if texts|length == 1 %}
108-
{{num_lines(texts[0].text)}} {{'line'|pluralize(num_lines(texts[0].text))}} of
109-
{% endif %}
110-
{{highlighter.name}}
111-
*/}}
95+
{{if eq (len .Texts) 1}}
96+
{{plusOne (len (index .Texts 0).LineNumberMapping)}}
97+
{{pluralize "line" (len (index .Texts 0).LineNumberMapping)}}
98+
of
99+
{{end}}
100+
{{.Highlighter.Name}}
112101
{{end}}
113102

114103
{{template "text.html" .}}

server/views/uploads.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212

1313
"github.com/chriskuehl/fluffy/server/config"
14+
"github.com/chriskuehl/fluffy/server/highlighting"
1415
"github.com/chriskuehl/fluffy/server/logging"
1516
"github.com/chriskuehl/fluffy/server/meta"
1617
"github.com/chriskuehl/fluffy/server/storage"
@@ -262,28 +263,41 @@ func HandleUpload(conf *config.Config, logger logging.Logger) http.HandlerFunc {
262263
}
263264

264265
func normalizeFormText(text string) string {
265-
return strings.Replace(text, "\r\n", "\n", -1)
266+
return strings.ReplaceAll(text, "\r\n", "\n")
266267
}
267268

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

271272
return func(w http.ResponseWriter, r *http.Request) {
272-
err := r.ParseForm()
273-
if err != nil {
274-
logger.Error(r.Context(), "parsing multipart form", "error", err)
275-
userError{http.StatusBadRequest, "Could not parse multipart form."}.output(w)
276-
return
273+
if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") {
274+
// cli versions 2.0.0 through 2.2.0 send multipart/form-data, so we need to support it forever.
275+
err := r.ParseMultipartForm(conf.MaxMultipartMemoryBytes)
276+
if err != nil {
277+
logger.Error(r.Context(), "parsing multipart form", "error", err)
278+
userError{http.StatusBadRequest, "Could not parse multipart form."}.output(w)
279+
return
280+
}
281+
} else {
282+
err := r.ParseForm()
283+
if err != nil {
284+
logger.Error(r.Context(), "parsing form", "error", err)
285+
userError{http.StatusBadRequest, "Could not parse form."}.output(w)
286+
return
287+
}
277288
}
278289

290+
fmt.Printf("r.Headers: %v\n", r.Header["Content-Type"])
291+
fmt.Printf("r.Form: %v\n", r.Form)
292+
fmt.Printf("r.PostForm: %v\n", r.PostForm)
293+
279294
_, jsonResponse := r.URL.Query()["json"]
280295
if _, ok := r.Form["json"]; ok {
281296
jsonResponse = true
282297
}
283298
fmt.Printf("jsonResponse: %v\n", jsonResponse)
284299

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

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

320334
var paste bytes.Buffer
321335

336+
// TODO: calculate this properly
337+
mapping := make([][]int, 0)
338+
for i := 0; i < len(strings.Split(text, "\n")); i++ {
339+
mapping = append(mapping, []int{i})
340+
}
341+
322342
pasteData := struct {
323343
Meta *meta.Meta
324344
// localStorage variable name for preferred style (either "preferredStyle" or
@@ -328,13 +348,24 @@ func HandlePaste(conf *config.Config, logger logging.Logger) http.HandlerFunc {
328348
DefaultStyle string
329349
CopyAndEditText string
330350
RawURL string
351+
Styles []highlighting.StyleCategory
352+
Highlighter highlighting.Highlighter
353+
Texts []*highlighting.Text
331354
}{
332355
Meta: pasteMeta,
333356
PreferredStyleVar: "preferredStyle",
334357
DefaultStyle: "default",
335358
// TODO: does this need any transformation?
336359
CopyAndEditText: text,
337360
RawURL: conf.FileURL(rawFile.Key()).String(),
361+
Styles: highlighting.Styles,
362+
Highlighter: &highlighting.PlainTextHighlighter{},
363+
Texts: []*highlighting.Text{
364+
{
365+
Text: text,
366+
LineNumberMapping: mapping,
367+
},
368+
},
338369
}
339370

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

0 commit comments

Comments
 (0)