mirror of
https://github.com/go-shiori/shiori.git
synced 2024-09-20 23:16:12 +08:00
Initial archive page
This commit is contained in:
parent
3345d8db29
commit
43040a9bc4
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Shiori - Bookmarks Manager</title>
|
||||
<title>$$.Title$$ - Shiori - Bookmarks Manager</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
@ -24,11 +24,11 @@
|
|||
<body>
|
||||
<div id="content-scene" :class="{night: displayOptions.nightMode}">
|
||||
<div id="header">
|
||||
<p id="metadata">Added {{modified}} UTC</p>
|
||||
<p id="title">{{title}}</p>
|
||||
<p id="metadata">Added $$.Modified$$ UTC</p>
|
||||
<p id="title">$$.Title$$</p>
|
||||
<div id="links">
|
||||
<a href="$$.URL$$" target="_blank">View Original</a>
|
||||
<a href="$$.URL$$" target="_blank">View Archive</a>
|
||||
<a href="/bookmark/$$.ID$$/archive" target="_blank">View Archive</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
|
@ -43,11 +43,6 @@
|
|||
new Vue({
|
||||
el: '#content-scene',
|
||||
mixins: [basePage],
|
||||
data: {
|
||||
title: "$$.Title$$",
|
||||
author: "$$.Author$$",
|
||||
modified: "$$.Modified$$"
|
||||
},
|
||||
methods: {
|
||||
loadSetting() {
|
||||
var opts = JSON.parse(localStorage.getItem("shiori-setting")) || {},
|
||||
|
@ -67,10 +62,6 @@
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
// Set title
|
||||
document.title = `${this.title} - Shiori - Bookmarks Manager`;
|
||||
|
||||
// Load setting
|
||||
this.loadSetting();
|
||||
}
|
||||
});
|
||||
|
|
1
internal/view/css/archive.css
Normal file
1
internal/view/css/archive.css
Normal file
|
@ -0,0 +1 @@
|
|||
:root{--main:#F44336;--border:#E5E5E5;--colorLink:#999;--archiveHeaderBg:rgba(255,255,255,0.95)}@media (prefers-color-scheme:dark){:root{--border:#191919;--archiveHeaderBg:rgba(41,41,41,0.95)}}#shiori-archive-header{top:0;left:0;right:0;height:60px;position:fixed;padding:0 16px;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;-webkit-box-align:center;align-items:center;font-size:16px;border-bottom:1px solid var(--border);background-color:var(--archiveHeaderBg);z-index:9999}#shiori-archive-header *{border-width:0;box-sizing:border-box;font-family:"Source Sans Pro",sans-serif;margin:0;padding:0}#shiori-archive-header>*:not(:last-child){margin-right:8px}#shiori-archive-header>.spacer{-webkit-box-flex:1;flex:1}#shiori-archive-header #shiori-logo{font-size:2em;font-weight:100;color:var(--main)}#shiori-archive-header #shiori-logo span{margin-right:8px}#shiori-archive-header a{font-size:1em;display:block;color:var(--colorLink);text-decoration:underline}#shiori-archive-header a:hover,#shiori-archive-header a:focus{color:var(--main)}
|
65
internal/view/less/archive.less
Normal file
65
internal/view/less/archive.less
Normal file
|
@ -0,0 +1,65 @@
|
|||
:root {
|
||||
--main: #F44336;
|
||||
--border: #E5E5E5;
|
||||
--colorLink: #999;
|
||||
--archiveHeaderBg: rgba(255, 255, 255, 0.95);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
--border: #191919;
|
||||
--archiveHeaderBg: rgba(41, 41, 41, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
#shiori-archive-header {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
position: fixed;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background-color: var(--archiveHeaderBg);
|
||||
z-index: 9999;
|
||||
|
||||
* {
|
||||
border-width: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
>*:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
>.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#shiori-logo {
|
||||
font-size: 2em;
|
||||
font-weight: 100;
|
||||
color: var(--main);
|
||||
|
||||
span {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 1em;
|
||||
display: block;
|
||||
color: var(--colorLink);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--main);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,17 +1,20 @@
|
|||
package webserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
nurl "net/url"
|
||||
"os"
|
||||
"path"
|
||||
fp "path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/go-shiori/shiori/pkg/warc"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
|
@ -85,13 +88,6 @@ func (h *handler) serveBookmarkContent(w http.ResponseWriter, r *http.Request, p
|
|||
"html": func(s string) template.HTML {
|
||||
return template.HTML(s)
|
||||
},
|
||||
"hostname": func(s string) string {
|
||||
parsed, err := nurl.ParseRequestURI(s)
|
||||
if err != nil || len(parsed.Scheme) == 0 {
|
||||
return s
|
||||
}
|
||||
return parsed.Hostname()
|
||||
},
|
||||
}
|
||||
|
||||
tplCache, err := createTemplate("content.html", funcMap)
|
||||
|
@ -126,3 +122,80 @@ func (h *handler) serveThumbnailImage(w http.ResponseWriter, r *http.Request, ps
|
|||
_, err = io.Copy(w, img)
|
||||
checkError(err)
|
||||
}
|
||||
|
||||
// serveBookmarkArchive is handler for GET /bookmark/:id/archive/*filepath
|
||||
func (h *handler) serveBookmarkArchive(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
// Get parameter from URL
|
||||
strID := ps.ByName("id")
|
||||
resourcePath := ps.ByName("filepath")
|
||||
resourcePath = strings.TrimPrefix(resourcePath, "/")
|
||||
|
||||
// Get bookmark from database
|
||||
id, err := strconv.Atoi(strID)
|
||||
checkError(err)
|
||||
|
||||
bookmark, exist := h.DB.GetBookmark(id, "")
|
||||
if !exist {
|
||||
panic(fmt.Errorf("Bookmark not found"))
|
||||
}
|
||||
|
||||
// Open archive, look in cache first
|
||||
var archive *warc.Archive
|
||||
cacheData, found := h.ArchiveCache.Get(strID)
|
||||
|
||||
if found {
|
||||
archive = cacheData.(*warc.Archive)
|
||||
} else {
|
||||
archivePath := fp.Join(h.DataDir, "archive", strID)
|
||||
archive, err = warc.Open(archivePath)
|
||||
checkError(err)
|
||||
|
||||
h.ArchiveCache.Set(strID, archive, 0)
|
||||
}
|
||||
|
||||
content, contentType, err := archive.Read(resourcePath)
|
||||
checkError(err)
|
||||
|
||||
// Set response header
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
// If this is HTML and root, inject shiori header
|
||||
if strings.Contains(strings.ToLower(contentType), "text/html") && resourcePath == "" {
|
||||
// Extract gzip
|
||||
buffer := bytes.NewBuffer(content)
|
||||
gzipReader, err := gzip.NewReader(buffer)
|
||||
checkError(err)
|
||||
|
||||
// Parse gzipped content
|
||||
doc, err := goquery.NewDocumentFromReader(gzipReader)
|
||||
checkError(err)
|
||||
|
||||
// Add Shiori overlay
|
||||
headerHTML := fmt.Sprintf(
|
||||
`<div id="shiori-archive-header">
|
||||
<p id="shiori-logo">
|
||||
<span>栞</span>shiori
|
||||
</p>
|
||||
<div class="spacer"></div>
|
||||
<a href="%s" target="_blank">View Original</a>
|
||||
<a href="/bookmark/%s/content" target="_blank">View Readable</a>
|
||||
</div>`, bookmark.URL, strID)
|
||||
|
||||
doc.Find("head").AppendHtml(`<link href="/css/archive.css" rel="stylesheet">`)
|
||||
doc.Find("body").PrependHtml(headerHTML)
|
||||
|
||||
// Revert back to HTML
|
||||
outerHTML, err := goquery.OuterHtml(doc.Selection)
|
||||
checkError(err)
|
||||
|
||||
// Gzip it again and send to response writer
|
||||
gzipWriter := gzip.NewWriter(w)
|
||||
gzipWriter.Write([]byte(outerHTML))
|
||||
gzipWriter.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// Serve content
|
||||
w.Write(content)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ type handler struct {
|
|||
DataDir string
|
||||
UserCache *cch.Cache
|
||||
SessionCache *cch.Cache
|
||||
ArchiveCache *cch.Cache
|
||||
}
|
||||
|
||||
// prepareLoginCache prepares login cache for future use
|
||||
|
|
|
@ -21,6 +21,7 @@ func ServeApp(DB database.DB, dataDir string, port int) error {
|
|||
DataDir: dataDir,
|
||||
UserCache: cch.New(time.Hour, 10*time.Minute),
|
||||
SessionCache: cch.New(time.Hour, 10*time.Minute),
|
||||
ArchiveCache: cch.New(time.Minute, 5*time.Minute),
|
||||
}
|
||||
|
||||
// Create router
|
||||
|
@ -35,6 +36,7 @@ func ServeApp(DB database.DB, dataDir string, port int) error {
|
|||
router.GET("/login", hdl.serveLoginPage)
|
||||
router.GET("/bookmark/:id/thumb", hdl.serveThumbnailImage)
|
||||
router.GET("/bookmark/:id/content", hdl.serveBookmarkContent)
|
||||
router.GET("/bookmark/:id/archive/*filepath", hdl.serveBookmarkArchive)
|
||||
|
||||
router.POST("/api/login", hdl.apiLogin)
|
||||
router.POST("/api/logout", hdl.apiLogout)
|
||||
|
|
Loading…
Reference in a new issue