mirror of
https://codeberg.org/VnPower/PixivFE
synced 2024-12-06 19:16:23 +01:00
Merge pull request 'Novel comments and special marks' (#108) from jackyzy823/PixivFE:novel-page-improve into v2
Reviewed-on: https://codeberg.org/VnPower/PixivFE/pulls/108
This commit is contained in:
+34
-30
@@ -832,36 +832,6 @@ edit style.scss
|
||||
.illust .illust-description {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.illust .comment {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.illust .comment p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.illust .comment .comment-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.illust .comment .comment-author {
|
||||
color: #fff;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
.illust .comment .stamp {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.illust .comment .emoji {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.illust .illust-other-works a.illust-other-works-author {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -877,6 +847,36 @@ edit style.scss
|
||||
object-fit: cover;
|
||||
object-position: center top;
|
||||
}
|
||||
.comment {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.comment p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.comment .comment-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.comment .comment-author {
|
||||
color: #fff;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
.comment .stamp {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.comment .emoji {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.novel-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(407px, 1fr));
|
||||
@@ -989,6 +989,10 @@ edit style.scss
|
||||
max-height: 100%;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
.novel .novel-content .novel-content-text a {
|
||||
text-decoration: underline;
|
||||
color: black;
|
||||
}
|
||||
.novel .novel-attr {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+41
-36
@@ -1111,42 +1111,6 @@ $font-fallback-sans: YuGothic,
|
||||
}
|
||||
|
||||
|
||||
.comment {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.comment-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
color: $fg;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stamp {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.illust-other-works {
|
||||
a.illust-other-works-author {
|
||||
display: inline-flex;
|
||||
@@ -1167,6 +1131,42 @@ $font-fallback-sans: YuGothic,
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.comment-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
color: $fg;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stamp {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.novel-container {
|
||||
display: grid;
|
||||
|
||||
@@ -1297,6 +1297,11 @@ $font-fallback-sans: YuGothic,
|
||||
max-height: 100%;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{{- if !.IsUgoira }}
|
||||
<div class="illust-images">
|
||||
{{- range index := .Images }}
|
||||
<a href="{{ .Original }}" target="_blank">
|
||||
<a href="{{ .Original }}" target="_blank" id="{{ index + 1 }}">
|
||||
{{- if .Width != 0 && .Height != 0 }}
|
||||
{* todo: do not reflow the page because of loading image *}
|
||||
<img src="{{ .Large }}" alt="Page {{ index }}" data-width="{{.Width}}" data-height="{{.Height}}" />
|
||||
<img src="{{ .Large }}" alt="Page {{ index + 1 }}" data-width="{{.Width}}" data-height="{{.Height}}" />
|
||||
{{- else }}
|
||||
<img src="{{ .Large }}" alt="Page {{ index }}" />
|
||||
<img src="{{ .Large }}" alt="Page {{ index + 1 }}" />
|
||||
{{- end }}
|
||||
</a>
|
||||
{{- end }}
|
||||
|
||||
@@ -122,6 +122,37 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<details>
|
||||
<summary>Comments</summary>
|
||||
{{- if .Novel.CommentOff == 1 }}
|
||||
<p>The creator turned comments off</p>
|
||||
{{- else if .Novel.CommentCount == 0 }}
|
||||
<p>There are no comments yet</p>
|
||||
{{- else }} {{ range .Novel.CommentsList }}
|
||||
<div class="comment">
|
||||
<a href="/users/{{ .AuthorID }}" class="comment-author">
|
||||
<img class="comment-avatar" src="{{ .Avatar }}" alt="{{ .AuthorName }}" />
|
||||
</a>
|
||||
<div class="comment-context">
|
||||
<a href="/users/{{ .AuthorID }}" class="comment-author">
|
||||
{{- .AuthorName }}
|
||||
</a>
|
||||
<p>
|
||||
{{- if .Stamp }}
|
||||
<img class="stamp" src="/proxy/s.pximg.net/common/images/stamp/generated-stamps/{{ .Stamp }}_s.jpg"
|
||||
alt="/proxy/s.pximg.net/common/images/stamp/generated-stamps/{{ .Stamp }}_s.jpg" />
|
||||
{{- else }} {{ raw: parseEmojis(.Context) }} {{ end }}
|
||||
</p>
|
||||
|
||||
<small>{{ .Date }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{{- end }} {{ end }}
|
||||
</details>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h2>Related works</h2>
|
||||
<div class="novel-container">
|
||||
{{- range .NovelRelated }}
|
||||
{{- include "fragments/novel-tn" . }}
|
||||
|
||||
@@ -141,6 +141,12 @@ func GetNovelRelatedURL(id string, limit int) string {
|
||||
return fmt.Sprintf(base, id, limit)
|
||||
}
|
||||
|
||||
func GetNovelCommentsURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/novels/comments/roots?novel_id=%s&offset=0&limit=100"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetNovelSeriesURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/novel/series/%s"
|
||||
|
||||
|
||||
+47
-1
@@ -3,6 +3,7 @@ package core
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
@@ -60,6 +61,18 @@ type Novel struct {
|
||||
ViewMode int `json:"viewMode"`
|
||||
// ...
|
||||
} `json:"suggestedSettings"`
|
||||
TextEmbeddedImages map[string]struct {
|
||||
NovelImageId string `json:"novelImageId"`
|
||||
SanityLevel string `json:"sl"`
|
||||
Urls struct {
|
||||
Two40Mw string `json:"240mw"`
|
||||
Four80Mw string `json:"480mw"`
|
||||
One200X1200 string `json:"1200x1200"`
|
||||
One28X128 string `json:"128x128"`
|
||||
Original string `json:"original"`
|
||||
} `json:"urls"`
|
||||
} `json:"textEmbeddedImages"`
|
||||
CommentsList []Comment
|
||||
}
|
||||
|
||||
type NovelBrief struct {
|
||||
@@ -123,7 +136,19 @@ func GetNovelByID(r *http.Request, id string) (Novel, error) {
|
||||
url := re_t.FindString(response)
|
||||
url = session.ProxyImageUrl(r, url[11:]) // truncate the "original":
|
||||
|
||||
return fmt.Sprintf(`<img src=%s alt="%s"/>`, url, s)
|
||||
// make [pixivimage:illustid-index] jump to anchor
|
||||
link := fmt.Sprintf("/artworks/%s", strings.ReplaceAll(illustid, "-", "#"))
|
||||
return fmt.Sprintf(`<a href="%s" target="_blank"><img src=%s alt="%s"/></a>`, link, url, s)
|
||||
})
|
||||
|
||||
re_u := regexp.MustCompile(`\[uploadedimage:(\d+)\]`)
|
||||
re_id := regexp.MustCompile(`\d+`)
|
||||
novel.Content = re_u.ReplaceAllStringFunc(novel.Content, func(s string) string {
|
||||
imageId := re_id.FindString(s)
|
||||
if val, ok := novel.TextEmbeddedImages[imageId]; ok {
|
||||
return fmt.Sprintf(`<img src=%s alt="%s"/>`, val.Urls.Original, s)
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
return novel, nil
|
||||
@@ -150,3 +175,24 @@ func GetNovelRelated(r *http.Request, id string) ([]NovelBrief, error) {
|
||||
|
||||
return novels.List, nil
|
||||
}
|
||||
|
||||
func GetNovelComments(r *http.Request, id string) ([]Comment, error) {
|
||||
var body struct {
|
||||
Comments []Comment `json:"comments"`
|
||||
}
|
||||
|
||||
URL := GetNovelCommentsURL(id)
|
||||
|
||||
response, err := API_GET_UnwrapJson(r.Context(), URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response = session.ProxyImageUrl(r, response)
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body.Comments, nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ func NovelPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if novel.CommentOff == 0 {
|
||||
// TODO should use token only if R-18/R-18G
|
||||
comments, err := core.GetNovelComments(r, id)
|
||||
if err == nil {
|
||||
novel.CommentsList = comments
|
||||
}
|
||||
}
|
||||
|
||||
user, err := core.GetUserBasicInformation(r, novel.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -305,8 +305,35 @@ func GetTemplateFunctions() map[string]any {
|
||||
return s[:len(s)-6]
|
||||
},
|
||||
"renderNovel": func(s string) HTML {
|
||||
furiganaTemplate := `<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>`
|
||||
furiganaPattern := regexp.MustCompile(`\[\[rb:\s*(.+?)\s*>\s*(.+?)\s*\]\]`)
|
||||
s = furiganaPattern.ReplaceAllString(s, furiganaTemplate)
|
||||
|
||||
chapterTemplate := `<h2>$1</h2>`
|
||||
chapterPattern := regexp.MustCompile(`\[chapter:\s*(.+?)\s*\]`)
|
||||
s = chapterPattern.ReplaceAllString(s, chapterTemplate)
|
||||
|
||||
jumpUriTemplate := `<a href="$2" target="_blank">$1</a>`
|
||||
jumpUriPattern := regexp.MustCompile(`\[\[jumpuri:\s*(.+?)\s*>\s*(.+?)\s*\]\]`)
|
||||
s = jumpUriPattern.ReplaceAllString(s, jumpUriTemplate)
|
||||
|
||||
jumpPageTemplate := `<a href="#$1">To page $1</a>`
|
||||
jumpPagePattern := regexp.MustCompile(`\[jump:\s*(\d+?)\s*\]`)
|
||||
s = jumpPagePattern.ReplaceAllString(s, jumpPageTemplate)
|
||||
|
||||
if strings.Contains(s, "[newpage]") {
|
||||
// if [newpage] in content , then prepend <hr id="1"/> to the page
|
||||
s = `<hr id="1"/>` + s
|
||||
pageIdx := 1
|
||||
|
||||
// Should run before replace `\n` -> `<br />`
|
||||
newPagePattern := regexp.MustCompile(`\s*\[newpage\]\s*`)
|
||||
s = newPagePattern.ReplaceAllStringFunc(s, func(_ string) string {
|
||||
pageIdx += 1
|
||||
return fmt.Sprintf(`<br /><hr id="%d"/>`, pageIdx)
|
||||
})
|
||||
}
|
||||
s = strings.ReplaceAll(s, "\n", "<br />")
|
||||
s = strings.ReplaceAll(s, "[newpage]", "Insert page separator here.")
|
||||
return HTML(s)
|
||||
},
|
||||
"novelGenre": GetNovelGenre,
|
||||
|
||||
Reference in New Issue
Block a user