mirror of
https://github.com/aljazceru/njump.git
synced 2025-12-17 22:34:25 +01:00
Improve long format markdown rendering
This commit is contained in:
@@ -44,9 +44,17 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h1, h2, h3 {
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
line-height: 1.1em;
|
line-height: 1.1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--default h2 {
|
||||||
|
color: #e32a6d;
|
||||||
|
}
|
||||||
|
.theme--dark h2 {
|
||||||
|
color: #e32a6d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--default a {
|
.theme--default a {
|
||||||
@@ -56,6 +64,10 @@ h1, h2 {
|
|||||||
color: #fafafa;
|
color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -123,15 +123,24 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h1, h2 {
|
h1, h2, h3 {
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
line-height: 1.1em;
|
line-height: 1.1em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
@include themed() {
|
||||||
|
color: t($accent1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
@include themed() {
|
@include themed() {
|
||||||
color: t($base7);
|
color: t($base7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
.background {
|
.background {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -39,9 +39,12 @@
|
|||||||
<div class="field content">
|
<div class="field content">
|
||||||
{{ if (not (eq .subject ""))}}
|
{{ if (not (eq .subject ""))}}
|
||||||
<h1>{{.subject | escapeString}}</h1>
|
<h1>{{.subject | escapeString}}</h1>
|
||||||
{{ end }} {{ if (or (eq .kindID 30023) (eq .kindID 30024))}}
|
{{ end }}
|
||||||
{{.content | mdToHTML | sanitizeXSS }} {{ else }} {{.content |
|
{{ if (or (eq .kindID 30023) (eq .kindID 30024))}}
|
||||||
escapeString | basicFormatting }} {{ end }}
|
{{.content | mdToHTML }}
|
||||||
|
{{ else }}
|
||||||
|
{{.content | escapeString | basicFormatting }}
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field separator"></div>
|
<div class="field separator"></div>
|
||||||
|
|||||||
193
utils.go
193
utils.go
@@ -180,77 +180,6 @@ func getPreviewStyle(r *http.Request) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func basicFormatting(input string) string {
|
|
||||||
lines := strings.Split(input, "\n")
|
|
||||||
|
|
||||||
var processedLines []string
|
|
||||||
for _, line := range lines {
|
|
||||||
processedLine := replaceURLsWithTags(line)
|
|
||||||
processedLines = append(processedLines, processedLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(processedLines, "<br/>")
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceURLsWithTags(line string) string {
|
|
||||||
var regex *regexp.Regexp
|
|
||||||
var rline string
|
|
||||||
|
|
||||||
// Match and replace image URLs with <img> tags
|
|
||||||
imgsPattern := fmt.Sprintf(`\s*(https?://\S+(\.jpg|\.jpeg|\.png|\.webp|\.gif))\s*`)
|
|
||||||
regex = regexp.MustCompile(imgsPattern)
|
|
||||||
rline = regex.ReplaceAllStringFunc(line, func(match string) string {
|
|
||||||
submatch := regex.FindStringSubmatch(match)
|
|
||||||
if len(submatch) < 2 {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
capturedGroup := submatch[1]
|
|
||||||
replacement := fmt.Sprintf(` <img src="%s" alt=""> `, capturedGroup)
|
|
||||||
return replacement
|
|
||||||
})
|
|
||||||
if rline != line {
|
|
||||||
return rline
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match and replace mp4 URLs with <video> tag
|
|
||||||
videoPattern := fmt.Sprintf(`\s*(https?://\S+(\.mp4|\.ogg|\.webm|.mov))\s*`)
|
|
||||||
regex = regexp.MustCompile(videoPattern)
|
|
||||||
rline = regex.ReplaceAllStringFunc(line, func(match string) string {
|
|
||||||
submatch := regex.FindStringSubmatch(match)
|
|
||||||
if len(submatch) < 2 {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
capturedGroup := submatch[1]
|
|
||||||
replacement := fmt.Sprintf(` <video controls width="100%%"><source src="%s"></video> `, capturedGroup)
|
|
||||||
return replacement
|
|
||||||
})
|
|
||||||
if rline != line {
|
|
||||||
return rline
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match and replace npup1, nprofile1, note1, nevent1, etc
|
|
||||||
nostrRegexPattern := `\S*(nostr:)?((npub|note|nevent|nprofile)1[a-z0-9]+)\S*`
|
|
||||||
nostrRegex := regexp.MustCompile(nostrRegexPattern)
|
|
||||||
line = nostrRegex.ReplaceAllStringFunc(line, func(match string) string {
|
|
||||||
submatch := nostrRegex.FindStringSubmatch(match)
|
|
||||||
if len(submatch) < 2 {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
capturedGroup := submatch[2]
|
|
||||||
first6 := capturedGroup[:6]
|
|
||||||
last6 := capturedGroup[len(capturedGroup)-6:]
|
|
||||||
replacement := fmt.Sprintf(`<a href="/%s" class="nostr">%s</a>`, capturedGroup, first6+"…"+last6)
|
|
||||||
return replacement
|
|
||||||
})
|
|
||||||
|
|
||||||
// Match and replace other URLs with <a> tags
|
|
||||||
hrefRegexPattern := `\S*(https?://\S+)\S*`
|
|
||||||
hrefRegex := regexp.MustCompile(hrefRegexPattern)
|
|
||||||
line = hrefRegex.ReplaceAllString(line, ` <a href="$1">$1</a> `)
|
|
||||||
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
|
|
||||||
func findParentNevent(event *nostr.Event) string {
|
func findParentNevent(event *nostr.Event) string {
|
||||||
parentNevent := ""
|
parentNevent := ""
|
||||||
replyTag := nip10.GetImmediateReply(event.Tags)
|
replyTag := nip10.GetImmediateReply(event.Tags)
|
||||||
@@ -266,7 +195,119 @@ func findParentNevent(event *nostr.Event) string {
|
|||||||
return parentNevent
|
return parentNevent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rendering functions
|
||||||
|
// ### ### ### ### ### ### ### ### ### ### ###
|
||||||
|
|
||||||
|
func replateImageURLsWithTags(input string, replacement string) string {
|
||||||
|
// Match and replace image URLs with a custom replacement
|
||||||
|
// Usually is html <img> => ` <img src="%s" alt=""> `
|
||||||
|
// or markdown !()[...] tags for further processing => ``
|
||||||
|
var regex *regexp.Regexp
|
||||||
|
imgsPattern := `\S*(\()?(https?://\S+(\.jpg|\.jpeg|\.png|\.webp|\.gif))\S*`
|
||||||
|
regex = regexp.MustCompile(imgsPattern)
|
||||||
|
input = regex.ReplaceAllStringFunc(input, func(match string) string {
|
||||||
|
submatch := regex.FindStringSubmatch(match)
|
||||||
|
if len(submatch) < 2 ||
|
||||||
|
strings.Contains(submatch[0], "](") { // Markdown  image
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
capturedGroup := submatch[2]
|
||||||
|
replacement := fmt.Sprintf(replacement, capturedGroup)
|
||||||
|
return replacement
|
||||||
|
})
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
func replateVideoURLsWithTags(input string, replacement string) string {
|
||||||
|
// Match and replace video URLs with a custom replacement
|
||||||
|
// Usually is html <video> => ` <video controls width="100%%"><source src="%s"></video> `
|
||||||
|
// or markdown !()[...] tags for further processing => ``
|
||||||
|
var regex *regexp.Regexp
|
||||||
|
videoPattern := `\S*(https?://\S+(\.mp4|\.ogg|\.webm|.mov))\S*`
|
||||||
|
regex = regexp.MustCompile(videoPattern)
|
||||||
|
input = regex.ReplaceAllStringFunc(input, func(match string) string {
|
||||||
|
submatch := regex.FindStringSubmatch(match)
|
||||||
|
if len(submatch) < 2 {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
capturedGroup := submatch[1]
|
||||||
|
replacement := fmt.Sprintf(replacement, capturedGroup)
|
||||||
|
return replacement
|
||||||
|
})
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceNostrURLsWithTags(input string) string {
|
||||||
|
// Match and replace npup1, nprofile1, note1, nevent1, etc
|
||||||
|
nostrRegexPattern := `\S*(nostr:)?((npub|note|nevent|nprofile|naddr)1[a-z0-9]+)\b`
|
||||||
|
nostrRegex := regexp.MustCompile(nostrRegexPattern)
|
||||||
|
input = nostrRegex.ReplaceAllStringFunc(input, func(match string) string {
|
||||||
|
submatch := nostrRegex.FindStringSubmatch(match)
|
||||||
|
if len(submatch) < 2 || strings.Contains(submatch[0], "/") {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
capturedGroup := submatch[2]
|
||||||
|
first6 := capturedGroup[:6]
|
||||||
|
last6 := capturedGroup[len(capturedGroup)-6:]
|
||||||
|
replacement := fmt.Sprintf(`<a href="/%s" class="nostr">%s</a>`, capturedGroup, first6+"…"+last6)
|
||||||
|
return replacement
|
||||||
|
})
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceURLsWithTags(line string) string {
|
||||||
|
|
||||||
|
var rline string
|
||||||
|
|
||||||
|
rline = replateImageURLsWithTags(line, ` <img src="%s" alt=""> `)
|
||||||
|
if rline != line {
|
||||||
|
return rline
|
||||||
|
}
|
||||||
|
|
||||||
|
rline = replateVideoURLsWithTags(line, `<video controls width="100%%"><source src="%s"></video>`)
|
||||||
|
if rline != line {
|
||||||
|
return rline
|
||||||
|
}
|
||||||
|
|
||||||
|
line = replaceNostrURLsWithTags(line)
|
||||||
|
|
||||||
|
// Match and replace other URLs with <a> tags
|
||||||
|
hrefRegexPattern := `\S*(https?://\S+)\S*`
|
||||||
|
hrefRegex := regexp.MustCompile(hrefRegexPattern)
|
||||||
|
line = hrefRegex.ReplaceAllString(line, `<a href="$1">$1</a>`)
|
||||||
|
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeXSS(html string) string {
|
||||||
|
p := bluemonday.UGCPolicy()
|
||||||
|
p.AllowStyling()
|
||||||
|
p.AllowElements("video", "source", "iframe")
|
||||||
|
p.AllowAttrs("controls", "width").OnElements("video")
|
||||||
|
p.AllowAttrs("src", "width").OnElements("source")
|
||||||
|
p.AllowAttrs("height", "width", "src", "frameborder").OnElements("iframe")
|
||||||
|
return p.Sanitize(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicFormatting(input string) string {
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
|
||||||
|
var processedLines []string
|
||||||
|
for _, line := range lines {
|
||||||
|
processedLine := replaceURLsWithTags(line)
|
||||||
|
processedLines = append(processedLines, processedLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(processedLines, "<br/>")
|
||||||
|
}
|
||||||
|
|
||||||
func mdToHTML(md string) string {
|
func mdToHTML(md string) string {
|
||||||
|
|
||||||
|
md = strings.ReplaceAll(md, "\u00A0", " ")
|
||||||
|
md = replateImageURLsWithTags(md, ``)
|
||||||
|
md = replateVideoURLsWithTags(md, `<video controls width="100%%"><source src="%s"></video>`)
|
||||||
|
md = replaceNostrURLsWithTags(md)
|
||||||
|
|
||||||
// create markdown parser with extensions
|
// create markdown parser with extensions
|
||||||
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.Footnotes
|
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.Footnotes
|
||||||
p := parser.NewWithExtensions(extensions)
|
p := parser.NewWithExtensions(extensions)
|
||||||
@@ -276,12 +317,10 @@ func mdToHTML(md string) string {
|
|||||||
htmlFlags := html.CommonFlags | html.HrefTargetBlank
|
htmlFlags := html.CommonFlags | html.HrefTargetBlank
|
||||||
opts := html.RendererOptions{Flags: htmlFlags}
|
opts := html.RendererOptions{Flags: htmlFlags}
|
||||||
renderer := html.NewRenderer(opts)
|
renderer := html.NewRenderer(opts)
|
||||||
|
output := string(markdown.Render(doc, renderer))
|
||||||
|
|
||||||
return string(markdown.Render(doc, renderer))
|
// Sanitize content
|
||||||
}
|
output = sanitizeXSS(output)
|
||||||
|
|
||||||
func sanitizeXSS(html string) string {
|
return output
|
||||||
p := bluemonday.UGCPolicy()
|
|
||||||
p.AllowStyling()
|
|
||||||
return p.Sanitize(html)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user