mirror of
https://github.com/go-shiori/shiori.git
synced 2024-09-21 07:26:13 +08:00
Initial archive page
This commit is contained in:
parent
3345d8db29
commit
43040a9bc4
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Shiori - Bookmarks Manager</title>
|
<title>$$.Title$$ - Shiori - Bookmarks Manager</title>
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -24,11 +24,11 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="content-scene" :class="{night: displayOptions.nightMode}">
|
<div id="content-scene" :class="{night: displayOptions.nightMode}">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<p id="metadata">Added {{modified}} UTC</p>
|
<p id="metadata">Added $$.Modified$$ UTC</p>
|
||||||
<p id="title">{{title}}</p>
|
<p id="title">$$.Title$$</p>
|
||||||
<div id="links">
|
<div id="links">
|
||||||
<a href="$$.URL$$" target="_blank">View Original</a>
|
<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>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
@ -43,11 +43,6 @@
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#content-scene',
|
el: '#content-scene',
|
||||||
mixins: [basePage],
|
mixins: [basePage],
|
||||||
data: {
|
|
||||||
title: "$$.Title$$",
|
|
||||||
author: "$$.Author$$",
|
|
||||||
modified: "$$.Modified$$"
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
loadSetting() {
|
loadSetting() {
|
||||||
var opts = JSON.parse(localStorage.getItem("shiori-setting")) || {},
|
var opts = JSON.parse(localStorage.getItem("shiori-setting")) || {},
|
||||||
|
@ -67,10 +62,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// Set title
|
|
||||||
document.title = `${this.title} - Shiori - Bookmarks Manager`;
|
|
||||||
|
|
||||||
// Load setting
|
|
||||||
this.loadSetting();
|
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
|
package webserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
nurl "net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
fp "path/filepath"
|
fp "path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"github.com/go-shiori/shiori/pkg/warc"
|
||||||
"github.com/julienschmidt/httprouter"
|
"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 {
|
"html": func(s string) template.HTML {
|
||||||
return template.HTML(s)
|
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)
|
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)
|
_, err = io.Copy(w, img)
|
||||||
checkError(err)
|
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
|
DataDir string
|
||||||
UserCache *cch.Cache
|
UserCache *cch.Cache
|
||||||
SessionCache *cch.Cache
|
SessionCache *cch.Cache
|
||||||
|
ArchiveCache *cch.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareLoginCache prepares login cache for future use
|
// prepareLoginCache prepares login cache for future use
|
||||||
|
|
|
@ -21,6 +21,7 @@ func ServeApp(DB database.DB, dataDir string, port int) error {
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
UserCache: cch.New(time.Hour, 10*time.Minute),
|
UserCache: cch.New(time.Hour, 10*time.Minute),
|
||||||
SessionCache: 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
|
// Create router
|
||||||
|
@ -35,6 +36,7 @@ func ServeApp(DB database.DB, dataDir string, port int) error {
|
||||||
router.GET("/login", hdl.serveLoginPage)
|
router.GET("/login", hdl.serveLoginPage)
|
||||||
router.GET("/bookmark/:id/thumb", hdl.serveThumbnailImage)
|
router.GET("/bookmark/:id/thumb", hdl.serveThumbnailImage)
|
||||||
router.GET("/bookmark/:id/content", hdl.serveBookmarkContent)
|
router.GET("/bookmark/:id/content", hdl.serveBookmarkContent)
|
||||||
|
router.GET("/bookmark/:id/archive/*filepath", hdl.serveBookmarkArchive)
|
||||||
|
|
||||||
router.POST("/api/login", hdl.apiLogin)
|
router.POST("/api/login", hdl.apiLogin)
|
||||||
router.POST("/api/logout", hdl.apiLogout)
|
router.POST("/api/logout", hdl.apiLogout)
|
||||||
|
|
Loading…
Reference in a new issue