Repos / pytaku / 3ec0908268
commit 3ec090826828e14a3d4004dfb2c5b764910b632e
Author: Bùi Thành Nhân <hi@imnhan.com>
Date: Sun Aug 23 16:58:01 2020 +0700
implement chapter route
diff --git a/src/pytaku/main.py b/src/pytaku/main.py
index 5ebcdfe..c155e13 100644
--- a/src/pytaku/main.py
+++ b/src/pytaku/main.py
@@ -19,7 +19,7 @@
)
from .conf import config
-from .decorators import process_token, require_login, toggle_has_read
+from .decorators import process_token, require_login
from .persistence import (
create_token,
delete_token,
@@ -55,6 +55,20 @@
)
+def _chapter_name(chapter: dict):
+ result = ""
+ if chapter.get("num_major") is not None:
+ result += "Ch. " if chapter.get("volume") else "Chapter "
+ result += str(chapter["num_major"])
+ if chapter.get("num_minor"):
+ result += '.{chapter["num_minor"]}'
+ if chapter.get("volume"):
+ result += f"Vol. {chapter['volume']}"
+ if chapter.get("name"):
+ result += f" - {chapter['name']}"
+ return result
+
+
@app.route("/following", methods=["GET"])
@require_login
def follows_view():
@@ -71,14 +85,14 @@ def follows_view():
@require_login
def follow_view(site, title_id):
follow(session["user"]["id"], site, title_id)
- return redirect(url_for("title_view", site=site, title_id=title_id))
+ return redirect(url_for("spa_title_view", site=site, title_id=title_id))
@app.route("/unfollow/<site>/<title_id>", methods=["POST"])
@require_login
def unfollow_view(site, title_id):
unfollow(session["user"]["id"], site, title_id)
- return redirect(url_for("title_view", site=site, title_id=title_id))
+ return redirect(url_for("spa_title_view", site=site, title_id=title_id))
@app.route("/logout", methods=["POST"])
@@ -170,38 +184,6 @@ def auth_view():
return render_template("old/auth.html")
-@app.route("/m/<site>/<title_id>/<chapter_id>")
-@toggle_has_read
-def chapter_view(site, title_id, chapter_id):
- chapter = load_chapter(site, title_id, chapter_id)
- if not chapter:
- print("Getting chapter", chapter_id)
- chapter = get_chapter(site, title_id, chapter_id)
- save_chapter(chapter)
- else:
- print("Loading chapter", chapter_id, "from db")
-
- if site in ("mangadex", "mangasee"):
- chapter["pages"] = [
- url_for("proxy_view", b64_url=_encode_proxy_url(p))
- for p in chapter["pages"]
- ]
-
- # YIIIIKES
- title = load_title(site, title_id)
- title["cover"] = title_cover(site, title_id, title["cover_ext"])
- if site == "mangadex":
- title["cover"] = url_for(
- "proxy_view", b64_url=_encode_proxy_url(title["cover"])
- )
- prev_chapter, next_chapter = get_prev_next_chapters(title, chapter)
- chapter["prev_chapter"] = prev_chapter
- chapter["next_chapter"] = next_chapter
-
- chapter["site"] = site
- return render_template("old/chapter.html", title=title, **chapter)
-
-
@app.route("/search")
def search_view():
query = request.args.get("q", "").strip()
@@ -381,6 +363,35 @@ def spa_title_view(site, title_id):
)
+@app.route("/m/<site>/<title_id>/<chapter_id>")
+def spa_chapter_view(site, title_id, chapter_id):
+ chapter = load_chapter(site, title_id, chapter_id)
+ if not chapter:
+ print("Getting chapter", chapter_id)
+ chapter = get_chapter(site, title_id, chapter_id)
+ save_chapter(chapter)
+ else:
+ print("Loading chapter", chapter_id, "from db")
+
+ # YIIIIKES
+ title = load_title(site, title_id)
+ title["cover"] = title_cover(site, title_id, title["cover_ext"])
+ if site == "mangadex":
+ title["cover"] = url_for(
+ "proxy_view", b64_url=_encode_proxy_url(title["cover"])
+ )
+
+ chapter["site"] = site
+ return render_template(
+ "spa.html",
+ open_graph={
+ "title": f'{_chapter_name(chapter)} - {title["name"]}',
+ "image": title["cover"],
+ "description": "\n".join(title["descriptions"]),
+ },
+ )
+
+
@app.route("/api/title/<site>/<title_id>", methods=["GET"])
@process_token(required=False)
def api_title(site, title_id):
@@ -388,6 +399,37 @@ def api_title(site, title_id):
return title
+@app.route("/api/chapter/<site>/<title_id>/<chapter_id>", methods=["GET"])
+@process_token(required=False)
+def api_chapter(site, title_id, chapter_id):
+ chapter = load_chapter(site, title_id, chapter_id)
+ if not chapter:
+ print("Getting chapter", chapter_id)
+ chapter = get_chapter(site, title_id, chapter_id)
+ save_chapter(chapter)
+ else:
+ print("Loading chapter", chapter_id, "from db")
+
+ if site in ("mangadex", "mangasee"):
+ chapter["pages"] = [
+ url_for("proxy_view", b64_url=_encode_proxy_url(p))
+ for p in chapter["pages"]
+ ]
+
+ # YIIIIKES
+ title = load_title(site, title_id)
+ title["cover"] = title_cover(site, title_id, title["cover_ext"])
+ if site == "mangadex":
+ title["cover"] = url_for(
+ "proxy_view", b64_url=_encode_proxy_url(title["cover"])
+ )
+ prev_chapter, next_chapter = get_prev_next_chapters(title, chapter)
+ chapter["prev_chapter"] = prev_chapter
+ chapter["next_chapter"] = next_chapter
+ chapter["site"] = site
+ return chapter
+
+
@app.route("/api/register", methods=["POST"])
def api_register():
username = request.json["username"].strip()
diff --git a/src/pytaku/static/js/layout.js b/src/pytaku/static/js/layout.js
index cbd56db..7a566e5 100644
--- a/src/pytaku/static/js/layout.js
+++ b/src/pytaku/static/js/layout.js
@@ -72,7 +72,7 @@ function Navbar(initialVNode) {
const Layout = {
view: (vnode) => {
- return m("div.main", [m(Navbar), vnode.children]);
+ return [m(Navbar), vnode.children];
},
};
diff --git a/src/pytaku/static/js/main.js b/src/pytaku/static/js/main.js
index 68e84ef..427f052 100644
--- a/src/pytaku/static/js/main.js
+++ b/src/pytaku/static/js/main.js
@@ -5,6 +5,7 @@ import Home from "./routes/home.js";
import Follows from "./routes/follows.js";
import Search from "./routes/search.js";
import Title from "./routes/title.js";
+import Chapter from "./routes/chapter.js";
Auth.init().then(() => {
const root = document.getElementById("spa-root");
@@ -63,5 +64,17 @@ Auth.init().then(() => {
})
),
},
+ "/m/:site/:titleId/:chapterId": {
+ render: (vnode) =>
+ m(
+ Layout,
+ m(Chapter, {
+ site: vnode.attrs.site,
+ titleId: vnode.attrs.titleId,
+ chapterId: vnode.attrs.chapterId,
+ key: vnode.attrs.chapterId,
+ })
+ ),
+ },
});
});
diff --git a/src/pytaku/static/js/routes/chapter.js b/src/pytaku/static/js/routes/chapter.js
new file mode 100644
index 0000000..b48fbcd
--- /dev/null
+++ b/src/pytaku/static/js/routes/chapter.js
@@ -0,0 +1,95 @@
+import { Auth } from "../models.js";
+import { LoadingMessage, fullChapterName, Button } from "../utils.js";
+
+function Chapter(initialVNode) {
+ let isLoading = false;
+ let chapter = {};
+
+ return {
+ oninit: (vnode) => {
+ document.title = "Manga chapter";
+
+ isLoading = true;
+ m.redraw();
+
+ Auth.request({
+ method: "GET",
+ url: "/api/chapter/:site/:titleId/:chapterId",
+ params: {
+ site: vnode.attrs.site,
+ titleId: vnode.attrs.titleId,
+ chapterId: vnode.attrs.chapterId,
+ },
+ })
+ .then((resp) => {
+ chapter = resp;
+ document.title = fullChapterName(chapter);
+ })
+ .finally(() => {
+ isLoading = false;
+ });
+ },
+ view: (vnode) => {
+ if (isLoading) {
+ return m("div.chapter.content", m(LoadingMessage));
+ }
+
+ const { site, titleId } = vnode.attrs;
+ const prev = chapter.prev_chapter;
+ const next = chapter.next_chapter;
+ const buttons = m("div.chapter--buttons", [
+ prev
+ ? m(
+ m.route.Link,
+ {
+ class: "touch-friendly",
+ href: `/m/${site}/${titleId}/${prev.id}`,
+ },
+ [m("i.icon.icon-chevrons-left"), m("span", "prev")]
+ )
+ : m(Button, {
+ text: "prev",
+ icon: "chevrons-left",
+ disabled: true,
+ }),
+ m(
+ m.route.Link,
+ {
+ class: "touch-friendly",
+ href: `/m/${site}/${titleId}`,
+ },
+ [m("i.icon.icon-list"), m("span", " chapter list")]
+ ),
+ next
+ ? m(
+ m.route.Link,
+ {
+ class: "touch-friendly",
+ href: `/m/${site}/${titleId}/${next.id}`,
+ },
+ [m("span", "next"), m("i.icon.icon-chevrons-right")]
+ )
+ : m(Button, {
+ text: "next",
+ icon: "chevrons-right",
+ disabled: true,
+ }),
+ ]);
+ return m("div.chapter.content", [
+ m("h1", fullChapterName(chapter)),
+ buttons,
+ m(
+ "div",
+ {
+ class:
+ "chapter--pages" + chapter.is_webtoon ? " chapter--webtoon" : "",
+ },
+ [chapter.pages.map((page) => m("img", { src: page }))]
+ ),
+ buttons,
+ ]);
+ },
+ };
+}
+
+export default Chapter;
diff --git a/src/pytaku/static/lookandfeel.css b/src/pytaku/static/lookandfeel.css
index 018294e..b874b45 100644
--- a/src/pytaku/static/lookandfeel.css
+++ b/src/pytaku/static/lookandfeel.css
@@ -28,6 +28,7 @@ h4 {
h1 {
font-size: 2rem;
+ line-height: 2rem;
}
h2 {
@@ -60,7 +61,6 @@ p {
button,
a.touch-friendly {
- display: inline-block;
user-select: none;
margin: 0;
display: inline-flex;
@@ -77,10 +77,12 @@ button {
background-color: var(--btn-gray);
border-bottom: 4px solid var(--btn-gray-bottom);
}
-button > i {
+button > *,
+a.touch-friendly > * {
margin-right: 0.3rem;
}
-button > i:last-child {
+button > *:last-child,
+a.touch-friendly > *:last-child {
margin-right: 0;
}
button:hover {
@@ -115,7 +117,7 @@ button[disabled]:active {
}
a.touch-friendly {
background-color: white;
- color: inherit;
+ color: black;
border: 1px solid #aaa;
text-decoration: none;
border-bottom: 4px solid grey;
diff --git a/src/pytaku/static/spa.css b/src/pytaku/static/spa.css
index 6fbb584..f772a66 100644
--- a/src/pytaku/static/spa.css
+++ b/src/pytaku/static/spa.css
@@ -94,8 +94,13 @@ .nav--link i {
}
/* Route content common styling */
+#spa-root {
+ display: flex;
+ flex-direction: column;
+}
.content {
padding: var(--body-padding);
+ flex-grow: 1;
}
.content > * {
display: block;
@@ -279,6 +284,38 @@ .title--details {
margin: 1rem 0;
}
+/* Chapter route */
+.chapter.content {
+ padding: var(--body-padding) 0;
+ text-align: center;
+ background-color: #444;
+ color: white;
+}
+
+.chapter--pages img {
+ display: block;
+ margin: 0 auto 0.7rem auto;
+}
+.chapter--pages.chapter--webtoon img {
+ margin: 0 auto;
+}
+
+.chapter--buttons {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+.chapter--buttons > a,
+.chapter--buttons > button {
+ margin-right: 0.5rem;
+ margin-top: 0.2rem;
+ margin-bottom: 0.2rem;
+}
+.chapter--buttons > a:last-child,
+.chapter--buttons > button:last-child {
+ margin-right: 0;
+}
+
/* Components defined in utils */
.utils--chapter {
margin-bottom: 0.5rem;