Merge pull request 'Add shortcut links to edit page' (#883) from shortcut-links into main

Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/883
This commit is contained in:
KitaitiMakoto 2021-01-24 17:16:11 +00:00
commit 76f7b5e7ac
12 changed files with 505 additions and 313 deletions

View file

@ -21,6 +21,7 @@ executors:
RUST_TEST_THREADS: 1
FEATURES: <<#parameters.postgres>>postgres<</ parameters.postgres>><<^parameters.postgres>>sqlite<</parameters.postgres>>
DATABASE_URL: <<#parameters.postgres>>postgres://postgres@localhost/plume<</parameters.postgres>><<^parameters.postgres>>plume.sqlite<</parameters.postgres>>
ROCKET_SECRET_KEY: VN5xV1DN7XdpATadOCYcuGeR/dV0hHfgx9mx9TarLdM=
commands:
@ -143,12 +144,14 @@ jobs:
cache: <<#parameters.postgres>>postgres<</ parameters.postgres>><<^parameters.postgres>>sqlite<</parameters.postgres>>
- run_with_coverage:
cmd: |
cargo run -p plume-cli --no-default-features --features=${FEATURES} -- migration run
cargo build -p plume-cli --no-default-features --features=${FEATURES} -j1
./target/debug/plm migration run
./target/debug/plm search init
cmd="cargo test --all --exclude plume-front --exclude plume-macro --no-run --no-default-features --features=${FEATURES} -j"
for i in 36 4 2 1 1; do
$cmd $i && break
done
cargo test --all --exclude plume-front --exclude plume-macro --no-default-features --features="${FEATURES}" -j1 -- --test-threads=1
cargo test --all --exclude plume-front --exclude plume-macro --no-default-features --features="${FEATURES}" -j1
- upload_coverage:
type: unit
- cache:

View file

@ -1,3 +1,3 @@
* {
font-family: monospace;
font-family: monospace;
}

View file

@ -64,37 +64,37 @@ main header.article {
}
main .article-info {
margin: 0 auto 3em;
font-size: 0.95em;
font-weight: 400;
margin: 0 auto 3em;
font-size: 0.95em;
font-weight: 400;
.author, .author a {
font-weight: 600;
}
.author, .author a {
font-weight: 600;
}
}
/* The article itself */
main article {
max-width: $article-width;
margin: 2.5em auto;
font-family: $lora;
font-size: 1.2em;
line-height: 1.7;
margin: 2.5em auto;
font-family: $lora;
font-size: 1.2em;
line-height: 1.7;
a:hover {
text-decoration: underline;
}
a:hover {
text-decoration: underline;
}
img {
display: block;
margin: 3em auto;
max-width: 100%;
img {
display: block;
margin: 3em auto;
max-width: 100%;
}
pre {
padding: 1em;
background: $gray;
overflow: auto;
padding: 1em;
background: $gray;
overflow: auto;
}
blockquote {
@ -126,7 +126,7 @@ main .article-meta {
> p {
margin: 2em $horizontal-margin;
font-size: 0.9em;
font-size: 0.9em;
}
/* Article Tags */
@ -157,15 +157,15 @@ main .article-meta {
/* Likes & Boosts */
.actions {
display: flex;
flex-direction: row;
justify-content: space-around;
flex-direction: row;
justify-content: space-around;
}
.likes, .reshares {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.5em 0;
flex-direction: column;
align-items: center;
padding: 0.5em 0;
p {
font-size: 1.5em;
@ -175,34 +175,34 @@ main .article-meta {
.action {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
background: none;
color: $text-color;
border: none;
font-size: 1.1em;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
background: none;
color: $text-color;
border: none;
font-size: 1.1em;
cursor: pointer;
svg.feather {
transition: background 0.1s ease-in;
display: flex;
align-items: center;
justify-content: center;
svg.feather {
transition: background 0.1s ease-in;
display: flex;
align-items: center;
justify-content: center;
margin: 0.5em 0;
width: 2.5em;
height: 2.5em;
margin: 0.5em 0;
width: 2.5em;
height: 2.5em;
border-radius: 50%;
border-radius: 50%;
}
&.reshared, &.liked {
svg.feather {
color: $background;
font-weight: 900;
color: $background;
font-weight: 900;
}
}
}
@ -213,14 +213,14 @@ main .article-meta {
.action svg.feather {
padding: 0.7em;
box-sizing: border-box;
color: $red;
fill: none;
border: solid $red thin;
box-sizing: border-box;
color: $red;
fill: none;
border: solid $red thin;
}
.action:hover svg.feather {
background: transparentize($red, 0.85);
background: transparentize($red, 0.85);
}
.action.liked svg.feather {
@ -238,22 +238,22 @@ main .article-meta {
.action svg.feather {
padding: 0.7em;
box-sizing: border-box;
color: $primary;
border: solid $primary thin;
font-weight: 600;
box-sizing: border-box;
color: $primary;
border: solid $primary thin;
font-weight: 600;
}
.action:hover svg.feather {
background: transparentize($primary, 0.85);
background: transparentize($primary, 0.85);
}
.action.reshared svg.feather {
background: $primary;
}
.action.reshared:hover svg.feather {
background: transparentize($primary, 0.75)
color: $primary;
background: transparentize($primary, 0.75)
color: $primary;
}
}
@ -262,9 +262,9 @@ main .article-meta {
margin: 0 $horizontal-margin;
h2 {
color: $primary;
font-size: 1.5em;
font-weight: 600;
color: $primary;
font-size: 1.5em;
font-weight: 600;
}
summary {
@ -279,16 +279,16 @@ main .article-meta {
// Respond & delete comment buttons
a.button, form.inline, form.inline input {
padding: 0;
background: none;
color: $text-color;
margin-right: 2em;
font-family: $route159;
padding: 0;
background: none;
color: $text-color;
margin-right: 2em;
font-family: $route159;
font-weight: normal;
&::before {
color: $primary;
padding-right: 0.5em;
&::before {
color: $primary;
padding-right: 0.5em;
}
&:hover { color: $primary; }
@ -296,8 +296,8 @@ main .article-meta {
.comment {
margin: 1em 0;
font-size: 1em;
border: none;
font-size: 1em;
border: none;
.content {
background: $gray;
@ -328,36 +328,36 @@ main .article-meta {
color: transparentize($text-color, 0.6);
}
.author {
display: flex;
flex-direction: row;
align-items: center;
align-content: center;
.author {
display: flex;
flex-direction: row;
align-items: center;
align-content: center;
* {
transition: all 0.1s ease-in;
}
* {
transition: all 0.1s ease-in;
}
.display-name {
color: $text-color;
.display-name {
color: $text-color;
}
&:hover {
.display-name { color: $primary; }
small { opacity: 1; }
}
}
}
& > .comment {
padding-left: 2em;
}
.text {
padding: 1.25em 0;
font-family: $lora;
font-size: 1.1em;
line-height: 1.4;
text-align: left;
.text {
padding: 1.25em 0;
font-family: $lora;
font-size: 1.1em;
line-height: 1.4;
text-align: left;
}
}
}

View file

@ -1,27 +1,27 @@
label {
display: block;
margin: 2em auto .5em;
font-size: 1.2em;
display: block;
margin: 2em auto .5em;
font-size: 1.2em;
}
input, textarea, select {
transition: all 0.1s ease-in;
display: block;
width: 100%;
margin: auto;
padding: 1em;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: block;
width: 100%;
margin: auto;
padding: 1em;
box-sizing: border-box;
-webkit-appearance: textarea;
background: $form-input-background;
color: $text-color;
border: solid $form-input-border thin;
background: $form-input-background;
color: $text-color;
border: solid $form-input-border thin;
font-size: 1.2em;
font-weight: 400;
font-size: 1.2em;
font-weight: 400;
&:focus {
border-color: $primary;
}
&:focus {
border-color: $primary;
}
}
form input[type="submit"] {
margin: 2em auto;
@ -29,18 +29,18 @@ form input[type="submit"] {
}
textarea {
resize: vertical;
resize: vertical;
overflow-y: scroll;
font-family: $lora;
font-size: 1.1em;
line-height: 1.5;
font-family: $lora;
font-size: 1.1em;
line-height: 1.5;
}
input[type="checkbox"] {
display: inline;
margin: initial;
min-width: initial;
width: initial;
display: inline;
margin: initial;
min-width: initial;
width: initial;
-webkit-appearance: checkbox;
}
@ -71,31 +71,31 @@ form.inline {
}
.button, .button:visited, input[type="submit"], input[type="submit"].button {
transition: all 0.1s ease-in;
display: inline-block;
transition: all 0.1s ease-in;
display: inline-block;
-webkit-appearance: none;
margin: 0.5em auto;
padding: 0.75em 1em;
margin: 0.5em auto;
padding: 0.75em 1em;
background: $primary;
color: $primary-text-color;
background: $primary;
color: $primary-text-color;
font-weight: bold;
border: none;
cursor: pointer;
cursor: pointer;
&:hover {
background: transparentize($primary, 0.1);
}
&:hover {
background: transparentize($primary, 0.1);
}
&.destructive {
background: $red;
&.destructive {
background: $red;
&:hover {
background: transparentize($red, 0.1);
}
}
}
&.secondary {
background: $gray;
@ -115,20 +115,20 @@ input[type="submit"] {
form.new-post {
max-width: 60em;
.title {
margin: 0 auto;
padding: 0.75em 0;
margin: 0 auto;
padding: 0.75em 0;
background: none;
border: none;
background: none;
border: none;
font-family: $playfair;
font-size: 2em;
text-align: left;
font-family: $playfair;
font-size: 2em;
text-align: left;
}
textarea {
min-height: 20em;
overflow-y: scroll;
resize: none;
min-height: 20em;
overflow-y: scroll;
resize: none;
-webkit-appearance: textarea;
}
}

View file

@ -6,43 +6,43 @@ html {
}
html, body {
margin: 0;
padding: 0;
background: $background;
color: $text-color;
font-family: $route159;
margin: 0;
padding: 0;
background: $background;
color: $text-color;
font-family: $route159;
::selection {
background: transparentize($primary, 0.7);
}
::-moz-selection {
::-moz-selection {
background: transparentize($primary, 0.7);
}
}
}
a, a:visited {
color: $primary;
text-decoration: none;
color: $primary;
text-decoration: none;
}
a::selection {
color: $background;
color: $background;
}
a::-moz-selection {
color: $background;
color: $background;
}
small {
margin-left: 1em;
color: transparentize($text-color, 0.6);
font-size: 0.75em;
word-wrap: break-word;
word-break: break-all;
margin-left: 1em;
color: transparentize($text-color, 0.6);
font-size: 0.75em;
word-wrap: break-word;
word-break: break-all;
}
.center {
text-align: center;
font-weight: bold;
opacity: 0.6;
padding: 5em;
text-align: center;
font-weight: bold;
opacity: 0.6;
padding: 5em;
}
.right {
@ -53,28 +53,28 @@ small {
}
.spaced {
margin: 4rem 0;
margin: 4rem 0;
}
.banner {
background: $gray;
padding-top: 2em;
padding-bottom: 1em;
margin: 3em 0px;
background: $gray;
padding-top: 2em;
padding-bottom: 1em;
margin: 3em 0px;
}
.hidden {
display: none;
appearance: none;
display: none;
appearance: none;
}
/* Main */
body > main > *, .h-feed > * {
margin: 1em $horizontal-margin;
margin: 1em $horizontal-margin;
}
body > main > .h-entry, .h-feed {
margin: 0;
margin: 0;
}
body > main {
@ -98,18 +98,18 @@ main {
margin-top: 1em;
&.article {
margin: 1em auto 0.5em;
font-family: $playfair;
font-size: 2.5em;
font-weight: normal;
margin: 1em auto 0.5em;
font-family: $playfair;
font-size: 2.5em;
font-weight: normal;
}
}
h2 {
font-size: 1.75em;
font-weight: 300;
font-size: 1.75em;
font-weight: 300;
&.article {
&.article {
font-size: 1.25em;
margin-bottom: 0.5em;
}
@ -139,15 +139,15 @@ main {
/* Errors */
p.error {
color: $red;
font-weight: bold;
color: $red;
font-weight: bold;
}
/* User page */
.user h1 {
display: flex;
flex-direction: row;
align-items: center;
display: flex;
flex-direction: row;
align-items: center;
margin: 0px;
}
@ -156,14 +156,14 @@ p.error {
}
.badge {
margin-right: 1em;
padding: 0.35em 1em;
margin-right: 1em;
padding: 0.35em 1em;
background: $background;
color: $primary;
border: 1px solid $primary;
background: $background;
color: $primary;
border: 1px solid $primary;
font-size: 1rem;
font-size: 1rem;
}
.user-summary {
@ -172,23 +172,25 @@ p.error {
/* Cards */
.cards {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 0 5%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 0 5%;
margin: 1rem 0 5rem;
}
.card {
flex: 1;
display: flex;
flex-direction: column;
flex: 1;
display: flex;
flex-direction: column;
min-width: 20em;
min-height: 20em;
margin: 1em;
box-sizing: border-box;
position: relative;
background: $gray;
min-width: 20em;
min-height: 20em;
margin: 1em;
box-sizing: border-box;
background: $gray;
text-overflow: ellipsis;
@ -213,11 +215,11 @@ p.error {
}
> * {
margin: 20px;
}
> * {
margin: 20px;
}
.cover {
.cover {
min-height: 10em;
background-position: center;
background-size: cover;
@ -225,26 +227,38 @@ p.error {
}
h3 {
margin: 0.75em 20px;
font-family: $playfair;
font-size: 1.75em;
font-weight: normal;
a {
transition: color 0.1s ease-in;
color: $text-color;
flex-grow: 1;
margin: 0;
font-family: $playfair;
font-size: 1.75em;
font-weight: normal;
line-height: 1.75;
a {
display: block;
transition: color 0.1s ease-in;
color: $text-color;
&:hover { color: $primary; }
}
&:hover { color: $primary; }
}
}
.controls {
float: right;
.button {
margin-top: 0;
margin-bottom: 0;
}
}
main {
flex: 1;
flex: 1;
font-family: $lora;
font-size: 1em;
line-height: 1.25;
text-align: left;
overflow: hidden;
font-family: $lora;
font-size: 1em;
line-height: 1.25;
text-align: left;
overflow: hidden;
}
}
@ -286,15 +300,15 @@ p.error {
/* Stats */
.stats {
display: flex;
justify-content: space-around;
margin: 2em;
display: flex;
justify-content: space-around;
margin: 2em;
> div {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> div {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
p {

View file

@ -3,8 +3,8 @@ body > header {
#content {
display: flex;
align-content: center;
justify-content: space-between;
align-content: center;
justify-content: space-between;
}
nav#menu {
@ -19,44 +19,44 @@ body > header {
a {
transform: skewX(15deg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 1.4em;
height: 1.4em;
margin: 0;
padding: 0;
color: $gray;
font-size: 1.33em;
}
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 1.4em;
height: 1.4em;
margin: 0;
padding: 0;
color: $gray;
font-size: 1.33em;
}
}
nav {
display: flex;
flex-direction: row;
align-items: center;
flex-direction: row;
align-items: center;
hr {
height: 100%;
width: 0.2em;
background: $primary;
border: none;
transform: skewX(-15deg);
hr {
height: 100%;
width: 0.2em;
background: $primary;
border: none;
transform: skewX(-15deg);
}
a {
display: flex;
align-items: center;
position: relative;
align-self: stretch;
margin: 0;
padding: 0 2em;
font-size: 1em;
display: flex;
align-items: center;
position: relative;
align-self: stretch;
margin: 0;
padding: 0 2em;
font-size: 1em;
i { font-size: 1.2em; }
i { font-size: 1.2em; }
&.title {
margin: 0;
&.title {
margin: 0;
text-align: center;
padding: 0.5em 1em;
font-size: 1.75em;
@ -70,7 +70,7 @@ body > header {
margin: 0;
padding-left: 0.5em;
}
}
}
}
}
}
@ -205,36 +205,36 @@ body > header {
/* Only enable label animations on large screens */
@media screen and (min-width: 600px) {
header nav a {
i {
transition: all 0.2s ease;
margin: 0;
}
.mobile-label {
transition: all 0.2s ease;
display: block;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
transform: translateZ(0);
-webkit-transform: none !important;
opacity: 0;
font-size: 0.9em;
white-space: nowrap;
}
img + .mobile-label { display: none; }
&:hover {
i { margin-bottom: 0.75em; }
.mobile-label {
opacity: 1;
transform: translate(-50%, 80%);
-webkit-transform: translate(-50%, 80%);
}
header nav a {
i {
transition: all 0.2s ease;
margin: 0;
}
}
.mobile-label {
transition: all 0.2s ease;
display: block;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
transform: translateZ(0);
-webkit-transform: none !important;
opacity: 0;
font-size: 0.9em;
white-space: nowrap;
}
img + .mobile-label { display: none; }
&:hover {
i { margin-bottom: 0.75em; }
.mobile-label {
opacity: 1;
transform: translate(-50%, 80%);
-webkit-transform: translate(-50%, 80%);
}
}
}
}
// Small screens

View file

@ -247,6 +247,7 @@ pub(crate) mod tests {
use diesel::Connection;
pub(crate) fn fill_database(conn: &Conn) -> Vec<(NewInstance, Instance)> {
diesel::delete(instances::table).execute(conn).unwrap();
let res = vec![
NewInstance {
default_license: "WTFPL".to_string(),

View file

@ -12,6 +12,7 @@ use activitypub::{
use chrono::{NaiveDateTime, TimeZone, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use heck::KebabCase;
use once_cell::sync::Lazy;
use plume_common::{
activity_pub::{
inbox::{AsObject, FromId},
@ -20,11 +21,13 @@ use plume_common::{
utils::md_to_html,
};
use riker::actors::{Publish, Tell};
use std::collections::HashSet;
use std::sync::Arc;
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
pub type LicensedArticle = CustomObject<Licensed, Article>;
static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
#[derive(Queryable, Identifiable, Clone, AsChangeset, Debug)]
#[changeset_options(treat_none_as_null = "true")]
pub struct Post {
@ -275,6 +278,24 @@ impl Post {
.map_err(Error::from)
}
/// This method exists for use in templates to reduce database access.
/// This should not be used for other purpose.
///
/// This caches query result. The best way to cache query result is holding it in `Post`s field
/// but Diesel doesn't allow it currently.
/// If sometime Diesel allow it, this method should be removed.
pub fn get_blog_fqn(&self, conn: &Connection) -> String {
if let Some(blog_fqn) = BLOG_FQN_CACHE.lock().unwrap().get(&self.blog_id) {
return blog_fqn.to_string();
}
let blog_fqn = self.get_blog(conn).unwrap().fqn;
BLOG_FQN_CACHE
.lock()
.unwrap()
.insert(self.blog_id, blog_fqn.clone());
blog_fqn
}
pub fn count_likes(&self, conn: &Connection) -> Result<i64> {
use crate::schema::likes;
likes::table

View file

@ -79,7 +79,6 @@ impl ActorFactoryArgs<(Arc<Searcher>, DbPool)> for SearchActor {
#[cfg(test)]
mod tests {
use crate::diesel::Connection;
use crate::diesel::RunQueryDsl;
use crate::{
blog_authors::{BlogAuthor, NewBlogAuthor},
blogs::{Blog, NewBlog},
@ -114,7 +113,7 @@ mod tests {
let conn = db_pool.clone().get().unwrap();
let title = random_hex()[..8].to_owned();
let (instance, user, blog) = fill_database(&conn);
let (_instance, _user, blog) = fill_database(&conn);
let author = &blog.list_authors(&conn).unwrap()[0];
let post = Post::insert(
@ -151,11 +150,6 @@ mod tests {
searcher.search_document(&conn, Query::from_str(&title).unwrap(), (0, 1))[0].id,
post_id
);
// TODO: Make sure records are deleted even when assertion failed
post.delete(&conn).unwrap();
blog.delete(&conn).unwrap();
user.delete(&conn).unwrap();
diesel::delete(&instance).execute(&conn).unwrap();
}
fn fill_database(conn: &Conn) -> (Instance, User, Blog) {
@ -164,7 +158,7 @@ mod tests {
conn,
NewInstance {
default_license: "CC-0-BY-SA".to_string(),
local: true,
local: false,
long_description: SafeString::new("Good morning"),
long_description_html: "<p>Good morning</p>".to_string(),
short_description: SafeString::new("Hello"),
@ -175,14 +169,29 @@ mod tests {
},
)
.unwrap();
let mut user = NewUser::default();
user.instance_id = instance.id;
user.username = random_hex().to_string();
user.ap_url = random_hex().to_string();
user.inbox_url = random_hex().to_string();
user.outbox_url = random_hex().to_string();
user.followers_endpoint = random_hex().to_string();
let user = User::insert(conn, user).unwrap();
let user = User::insert(
conn,
NewUser {
username: random_hex().to_string(),
display_name: random_hex().to_string(),
outbox_url: random_hex().to_string(),
inbox_url: random_hex().to_string(),
summary: "".to_string(),
email: None,
hashed_password: None,
instance_id: instance.id,
ap_url: random_hex().to_string(),
private_key: None,
public_key: "".to_string(),
shared_inbox_url: None,
followers_endpoint: random_hex().to_string(),
avatar_id: None,
summary_html: SafeString::new(""),
role: 0,
fqn: random_hex().to_string(),
},
)
.unwrap();
let mut blog = NewBlog::default();
blog.instance_id = instance.id;
blog.actor_id = random_hex().to_string();

View file

@ -60,7 +60,7 @@ fn init_pool() -> Option<DbPool> {
Some(pool)
}
fn main() {
pub(crate) fn init_rocket() -> rocket::Rocket {
match dotenv::dotenv() {
Ok(path) => eprintln!("Configuration read from {}", path.display()),
Err(ref e) if e.not_found() => eprintln!("no .env was found"),
@ -126,7 +126,7 @@ Then try to restart Plume.
warn!("Please refer to the documentation to see how to configure it.");
}
let rocket = rocket::custom(CONFIG.rocket.clone().unwrap())
rocket::custom(CONFIG.rocket.clone().unwrap())
.mount(
"/",
routes![
@ -268,9 +268,14 @@ Then try to restart Plume.
])
.finalize()
.expect("main: csrf fairing creation error"),
);
)
}
fn main() {
let rocket = init_rocket();
#[cfg(feature = "test")]
let rocket = rocket.mount("/test", routes![test_routes::health,]);
rocket.launch();
}

View file

@ -371,3 +371,135 @@ pub fn atom_feed(name: String, rockets: PlumeRocket) -> Option<Content<String>>
feed.to_string(),
))
}
#[cfg(test)]
mod tests {
use crate::init_rocket;
use diesel::Connection;
use plume_common::utils::random_hex;
use plume_models::{
blog_authors::{BlogAuthor, NewBlogAuthor},
blogs::{Blog, NewBlog},
db_conn::{DbConn, DbPool},
instance::{Instance, NewInstance},
post_authors::{NewPostAuthor, PostAuthor},
posts::{NewPost, Post},
safe_string::SafeString,
users::{NewUser, User, AUTH_COOKIE},
};
use rocket::{
http::{Cookie, Cookies, SameSite},
local::{Client, LocalRequest},
};
#[test]
fn edit_link_within_post_card() {
let rocket = init_rocket();
let client = Client::new(rocket).expect("valid rocket instance");
let dbpool = client.rocket().state::<DbPool>().unwrap();
let conn = &DbConn(dbpool.get().unwrap());
let (_instance, user, blog, post) = create_models(conn);
let blog_path = uri!(super::activity_details: name = &blog.fqn).to_string();
let edit_link = uri!(
super::super::posts::edit: blog = &blog.fqn,
slug = &post.slug
)
.to_string();
let mut response = client.get(&blog_path).dispatch();
let body = response.body_string().unwrap();
assert!(!body.contains(&edit_link));
let request = client.get(&blog_path);
login(&request, &user);
let mut response = request.dispatch();
let body = response.body_string().unwrap();
assert!(body.contains(&edit_link));
}
fn create_models(conn: &DbConn) -> (Instance, User, Blog, Post) {
conn.transaction::<(Instance, User, Blog, Post), diesel::result::Error, _>(|| {
let instance = Instance::get_local().unwrap_or_else(|_| {
let instance = Instance::insert(
conn,
NewInstance {
default_license: "CC-0-BY-SA".to_string(),
local: true,
long_description: SafeString::new("Good morning"),
long_description_html: "<p>Good morning</p>".to_string(),
short_description: SafeString::new("Hello"),
short_description_html: "<p>Hello</p>".to_string(),
name: random_hex().to_string(),
open_registrations: true,
public_domain: random_hex().to_string(),
},
)
.unwrap();
Instance::cache_local(conn);
instance
});
let mut user = NewUser::default();
user.instance_id = instance.id;
user.username = random_hex().to_string();
user.ap_url = random_hex().to_string();
user.inbox_url = random_hex().to_string();
user.outbox_url = random_hex().to_string();
user.followers_endpoint = random_hex().to_string();
let user = User::insert(conn, user).unwrap();
let mut blog = NewBlog::default();
blog.instance_id = instance.id;
blog.actor_id = random_hex().to_string();
blog.ap_url = random_hex().to_string();
blog.inbox_url = random_hex().to_string();
blog.outbox_url = random_hex().to_string();
let blog = Blog::insert(conn, blog).unwrap();
BlogAuthor::insert(
conn,
NewBlogAuthor {
blog_id: blog.id,
author_id: user.id,
is_owner: true,
},
)
.unwrap();
let post = Post::insert(
conn,
NewPost {
blog_id: blog.id,
slug: random_hex()[..8].to_owned(),
title: random_hex()[..8].to_owned(),
content: SafeString::new(""),
published: true,
license: "CC-By-SA".to_owned(),
ap_url: "".to_owned(),
creation_date: None,
subtitle: "".to_owned(),
source: "".to_owned(),
cover_id: None,
},
)
.unwrap();
PostAuthor::insert(
conn,
NewPostAuthor {
post_id: post.id,
author_id: user.id,
},
)
.unwrap();
Ok((instance, user, blog, post))
})
.unwrap()
}
fn login(request: &LocalRequest, user: &User) {
request.inner().guard::<Cookies>().unwrap().add_private(
Cookie::build(AUTH_COOKIE, user.id.to_string())
.same_site(SameSite::Lax)
.finish(),
);
}
}

View file

@ -8,11 +8,18 @@
@if article.cover_id.is_some() {
<div class="cover" style="background-image: url('@Html(article.cover_url(ctx.0).unwrap_or_default())')"></div>
}
<h3 class="p-name" dir="auto">
<a class="u-url" href="@uri!(posts::details: blog = article.get_blog(ctx.0).unwrap().fqn, slug = &article.slug, responding_to = _)">
@article.title
</a>
</h3>
<header>
@if ctx.2.clone().and_then(|u| article.is_author(ctx.0, u.id).ok()).unwrap_or(false) {
<div class="controls">
<a class="button" href="@uri!(posts::edit: blog = &article.get_blog_fqn(ctx.0), slug = &article.slug)">@i18n!(ctx.1, "Edit")</a>
</div>
}
<h3 class="p-name" dir="auto">
<a class="u-url" href="@uri!(posts::details: blog = article.get_blog_fqn(ctx.0), slug = &article.slug, responding_to = _)">
@article.title
</a>
</h3>
</header>
<main>
<p class="p-summary" dir="auto">@article.subtitle</p>
</main>
@ -26,7 +33,7 @@
@if article.published {
<span class="dt-published" datetime="@article.creation_date.format("%F %T")">@article.creation_date.format("%B %e, %Y")</span>
}
<a href="@uri!(blogs::details: name = &article.get_blog(ctx.0).unwrap().fqn, page = _)">@article.get_blog(ctx.0).unwrap().title</a>
<a href="@uri!(blogs::details: name = &article.get_blog_fqn(ctx.0), page = _)">@article.get_blog(ctx.0).unwrap().title</a>
</div>
@if !article.published {