show post preview
This commit is contained in:
parent
59d0dd3a84
commit
33f02c2ece
7 changed files with 177 additions and 53 deletions
|
@ -3,11 +3,18 @@ query GetPost($id: ID!) {
|
||||||
title
|
title
|
||||||
createdAt
|
createdAt
|
||||||
readingTime
|
readingTime
|
||||||
|
uniqueSlug
|
||||||
creator {
|
creator {
|
||||||
name
|
name
|
||||||
id
|
id
|
||||||
imageId
|
imageId
|
||||||
}
|
}
|
||||||
|
previewImage {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
previewContent {
|
||||||
|
subtitle
|
||||||
|
}
|
||||||
content {
|
content {
|
||||||
bodyModel {
|
bodyModel {
|
||||||
paragraphs {
|
paragraphs {
|
||||||
|
|
|
@ -3,63 +3,84 @@ type Query {
|
||||||
}
|
}
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
}
|
}
|
||||||
|
|
||||||
type Paragraphs {
|
type Paragraphs {
|
||||||
text: String!
|
text: String!
|
||||||
type: String!
|
type: String!
|
||||||
href: String
|
href: String
|
||||||
layout: String
|
layout: String
|
||||||
iframe: IFrame
|
iframe: IFrame
|
||||||
metadata: MetaData
|
metadata: MetaData
|
||||||
markups: [MarkUp! ]!
|
markups: [MarkUp!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarkUp {
|
type MarkUp {
|
||||||
title: String
|
title: String
|
||||||
type: String!
|
type: String!
|
||||||
href: String
|
href: String
|
||||||
userId: String
|
userId: String
|
||||||
start: Int!
|
start: Int!
|
||||||
end: Int!
|
end: Int!
|
||||||
anchorType: String
|
anchorType: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type IFrame {
|
type IFrame {
|
||||||
mediaResource: MediaResource
|
mediaResource: MediaResource
|
||||||
}
|
}
|
||||||
|
|
||||||
type MediaResource{
|
type MediaResource {
|
||||||
href: String!
|
href: String!
|
||||||
iframeSrc: String!
|
iframeSrc: String!
|
||||||
iframeWidth: Int!
|
iframeWidth: Int!
|
||||||
iframeHeight: Int
|
iframeHeight: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetaData {
|
type MetaData {
|
||||||
id: String!
|
id: String!
|
||||||
originalWidth: Int
|
originalWidth: Int
|
||||||
originalHeight: Int
|
originalHeight: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BodyModel {
|
||||||
type BodyModel { paragraphs: [Paragraphs! ]! }
|
paragraphs: [Paragraphs!]!
|
||||||
|
|
||||||
type Content { bodyModel: BodyModel! }
|
|
||||||
|
|
||||||
type User { id: String! name: String! imageId: String! }
|
|
||||||
|
|
||||||
type Post {
|
|
||||||
id: ID!
|
|
||||||
readingTime: Float!
|
|
||||||
title: String!
|
|
||||||
createdAt: Int!
|
|
||||||
content: Content!
|
|
||||||
creator: User!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data { post: Post }
|
type Content {
|
||||||
|
bodyModel: BodyModel!
|
||||||
|
}
|
||||||
|
|
||||||
type AutogeneratedMainType { data: Data }
|
type User {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
imageId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Post {
|
||||||
|
id: ID!
|
||||||
|
readingTime: Float!
|
||||||
|
title: String!
|
||||||
|
createdAt: Int!
|
||||||
|
content: Content!
|
||||||
|
creator: User!
|
||||||
|
previewImage: PreviewImage
|
||||||
|
previewContent: PreviewContent
|
||||||
|
uniqueSlug: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewImage {
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewContent {
|
||||||
|
subtitle: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data {
|
||||||
|
post: Post
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutogeneratedMainType {
|
||||||
|
data: Data
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ use sled::{Db, Tree};
|
||||||
use crate::proxy::StringUtils;
|
use crate::proxy::StringUtils;
|
||||||
use crate::SETTINGS;
|
use crate::SETTINGS;
|
||||||
|
|
||||||
const POST_CACHE_VERSION: usize = 1;
|
const POST_CACHE_VERSION: usize = 2;
|
||||||
const GIST_CACHE_VERSION: usize = 1;
|
const GIST_CACHE_VERSION: usize = 1;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -53,6 +53,10 @@ impl PostResp {
|
||||||
pub fn get_gist_id<'a>(&self, url: &'a str) -> &'a str {
|
pub fn get_gist_id<'a>(&self, url: &'a str) -> &'a str {
|
||||||
url.split('/').last().unwrap()
|
url.split('/').last().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_subtitle(&self) -> &str {
|
||||||
|
self.preview_content.as_ref().unwrap().subtitle.as_str()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
|
@ -111,7 +115,7 @@ impl Data {
|
||||||
for (tree, key, current_version) in trees {
|
for (tree, key, current_version) in trees {
|
||||||
if let Ok(Some(v)) = tree.get(key) {
|
if let Ok(Some(v)) = tree.get(key) {
|
||||||
let version = bincode::deserialize::<usize>(&v[..]).unwrap();
|
let version = bincode::deserialize::<usize>(&v[..]).unwrap();
|
||||||
let clean = !(version == current_version);
|
let clean = version != current_version;
|
||||||
|
|
||||||
if clean {
|
if clean {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
23
src/proxy.rs
23
src/proxy.rs
|
@ -17,6 +17,7 @@
|
||||||
use std::ops::{Bound, RangeBounds};
|
use std::ops::{Bound, RangeBounds};
|
||||||
|
|
||||||
use actix_web::{http::header, web, HttpResponse, Responder};
|
use actix_web::{http::header, web, HttpResponse, Responder};
|
||||||
|
use chrono::{TimeZone, Utc};
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::CONTENT_TYPE;
|
||||||
use sailfish::TemplateOnce;
|
use sailfish::TemplateOnce;
|
||||||
|
@ -110,6 +111,9 @@ impl StringUtils for str {
|
||||||
#[template(rm_whitespace = true)]
|
#[template(rm_whitespace = true)]
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
pub data: PostResp,
|
pub data: PostResp,
|
||||||
|
pub date: String,
|
||||||
|
pub preview_img: String,
|
||||||
|
pub reading_time: usize,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub gists: Option<Vec<(String, crate::data::GistContent)>>,
|
pub gists: Option<Vec<(String, crate::data::GistContent)>>,
|
||||||
}
|
}
|
||||||
|
@ -166,7 +170,7 @@ async fn page(path: web::Path<(String, String)>, data: AppData) -> impl Responde
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.href;
|
.href;
|
||||||
if src.contains("gist.github.com") {
|
if src.contains("gist.github.com") {
|
||||||
let gist_id = post_data.get_gist_id(&src);
|
let gist_id = post_data.get_gist_id(src);
|
||||||
let fut = data.get_gist(gist_id.to_owned());
|
let fut = data.get_gist(gist_id.to_owned());
|
||||||
futs.push(fut);
|
futs.push(fut);
|
||||||
}
|
}
|
||||||
|
@ -179,10 +183,27 @@ async fn page(path: web::Path<(String, String)>, data: AppData) -> impl Responde
|
||||||
Some(x)
|
Some(x)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let date = Utc
|
||||||
|
.timestamp_millis(post_data.created_at)
|
||||||
|
.format("%b %e, %Y")
|
||||||
|
.to_string();
|
||||||
|
let reading_time = post_data.reading_time.floor() as usize;
|
||||||
|
let preview_img = post_data
|
||||||
|
.preview_image
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.id
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let preview_img = crate::V1_API_ROUTES.proxy.get_medium_asset(preview_img);
|
||||||
|
|
||||||
let page = Post {
|
let page = Post {
|
||||||
id: id.to_owned(),
|
id: id.to_owned(),
|
||||||
data: post_data,
|
data: post_data,
|
||||||
|
date,
|
||||||
gists,
|
gists,
|
||||||
|
reading_time,
|
||||||
|
preview_img,
|
||||||
};
|
};
|
||||||
|
|
||||||
let page = page.render_once().unwrap();
|
let page = page.render_once().unwrap();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<. let gist_id = data.get_gist_id(&src); .>
|
<. let gist_id = data.get_gist_id(src); .>
|
||||||
<. let (_, gist)= gists.as_ref().unwrap().iter().find(|(id, _)| id == gist_id).as_ref().unwrap(); .>
|
<. let (_, gist)= gists.as_ref().unwrap().iter().find(|(id, _)| id == gist_id).as_ref().unwrap(); .>
|
||||||
<. for file in &gist.files {.>
|
<. for file in &gist.files {.>
|
||||||
<code> <.= file.get_html_content() .> </code>
|
<code> <.= file.get_html_content() .> </code>
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<. include!("./post_meta.html"); .>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title><.= data.title .></title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<. use chrono::{TimeZone, Utc}; .>
|
|
||||||
<. let dt = Utc.timestamp_millis(data.created_at); .>
|
|
||||||
<. let date = dt.format("%b %e, %Y").to_string(); .>
|
|
||||||
<h1><.= data.title .></h1>
|
<h1><.= data.title .></h1>
|
||||||
<p class="meta">
|
<p class="meta">
|
||||||
<a class="author" href="https://medium.com/u/<.= data.creator.id .>" rel="noreferrer">
|
<a class="author" href="https://medium.com/u/<.= data.creator.id .>" rel="noreferrer">
|
||||||
|
@ -20,7 +15,7 @@
|
||||||
/>
|
/>
|
||||||
<.= data.creator.name .></a
|
<.= data.creator.name .></a
|
||||||
>
|
>
|
||||||
on <.= &date .> · <.= data.reading_time.floor() as usize .> min read
|
on <.= &date .> · <.= reading_time .> min read
|
||||||
</p>
|
</p>
|
||||||
<article>
|
<article>
|
||||||
<. let paragraphs = &data.content.body_model.paragraphs; .>
|
<. let paragraphs = &data.content.body_model.paragraphs; .>
|
||||||
|
|
76
templates/post_meta.html
Normal file
76
templates/post_meta.html
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<title><.= data.title .> | by <.= data.creator.name .> | <.= &date .></title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width,minimum-scale=1,initial-scale=1,maximum-scale=1"
|
||||||
|
/>
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="twitter:app:name:iphone" content="libmedium" />
|
||||||
|
<meta property="og:site_name" content="libmedium" />
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="article:published_time" content="2021-10-21T10:54:12.523Z" />
|
||||||
|
<meta
|
||||||
|
name="title"
|
||||||
|
content="Moscow state university network built by students | by Pavel Safronov | Oct, 2021 | libmedium"
|
||||||
|
/>
|
||||||
|
<meta property="og:title" content="<.= data.title .>" />
|
||||||
|
<meta property="twitter:title" content="<.= data.title .>" />
|
||||||
|
<meta name="twitter:app:url:iphone" content="medium://p/211539855cf9" />
|
||||||
|
<meta data-rh="true" property="al:android:app_name" content="libmedium" />
|
||||||
|
<meta name="description" content="<.= data.get_subtitle() .>" />
|
||||||
|
<meta property="og:description" content="<.= data.get_subtitle() .>" />
|
||||||
|
<meta property="twitter:description" content="<.= data.get_subtitle() .>" />
|
||||||
|
<meta
|
||||||
|
property="og:url"
|
||||||
|
content="http://<.= &*crate::SETTINGS.server.domain .>/<.= data.creator.name .>/<.= data.unique_slug .>"
|
||||||
|
/>
|
||||||
|
<meta property="og:image" content="<.= preview_img .>" />
|
||||||
|
<meta name="twitter:image:src" content="<.= preview_img .>" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta
|
||||||
|
property="article:author"
|
||||||
|
content="https://medium.com/@<.= data.creator.id .>"
|
||||||
|
/>
|
||||||
|
<!--
|
||||||
|
<meta name="twitter:creator" content="@username" />
|
||||||
|
-->
|
||||||
|
<meta name="author" content="<.= data.creator.name .>" />
|
||||||
|
<meta
|
||||||
|
data-rh="true"
|
||||||
|
name="robots"
|
||||||
|
content="index,follow,max-image-preview:large"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:label1" content="Reading time" />
|
||||||
|
<meta name="twitter:data1" content="<.= reading_time .>min read" />
|
||||||
|
<!--
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="152x152"
|
||||||
|
href="https://miro.medium.com/fit/c/152/152/1*sHhtYhaCe2Uc3IU0IgKwIQ.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="120x120"
|
||||||
|
href="https://miro.medium.com/fit/c/120/120/1*sHhtYhaCe2Uc3IU0IgKwIQ.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="76x76"
|
||||||
|
href="https://miro.medium.com/fit/c/76/76/1*sHhtYhaCe2Uc3IU0IgKwIQ.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="60x60"
|
||||||
|
href="https://miro.medium.com/fit/c/60/60/1*sHhtYhaCe2Uc3IU0IgKwIQ.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="mask-icon"
|
||||||
|
href="https://cdn-static-1.medium.com/_/fp/icons/libmedium-Avatar-500x500.svg"
|
||||||
|
color="#171717"
|
||||||
|
/>
|
||||||
|
-->
|
||||||
|
<link rel="author" href="https://medium.com/<.= data.creator.name .>" />
|
||||||
|
<link
|
||||||
|
rel="canonical"
|
||||||
|
href="http://<.= &*crate::SETTINGS.server.domain .>/<.= data.creator.name .>/<.= data.unique_slug .>"
|
||||||
|
/>
|
Loading…
Reference in a new issue