diff --git a/api/routes/show.go b/api/routes/show.go index f0c1e70..694a680 100644 --- a/api/routes/show.go +++ b/api/routes/show.go @@ -1,13 +1,12 @@ package routes import ( - "fmt" - "log" - "strings" + "html/template" "time" "github.com/gofiber/fiber/v2" "github.com/medium.rip/pkg/client" + "github.com/medium.rip/pkg/converters" ) func show(c *fiber.Ctx) error { @@ -23,23 +22,15 @@ func show(c *fiber.Ctx) error { post := e.Data.Post publishDate := time.UnixMilli(e.Data.Post.CreatedAt) - log.Println(publishDate) - var sb strings.Builder - - for _, node := range post.Content.BodyModel.Paragraphs { - switch node.Type { - case "H3": - sb.WriteString(fmt.Sprintf("

%s

", node.Text)) - } - } + p := converters.ConvertParagraphs(post.Content.BodyModel.Paragraphs) return c.Render("show", fiber.Map { "Title": post.Title, "UserId": post.Creator.ID, "Author": post.Creator.Name, "PublishDate": publishDate.Format(time.DateOnly), - "Nodes": post.Content.BodyModel.Paragraphs, + "Paragraphs": template.HTML(p), }) } diff --git a/frontend/dist/index.html b/frontend/dist/index.html index b737067..9e661d4 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -6,7 +6,7 @@ {{ .Title }} - +

Hello, World!

diff --git a/frontend/dist/show.html b/frontend/dist/show.html index fa61c49..291653d 100644 --- a/frontend/dist/show.html +++ b/frontend/dist/show.html @@ -14,21 +14,14 @@ rel="stylesheet"> {{ .Title }} - + -
+

{{.Title}}

{{.Author}} on {{.PublishDate}}

- {{range .Nodes}} - {{if eq .Type "H3"}} -

{{.Text}}

- {{end}} - {{if eq .Type "IMG"}} - - {{end}} - {{end}} + {{.Paragraphs}}
diff --git a/frontend/show.html b/frontend/show.html index c417323..246aee7 100644 --- a/frontend/show.html +++ b/frontend/show.html @@ -17,17 +17,10 @@ -
+

{{.Title}}

{{.Author}} on {{.PublishDate}}

- {{range .Nodes}} - {{if eq .type "H3"}} -

.text

- {{end}} - {{if eq .type "IMG"}} -

.text

- {{end}} - {{end}} + {{.Paragraphs}}
diff --git a/pkg/converters/markup_converter.go b/pkg/converters/markup_converter.go index 88665f2..38ff7b5 100644 --- a/pkg/converters/markup_converter.go +++ b/pkg/converters/markup_converter.go @@ -9,20 +9,20 @@ import ( ) type RangeWithMarkup struct { - Range []int + Range []int Markups []entities.Markup } func unique(intSlice []int) []int { - keys := make(map[int]bool) - list := []int{} - for _, entry := range intSlice { - if _, value := keys[entry]; !value { - keys[entry] = true - list = append(list, entry) - } - } - return list + keys := make(map[int]bool) + list := []int{} + for _, entry := range intSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list } func ranges(text string, markups []entities.Markup) []RangeWithMarkup { @@ -54,14 +54,14 @@ func ranges(text string, markups []entities.Markup) []RangeWithMarkup { // check if this markup is covered by the range coveredMarkups := make([]entities.Markup, 0) for _, m := range markups { - if (int(m.Start) >= start && int(m.Start) < end) || (int(m.End - 1) >= start && int(m.End - 1) < end) { + if (int(m.Start) >= start && int(m.Start) < end) || (int(m.End-1) >= start && int(m.End-1) < end) { coveredMarkups = append(coveredMarkups, m) } } // append the range ranges = append(ranges, RangeWithMarkup{ - Range: []int{start, end}, + Range: []int{start, end}, Markups: coveredMarkups, }) } @@ -69,22 +69,22 @@ func ranges(text string, markups []entities.Markup) []RangeWithMarkup { return ranges } -func Convert(text string, markups []entities.Markup) string { +func ConvertMarkup(text string, markups []entities.Markup) string { var markedUp strings.Builder for _, r := range ranges(text, markups) { textToWrap := string(text[r.Range[0]:r.Range[1]]) - markedUp.WriteString(WrapInMarkups(textToWrap, r.Markups)) + markedUp.WriteString(wrapInMarkups(textToWrap, r.Markups)) } return markedUp.String() } -func WrapInMarkups(child string, markups []entities.Markup) string { +func wrapInMarkups(child string, markups []entities.Markup) string { if len(markups) == 0 { return child } markedUp := markupNodeInContainer(child, markups[0]) - return WrapInMarkups(markedUp, markups[1:]) + return wrapInMarkups(markedUp, markups[1:]) } func markupNodeInContainer(child string, markup entities.Markup) string { @@ -105,4 +105,4 @@ func markupNodeInContainer(child string, markup entities.Markup) string { return fmt.Sprintf(`%s`, child) } return child -} \ No newline at end of file +} diff --git a/pkg/converters/markup_converter_test.go b/pkg/converters/markup_converter_test.go index 69e26d5..3e0f5ec 100644 --- a/pkg/converters/markup_converter_test.go +++ b/pkg/converters/markup_converter_test.go @@ -58,7 +58,7 @@ func TestRanges(t *testing.T) { } func TestConvert(t *testing.T) { - markup := Convert("strong and emphasized only", []entities.Markup{ + markup := ConvertMarkup("strong and emphasized only", []entities.Markup{ { Type: "STRONG", Start: 0, @@ -74,4 +74,4 @@ func TestConvert(t *testing.T) { if markup != "strong and emphasized only" { t.Errorf("Expected markup to be strong and emphasized only, got %s", markup) } -} \ No newline at end of file +} diff --git a/pkg/converters/paragraph_converter.go b/pkg/converters/paragraph_converter.go new file mode 100644 index 0000000..7661e9a --- /dev/null +++ b/pkg/converters/paragraph_converter.go @@ -0,0 +1,144 @@ +package converters + +import ( + "fmt" + "strings" + + "github.com/medium.rip/pkg/entities" +) + +const IMAGE_HOST = "https://cdn-images-1.medium.com/fit/c" +const MAX_WIDTH = 800 +const FALLBACK_HEIGHT = 600 + +type Image struct { + ID string + OriginalHeight int64 + OriginalWidth int64 +} + +func (i *Image) Initialize(originalWidth *int64, originalHeight *int64) { + if originalWidth != nil { + i.OriginalWidth = *originalWidth + } else { + i.OriginalWidth = MAX_WIDTH + } + + if originalHeight != nil { + i.OriginalHeight = *originalHeight + } else { + i.OriginalHeight = FALLBACK_HEIGHT + } +} + +func (i *Image) width() int64 { + if i.OriginalWidth > MAX_WIDTH { + return MAX_WIDTH + } else { + return i.OriginalWidth + } +} + +func (i *Image) src() string { + return fmt.Sprintf("%s/%d/%d/%s", IMAGE_HOST, i.width(), i.height(), i.ID) +} + +func (i *Image) height() int64 { + if i.OriginalWidth > MAX_WIDTH { + return i.OriginalHeight * int64(i.ratio()) + } else { + return i.OriginalHeight + } +} + +func (i *Image) ratio() float32 { + return float32(MAX_WIDTH) / float32(i.OriginalWidth) +} + +func ConvertParagraphs(paragraphs []entities.Paragraph) string { + if len(paragraphs) == 0 { + return "" + } + + var ps strings.Builder + + for i, p := range paragraphs { + switch p.Type { + case "BQ", "MIXTAPE_EMBED", "PQ": + children := ConvertMarkup(p.Text, p.Markups) + ps.WriteString(fmt.Sprintf("

%s

", children)) + case "H2": + children := ConvertMarkup(p.Text, p.Markups) + if p.Name != "" { + ps.WriteString(fmt.Sprintf("

%s

", p.Name, children)) + } else { + ps.WriteString(fmt.Sprintf("

%s

", children)) + } + case "H3": + children := ConvertMarkup(p.Text, p.Markups) + if p.Name != "" { + ps.WriteString(fmt.Sprintf("

%s

", p.Name, children)) + } else { + ps.WriteString(fmt.Sprintf("

%s

", children)) + } + case "H4": + children := ConvertMarkup(p.Text, p.Markups) + if p.Name != "" { + ps.WriteString(fmt.Sprintf("

%s

", p.Name, children)) + } else { + ps.WriteString(fmt.Sprintf("

%s

", children)) + } + // TODO: handle IFRAME + case "IMG": + ps.WriteString(convertImg(p)) + case "OLI": + listItems := convertOli(paragraphs[i:]) + ps.WriteString(fmt.Sprintf("
    %s
", listItems)) + case "ULI": + listItems := convertUli(paragraphs[i:]) + ps.WriteString(fmt.Sprintf("", listItems)) + case "P": + children := ConvertMarkup(p.Text, p.Markups) + ps.WriteString(fmt.Sprintf("

%s

", children)) + case "PRE": + children := ConvertMarkup(p.Text, p.Markups) + ps.WriteString(fmt.Sprintf("
%s
", children)) + case "SECTION_CAPTION": + // unused + default: + } + } + + return ps.String() +} + +func convertImg(p entities.Paragraph) string { + if p.Metadata != nil { + captionMarkup := ConvertMarkup(p.Text, p.Markups) + img := Image{ID : p.Metadata.ID} + img.Initialize(&p.Metadata.OriginalWidth, &p.Metadata.OriginalHeight) + return fmt.Sprintf("
%s
", img.src(), img.width(), captionMarkup) + } else { + return "" + } +} + +func convertOli(ps []entities.Paragraph) string { + if len(ps) != 0 && ps[0].Type == "OLI" { + p := ps[0] + children := ConvertMarkup(p.Text, p.Markups) + return fmt.Sprintf("
  • %s
  • ", children) + convertOli(ps[1:]) + } else { + return "" + } +} + +func convertUli(ps []entities.Paragraph) string { + if len(ps) != 0 && ps[0].Type == "ULI" { + p := ps[0] + children := ConvertMarkup(p.Text, p.Markups) + return fmt.Sprintf("
  • %s
  • ", children) + convertUli(ps[1:]) + } else { + return "" + } +} diff --git a/pkg/converters/paragraph_converter_test.go b/pkg/converters/paragraph_converter_test.go new file mode 100644 index 0000000..edc46a1 --- /dev/null +++ b/pkg/converters/paragraph_converter_test.go @@ -0,0 +1,22 @@ +package converters + +import ( + "testing" + + "github.com/medium.rip/pkg/entities" +) + +func TestConvertOli(t *testing.T) { + oli := entities.Paragraph{ + Name: "1-1", + Type: "OLI", + Text: "This is an ordered list item.", + Markups: []entities.Markup{}, + } + olis := []entities.Paragraph{oli} + oliHTML := ConvertParagraphs(olis) + expected := "
    1. This is an ordered list item.
    " + if oliHTML != expected { + t.Errorf("ConvertParagraphs(olis) = %s; want %s", oliHTML, expected) + } +} \ No newline at end of file