UP Reader
parent
4b4b39b82d
commit
ed9a47c5ad
|
@ -1,8 +1,11 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
func InitApi() {
|
func InitApi() {
|
||||||
BilibiliInit()
|
BilibiliInit()
|
||||||
http.HandleFunc("/api/mdt", GetInfo)
|
http.HandleFunc("/api/mdt", GetInfo)
|
||||||
|
http.HandleFunc("/api/memos", GetMemosJson)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blog/config"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemoInfo struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RowStatus string `json:"rowStatus"`
|
||||||
|
CreatorId int `json:"creatorId"`
|
||||||
|
CreatedTs int `json:"createdTs"`
|
||||||
|
UpdatedTs int `json:"updatedTs"`
|
||||||
|
DisplayTs int `json:"displayTs"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
Pinned bool `json:"pinned"`
|
||||||
|
CreatorName string `json:"creatorName"`
|
||||||
|
CreatorUsername string `json:"creatorUsername"`
|
||||||
|
ResourceList []interface{} `json:"resourceList"`
|
||||||
|
RelationList []interface{} `json:"relationList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemosJson(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
id := r.Form.Get("id")
|
||||||
|
resp, err := http.Get(config.Cfg.MemosURL + "/api/v1/memo/" + id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法获取文章数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var info MemoInfo
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&info) // 使用json.NewDecoder解码JSON数据
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析文章数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
compileRegex := regexp.MustCompile("(?s)```json\n(.*?)```")
|
||||||
|
matchArr := compileRegex.FindStringSubmatch(info.Content)
|
||||||
|
if len(matchArr) > 0 {
|
||||||
|
source := make([]byte, 4096)
|
||||||
|
source = []byte(fmt.Sprintf("[%v]", matchArr[len(matchArr)-1]))
|
||||||
|
w.Header().Add("Content-Type", "application/octet-stream;charset=utf-8")
|
||||||
|
w.Header().Add("Content-Disposition", "attachment;filename="+info.Name+".json")
|
||||||
|
_, err = w.Write(source)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析源数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, "无法解析页面请求"+id, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
|
@ -5,4 +5,5 @@ type memosConfig struct {
|
||||||
MemosURL string `json:"memos_url"`
|
MemosURL string `json:"memos_url"`
|
||||||
MemosAlbumTag string `json:"memos_album_tag"`
|
MemosAlbumTag string `json:"memos_album_tag"`
|
||||||
MemosTalkTag string `json:"memos_talk_tag"`
|
MemosTalkTag string `json:"memos_talk_tag"`
|
||||||
|
MemosReaderTag string `json:"memos_reader_tag"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,16 @@ func Memos(w http.ResponseWriter, r *http.Request) {
|
||||||
"author": config.Cfg.Author,
|
"author": config.Cfg.Author,
|
||||||
"qq": config.Cfg.Qq,
|
"qq": config.Cfg.Qq,
|
||||||
}))
|
}))
|
||||||
|
case "reader":
|
||||||
|
Template := models.Template.Reader
|
||||||
|
//qq := "https://q1.qlogo.cn/g?b=qq&nk=" + config.Cfg.Qq + "&s=5"
|
||||||
|
Template.WriteData(w, models.BuildViewData("Reader", map[string]interface{}{
|
||||||
|
"url": config.Cfg.MemosURL,
|
||||||
|
"user": config.Cfg.MemosUser,
|
||||||
|
"tag": config.Cfg.MemosReaderTag,
|
||||||
|
"author": config.Cfg.Author,
|
||||||
|
"qq": config.Cfg.Qq,
|
||||||
|
}))
|
||||||
case "album":
|
case "album":
|
||||||
Template := models.Template.Album
|
Template := models.Template.Album
|
||||||
tag := strings.Split(config.Cfg.MemosAlbumTag, ",")
|
tag := strings.Split(config.Cfg.MemosAlbumTag, ",")
|
||||||
|
|
|
@ -12,6 +12,7 @@ type TemplatePointer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HtmlTemplate struct {
|
type HtmlTemplate struct {
|
||||||
|
Reader TemplatePointer
|
||||||
Talk TemplatePointer
|
Talk TemplatePointer
|
||||||
Album TemplatePointer
|
Album TemplatePointer
|
||||||
Article TemplatePointer
|
Article TemplatePointer
|
||||||
|
@ -68,7 +69,7 @@ func BuildViewData(title string, data interface{}) map[string]interface{} {
|
||||||
// error: 错误信息(如果有)
|
// error: 错误信息(如果有)
|
||||||
func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
|
func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
|
||||||
tp, err := readHtmlTemplate(
|
tp, err := readHtmlTemplate(
|
||||||
[]string{"index", "extraNav", "dashboard", "categories", "article", "album", "talk"},
|
[]string{"index", "extraNav", "dashboard", "categories", "article", "album", "talk", "reader"},
|
||||||
viewDir)
|
viewDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HtmlTemplate{}, err
|
return HtmlTemplate{}, err
|
||||||
|
@ -82,6 +83,7 @@ func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
|
||||||
Article: tp[4],
|
Article: tp[4],
|
||||||
Album: tp[5],
|
Album: tp[5],
|
||||||
Talk: tp[6],
|
Talk: tp[6],
|
||||||
|
Reader: tp[7],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func SpreadDigit(n int) []int {
|
func SpreadDigit(n int) []int {
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
{{if ne .Config.MemosTalkTag ""}}
|
{{if ne .Config.MemosTalkTag ""}}
|
||||||
<li><a class="dropdown-item" href="/memos?name=talk">Talk</a></li>
|
<li><a class="dropdown-item" href="/memos?name=talk">Talk</a></li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{if ne .Config.MemosReaderTag ""}}
|
||||||
|
<li><a class="dropdown-item" href="/memos?name=reader">Reader</a></li>
|
||||||
|
{{ end }}
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
{{range $nav := .Navs }}
|
{{range $nav := .Navs }}
|
||||||
<li><a class="dropdown-item" href="/extra-nav?name={{ $nav.Title }}">{{ $nav.Title }}</a></li>
|
<li><a class="dropdown-item" href="/extra-nav?name={{ $nav.Title }}">{{ $nav.Title }}</a></li>
|
||||||
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
{{template "header" .}}
|
||||||
|
<div class="sub-title">- {{ .Title }} -</div>
|
||||||
|
<style>
|
||||||
|
div#page {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme=dark] #twikoo .tk-content, #twikoo .tk-content {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_item, .tk-expand, .tk-comments-container > .tk-comment, .tk-submit:nth-child(1) {
|
||||||
|
border: 1px solid #e0e3ed;
|
||||||
|
box-shadow: 0 5px 10px rgb(189 189 189 / 10%);
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
border-radius: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_item:hover, .tk-comments-container > .tk-comment:hover, .tk-submit:nth-child(1):hover {
|
||||||
|
border-color: var(--primary, #673ab7)
|
||||||
|
}
|
||||||
|
|
||||||
|
.tk-submit {
|
||||||
|
padding: 20px 10px 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.tk-comments-container > .tk-comment {
|
||||||
|
padding: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk {
|
||||||
|
margin-top: 1rem
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk .loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--primary, #673ab7)
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk .loading img {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin: 0 !important;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom, .talk_meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom {
|
||||||
|
justify-content: space-between
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
span.talk_nick {
|
||||||
|
color: #6dbdc3;
|
||||||
|
font-size: 1.2rem
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
span.talk_date {
|
||||||
|
opacity: .6
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_content {
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.zone_imgbox a {
|
||||||
|
display: block;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: var(--w);
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone_imgbox img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 !important;
|
||||||
|
object-fit: cover
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom {
|
||||||
|
opacity: .9
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom .icon {
|
||||||
|
color: var(--primary, #673ab7);
|
||||||
|
float: right;
|
||||||
|
transition: all .3s
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom .icon:hover {
|
||||||
|
color: #49b1f5
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_content > a {
|
||||||
|
margin: 0 3px;
|
||||||
|
color: #ff7d73 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_content > a:hover {
|
||||||
|
text-decoration: none !important;
|
||||||
|
color: #ff5143 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit {
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
color: rgba(76, 73, 72, 0.6)
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme=dark] .limit {
|
||||||
|
color: rgba(255, 255, 255, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
.zone_imgbox {
|
||||||
|
--w: calc(33% - 5px)
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk {
|
||||||
|
margin: 10px 3px 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#post-comment {
|
||||||
|
margin: 0 3px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.zone_imgbox {
|
||||||
|
gap: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone_imgbox {
|
||||||
|
--w: calc(50% - 3px)
|
||||||
|
}
|
||||||
|
|
||||||
|
span.talk_date {
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.downloadBtn {
|
||||||
|
display: flex;
|
||||||
|
background: var(--primary, #673ab7);
|
||||||
|
color: white;
|
||||||
|
padding: 6px 22px;
|
||||||
|
margin: 8px 0;
|
||||||
|
border-radius: 7px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12pt;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 2px 0 rgb(0 40 56 / 15%);
|
||||||
|
font-weight: bold;
|
||||||
|
transition: box-shadow 300ms ease-in;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="talk">
|
||||||
|
<div class='loading'><img src="/public/img/loading.svg" alt="加载中...">
|
||||||
|
<a>加载中...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function Format(item) {
|
||||||
|
let date = getTime(new Date(item.createdTs * 1000).toString()),
|
||||||
|
content = item.content,
|
||||||
|
id = item.id,
|
||||||
|
name = item.name,
|
||||||
|
codes = content.match(/```(.*)```/igs);
|
||||||
|
content = content.replace(/#(.*?)\s/g, '').replace(/{.*}/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/```(.*)```/igs, '')
|
||||||
|
let text =`<a href="{{ .Data.url }}/m/${name}">原文地址</a>`;
|
||||||
|
if (codes) codes.forEach(item => {
|
||||||
|
content += `<div style="display: flex;"><a type="button" class="downloadBtn" href="legado://import/auto?src=${window.location.origin}/api/memos?id=${id}">一键导入</a>`
|
||||||
|
content += `<a type="button" class="downloadBtn" href="${window.location.origin}/api/memos?id=${id}">下载文件</a></div>`
|
||||||
|
// content += marked(item);
|
||||||
|
})
|
||||||
|
return {content, date, text}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTime(time) {
|
||||||
|
var nol = function (h) {
|
||||||
|
return h > 9 ? h : '0' + h
|
||||||
|
}
|
||||||
|
var now = new Date()
|
||||||
|
var yesterday = new Date(now.getTime() - (1000 * 60 * 60 * 24)).toLocaleDateString()
|
||||||
|
var twoDaysAgo = new Date(now.getTime() - 2 * (1000 * 60 * 60 * 24)).toLocaleDateString()
|
||||||
|
let d = new Date(time),
|
||||||
|
ls = [d.getFullYear(), nol(d.getMonth() + 1), nol(d.getDate()), nol(d.getHours()), nol(d.getMinutes()), nol(d.getSeconds())],
|
||||||
|
day = d.toLocaleDateString()
|
||||||
|
if (day === now.toLocaleDateString()) {
|
||||||
|
return '今天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else if (day === yesterday) {
|
||||||
|
return '昨天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else if (day === twoDaysAgo) {
|
||||||
|
return '前天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else {
|
||||||
|
if (now.getFullYear() == ls[0]) return ls[1] + '月' + ls[2] + '日 ' + ls[3] + ':' + ls[4]
|
||||||
|
else return ls[0] + '年' + ls[1] + '月' + ls[2] + '日 ' + ls[3] + ':' + ls[4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('{{ .Data.url }}/api/v1/memo?creatorId={{ .Data.user }}&tag={{ .Data.tag }}').then(res => res.json()).then(data => {
|
||||||
|
let items = [], html = ''
|
||||||
|
data.forEach(item => {
|
||||||
|
items.push(Format(item))
|
||||||
|
})
|
||||||
|
if (items.length == 30) document.querySelector('.limit').style.display = 'block'
|
||||||
|
items.forEach(item => {
|
||||||
|
html += '<div class="talk_item"><div class="talk_meta">';
|
||||||
|
html += "<img class=\"no-lightbox no-lazyload avatar\" src=\"https://q1.qlogo.cn/g?b=qq&nk={{ .Data.qq }}&s=5\">";
|
||||||
|
html += `<div class="info"><span class="talk_nick">{{ .Data.author }}</span><span class="talk_date">${item.date}</span>`;
|
||||||
|
html += `</div></div><div class="talk_content">${item.content}</div>${item.text}</div>`;
|
||||||
|
})
|
||||||
|
document.getElementById('talk').innerHTML = html;
|
||||||
|
waterfall('#talk');
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<script src="/public/js/prism.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/waterfall.min.js"></script>
|
||||||
|
{{template "footer" .}}
|
|
@ -79,7 +79,7 @@
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
html += '<div class="talk_item"><div class="talk_meta">';
|
html += '<div class="talk_item"><div class="talk_meta">';
|
||||||
html += "<img class=\"no-lightbox no-lazyload avatar\" src=\"https://q1.qlogo.cn/g?b=qq&nk={{ .Data.qq }}&s=5\">";
|
html += "<img class=\"no-lightbox no-lazyload avatar\" src=\"https://q1.qlogo.cn/g?b=qq&nk={{ .Data.qq }}&s=5\">";
|
||||||
html += `<div class="info"><span class="talk_nick">Leonus</span><span class="talk_date">${item.date}</span>`;
|
html += `<div class="info"><span class="talk_nick">{{ .Data.author }}</span><span class="talk_date">${item.date}</span>`;
|
||||||
html += `</div></div><div class="talk_content">${item.content}</div></div>`;
|
html += `</div></div><div class="talk_content">${item.content}</div></div>`;
|
||||||
})
|
})
|
||||||
document.getElementById('talk').innerHTML = html;
|
document.getElementById('talk').innerHTML = html;
|
||||||
|
|
Loading…
Reference in New Issue