Merge branch 'master' into federation

This commit is contained in:
Felix Ableitner 2020-03-18 22:51:34 +01:00
commit 33cce05300
17 changed files with 143 additions and 78 deletions

View file

@ -6,6 +6,8 @@ services:
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always
environment:
- RUST_LOG=debug
volumes: volumes:
- ./lemmy.hjson:/config/config.hjson:ro - ./lemmy.hjson:/config/config.hjson:ro
depends_on: depends_on:

View file

@ -18,6 +18,8 @@ services:
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always
environment:
- RUST_LOG=debug
volumes: volumes:
- ../lemmy.hjson:/config/config.hjson:ro - ../lemmy.hjson:/config/config.hjson:ro
depends_on: depends_on:

View file

@ -16,6 +16,8 @@ services:
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always
environment:
- RUST_LOG=debug
volumes: volumes:
- ./lemmy.hjson:/config/config.hjson:ro - ./lemmy.hjson:/config/config.hjson:ro
depends_on: depends_on:

1
docs/src/SUMMARY.md vendored
View file

@ -15,4 +15,5 @@
- [Local Development](contributing_local_development.md) - [Local Development](contributing_local_development.md)
- [Websocket/HTTP API](contributing_websocket_http_api.md) - [Websocket/HTTP API](contributing_websocket_http_api.md)
- [ActivityPub API Outline](contributing_apub_api_outline.md) - [ActivityPub API Outline](contributing_apub_api_outline.md)
- [Theming Guide](contributing_theming.md)
- [Lemmy Council](lemmy_council.md) - [Lemmy Council](lemmy_council.md)

18
docs/src/contributing_theming.md vendored Normal file
View file

@ -0,0 +1,18 @@
# Theming Guide
Lemmy uses [Bootstrap v4](https://getbootstrap.com/), and very few custom css classes, so any bootstrap v4 compatible theme should work fine.
## Creating
- Use a tool like [bootstrap.build](https://bootstrap.build/) to create a bootstrap v4 theme. Export the `bootstrap.min.css` once you're done, and save the `_variables.scss` too.
## Testing
- To test out a theme, you can either use your browser's web tools, or a plugin like stylus to copy-paste a theme, when viewing Lemmy.
## Adding
1. Copy `{my-theme-name}.min.css` to `ui/assets/css/themes`. (You can also copy the `_variables.scss` here if you want).
1. Go to `ui/src/utils.ts` and add `{my-theme-name}` to the themes list.
1. Test locally
1. Do a pull request with those changes.

2
install.sh vendored
View file

@ -37,7 +37,7 @@ yarn build
# Build and run the backend # Build and run the backend
cd ../server cd ../server
cargo run RUST_LOG=debug cargo run
# For live coding, where both the front and back end, automagically reload on any save, do: # For live coding, where both the front and back end, automagically reload on any save, do:
# cd ui && yarn start # cd ui && yarn start

View file

@ -2,6 +2,7 @@ use super::*;
use crate::send_email; use crate::send_email;
use crate::settings::Settings; use crate::settings::Settings;
use diesel::PgConnection; use diesel::PgConnection;
use log::error;
use std::str::FromStr; use std::str::FromStr;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -128,7 +129,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
// Let the uniqueness handle this fail // Let the uniqueness handle this fail
match UserMention::create(&conn, &user_mention_form) { match UserMention::create(&conn, &user_mention_form) {
Ok(_mention) => (), Ok(_mention) => (),
Err(_e) => eprintln!("{}", &_e), Err(_e) => error!("{}", &_e),
}; };
// Send an email to those users that have notifications on // Send an email to those users that have notifications on
@ -145,7 +146,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
); );
match send_email(subject, &mention_email, &mention_user.name, html) { match send_email(subject, &mention_email, &mention_user.name, html) {
Ok(_o) => _o, Ok(_o) => _o,
Err(e) => eprintln!("{}", e), Err(e) => error!("{}", e),
}; };
} }
} }
@ -174,7 +175,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
); );
match send_email(subject, &comment_reply_email, &parent_user.name, html) { match send_email(subject, &comment_reply_email, &parent_user.name, html) {
Ok(_o) => _o, Ok(_o) => _o,
Err(e) => eprintln!("{}", e), Err(e) => error!("{}", e),
}; };
} }
} }
@ -199,7 +200,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
); );
match send_email(subject, &post_reply_email, &parent_user.name, html) { match send_email(subject, &post_reply_email, &parent_user.name, html) {
Ok(_o) => _o, Ok(_o) => _o,
Err(e) => eprintln!("{}", e), Err(e) => error!("{}", e),
}; };
} }
} }
@ -318,7 +319,7 @@ impl Perform<CommentResponse> for Oper<EditComment> {
// Let the uniqueness handle this fail // Let the uniqueness handle this fail
match UserMention::create(&conn, &user_mention_form) { match UserMention::create(&conn, &user_mention_form) {
Ok(_mention) => (), Ok(_mention) => (),
Err(_e) => eprintln!("{}", &_e), Err(_e) => error!("{}", &_e),
} }
} }
} }

View file

@ -3,6 +3,7 @@ use crate::settings::Settings;
use crate::{generate_random_string, send_email}; use crate::{generate_random_string, send_email};
use bcrypt::verify; use bcrypt::verify;
use diesel::PgConnection; use diesel::PgConnection;
use log::error;
use std::str::FromStr; use std::str::FromStr;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -1008,7 +1009,7 @@ impl Perform<PrivateMessageResponse> for Oper<CreatePrivateMessage> {
); );
match send_email(subject, &email, &recipient_user.name, html) { match send_email(subject, &email, &recipient_user.name, html) {
Ok(_o) => _o, Ok(_o) => _o,
Err(e) => eprintln!("{}", e), Err(e) => error!("{}", e),
}; };
} }
} }

View file

@ -40,6 +40,7 @@ use lettre::smtp::extension::ClientId;
use lettre::smtp::ConnectionReuseParameters; use lettre::smtp::ConnectionReuseParameters;
use lettre::{ClientSecurity, SmtpClient, Transport}; use lettre::{ClientSecurity, SmtpClient, Transport};
use lettre_email::Email; use lettre_email::Email;
use log::error;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
@ -192,7 +193,7 @@ fn fetch_iframely_and_pictshare_data(
Some(url) => match fetch_iframely(&url) { Some(url) => match fetch_iframely(&url) {
Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
Err(e) => { Err(e) => {
eprintln!("iframely err: {}", e); error!("iframely err: {}", e);
(None, None, None, None) (None, None, None, None)
} }
}, },
@ -204,7 +205,7 @@ fn fetch_iframely_and_pictshare_data(
Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) { Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) {
Ok(res) => Some(res.url), Ok(res) => Some(res.url),
Err(e) => { Err(e) => {
eprintln!("pictshare err: {}", e); error!("pictshare err: {}", e);
None None
} }
}, },

View file

@ -3,6 +3,7 @@ use actix::prelude::*;
use actix_web::web; use actix_web::web;
use actix_web::*; use actix_web::*;
use actix_web_actors::ws; use actix_web_actors::ws;
use log::{error, info};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
pub fn config(cfg: &mut web::ServiceConfig) { pub fn config(cfg: &mut web::ServiceConfig) {
@ -99,7 +100,6 @@ impl Handler<WSMessage> for WSSession {
type Result = (); type Result = ();
fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) { fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) {
// println!("id: {} msg: {}", self.id, msg.0);
ctx.text(msg.0); ctx.text(msg.0);
} }
} }
@ -107,11 +107,10 @@ impl Handler<WSMessage> for WSSession {
/// WebSocket message handler /// WebSocket message handler
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSSession { impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSSession {
fn handle(&mut self, result: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) { fn handle(&mut self, result: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
// println!("WEBSOCKET MESSAGE: {:?} from id: {}", msg, self.id);
let message = match result { let message = match result {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
println!("{}", e); error!("{}", e);
return; return;
} }
}; };
@ -125,7 +124,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSSession {
} }
ws::Message::Text(text) => { ws::Message::Text(text) => {
let m = text.trim().to_owned(); let m = text.trim().to_owned();
println!("WEBSOCKET MESSAGE: {:?} from id: {}", &m, self.id); info!("Message received: {:?} from id: {}", &m, self.id);
self self
.cs_addr .cs_addr
@ -138,14 +137,14 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WSSession {
match res { match res {
Ok(res) => ctx.text(res), Ok(res) => ctx.text(res),
Err(e) => { Err(e) => {
eprintln!("{}", &e); error!("{}", &e);
} }
} }
actix::fut::ready(()) actix::fut::ready(())
}) })
.wait(ctx); .wait(ctx);
} }
ws::Message::Binary(_bin) => println!("Unexpected binary"), ws::Message::Binary(_bin) => info!("Unexpected binary"),
ws::Message::Close(_) => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
} }
@ -163,7 +162,7 @@ impl WSSession {
// check client heartbeats // check client heartbeats
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
// heartbeat timed out // heartbeat timed out
println!("Websocket Client heartbeat failed, disconnecting!"); error!("Websocket Client heartbeat failed, disconnecting!");
// notify chat server // notify chat server
act.cs_addr.do_send(Disconnect { act.cs_addr.do_send(Disconnect {

View file

@ -6,6 +6,7 @@ use actix::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
use log::{error, info, warn};
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@ -343,7 +344,7 @@ impl ChatServer {
} }
if rate_limit.allowance < 1.0 { if rate_limit.allowance < 1.0 {
println!( warn!(
"Rate limited IP: {}, time_passed: {}, allowance: {}", "Rate limited IP: {}, time_passed: {}, allowance: {}",
&info.ip, time_passed, rate_limit.allowance &info.ip, time_passed, rate_limit.allowance
); );
@ -387,7 +388,7 @@ impl Handler<Connect> for ChatServer {
fn handle(&mut self, msg: Connect, _ctx: &mut Context<Self>) -> Self::Result { fn handle(&mut self, msg: Connect, _ctx: &mut Context<Self>) -> Self::Result {
// register session with random id // register session with random id
let id = self.rng.gen::<usize>(); let id = self.rng.gen::<usize>();
println!("{} joined", &msg.ip); info!("{} joined", &msg.ip);
self.sessions.insert( self.sessions.insert(
id, id,
@ -448,13 +449,16 @@ impl Handler<StandardMessage> for ChatServer {
type Result = MessageResult<StandardMessage>; type Result = MessageResult<StandardMessage>;
fn handle(&mut self, msg: StandardMessage, _: &mut Context<Self>) -> Self::Result { fn handle(&mut self, msg: StandardMessage, _: &mut Context<Self>) -> Self::Result {
let msg_out = match parse_json_message(self, msg) { match parse_json_message(self, msg) {
Ok(m) => m, Ok(m) => {
Err(e) => e.to_string(), info!("Message Sent: {}", m);
}; MessageResult(m)
}
println!("Message Sent: {}", msg_out); Err(e) => {
MessageResult(msg_out) error!("Error during message handling {}", e);
MessageResult(e.to_string())
}
}
} }
} }

View file

@ -198,9 +198,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<span <span
className={`unselectable pointer ${this.scoreColor}`} className={`unselectable pointer ${this.scoreColor}`}
onClick={linkEvent(node, this.handleCommentUpvote)} onClick={linkEvent(node, this.handleCommentUpvote)}
data-tippy-content={i18n.t('number_of_points', { data-tippy-content={this.pointsTippy}
count: this.state.score,
})}
> >
<svg class="icon icon-inline mr-1"> <svg class="icon icon-inline mr-1">
<use xlinkHref="#icon-zap"></use> <use xlinkHref="#icon-zap"></use>
@ -916,6 +914,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.likeComment(form);
this.setState(this.state); this.setState(this.state);
setupTippy();
} }
handleCommentDownvote(i: CommentNodeI) { handleCommentDownvote(i: CommentNodeI) {
@ -943,6 +942,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.likeComment(form);
this.setState(this.state); this.setState(this.state);
setupTippy();
} }
handleModRemoveShow(i: CommentNode) { handleModRemoveShow(i: CommentNode) {
@ -1166,4 +1166,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return 'text-muted'; return 'text-muted';
} }
} }
get pointsTippy(): string {
let points = i18n.t('number_of_points', {
count: this.state.score,
});
let upvotes = i18n.t('number_of_upvotes', {
count: this.state.upvotes,
});
let downvotes = i18n.t('number_of_downvotes', {
count: this.state.downvotes,
});
return `${points}${upvotes}${downvotes}`;
}
} }

View file

@ -262,9 +262,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</button> </button>
<div <div
class={`unselectable pointer font-weight-bold text-muted px-1`} class={`unselectable pointer font-weight-bold text-muted px-1`}
data-tippy-content={i18n.t('number_of_points', { data-tippy-content={this.pointsTippy}
count: this.state.score,
})}
> >
{this.state.score} {this.state.score}
</div> </div>
@ -466,9 +464,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<> <>
<span <span
class="unselectable pointer mr-2" class="unselectable pointer mr-2"
data-tippy-content={i18n.t('number_of_points', { data-tippy-content={this.pointsTippy}
count: this.state.score,
})}
> >
<li className="list-inline-item"> <li className="list-inline-item">
<span className="text-muted"> <span className="text-muted">
@ -1022,6 +1018,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
WebSocketService.Instance.likePost(form); WebSocketService.Instance.likePost(form);
i.setState(i.state); i.setState(i.state);
setupTippy();
} }
handlePostDisLike(i: PostListing) { handlePostDisLike(i: PostListing) {
@ -1048,6 +1045,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
WebSocketService.Instance.likePost(form); WebSocketService.Instance.likePost(form);
i.setState(i.state); i.setState(i.state);
setupTippy();
} }
handleEditClick(i: PostListing) { handleEditClick(i: PostListing) {
@ -1291,4 +1289,20 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.setState(i.state); i.setState(i.state);
setupTippy(); setupTippy();
} }
get pointsTippy(): string {
let points = i18n.t('number_of_points', {
count: this.state.score,
});
let upvotes = i18n.t('number_of_upvotes', {
count: this.state.upvotes,
});
let downvotes = i18n.t('number_of_downvotes', {
count: this.state.downvotes,
});
return `${points}${upvotes}${downvotes}`;
}
} }

76
ui/src/utils.ts vendored
View file

@ -16,7 +16,7 @@ import 'moment/locale/ja';
import { import {
UserOperation, UserOperation,
Comment, Comment,
CommentNode, CommentNode as CommentNodeI,
Post, Post,
PrivateMessage, PrivateMessage,
User, User,
@ -53,6 +53,39 @@ export const postRefetchSeconds: number = 60 * 1000;
export const fetchLimit: number = 20; export const fetchLimit: number = 20;
export const mentionDropdownFetchLimit = 10; export const mentionDropdownFetchLimit = 10;
export const languages = [
{ code: 'ca', name: 'Català' },
{ code: 'en', name: 'English' },
{ code: 'eo', name: 'Esperanto' },
{ code: 'es', name: 'Español' },
{ code: 'de', name: 'Deutsch' },
{ code: 'fa', name: 'فارسی' },
{ code: 'ja', name: '日本語' },
{ code: 'pt_BR', name: 'Português Brasileiro' },
{ code: 'zh', name: '中文' },
{ code: 'fi', name: 'Suomi' },
{ code: 'fr', name: 'Français' },
{ code: 'sv', name: 'Svenska' },
{ code: 'ru', name: 'Русский' },
{ code: 'nl', name: 'Nederlands' },
{ code: 'it', name: 'Italiano' },
];
export const themes = [
'litera',
'materia',
'minty',
'solar',
'united',
'cyborg',
'darkly',
'journal',
'sketchy',
'vaporwave',
'vaporwave-dark',
'i386',
];
export function randomStr() { export function randomStr() {
return Math.random() return Math.random()
.toString(36) .toString(36)
@ -275,24 +308,6 @@ export function debounce(
}; };
} }
export const languages = [
{ code: 'ca', name: 'Català' },
{ code: 'en', name: 'English' },
{ code: 'eo', name: 'Esperanto' },
{ code: 'es', name: 'Español' },
{ code: 'de', name: 'Deutsch' },
{ code: 'fa', name: 'فارسی' },
{ code: 'ja', name: '日本語' },
{ code: 'pt_BR', name: 'Português Brasileiro' },
{ code: 'zh', name: '中文' },
{ code: 'fi', name: 'Suomi' },
{ code: 'fr', name: 'Français' },
{ code: 'sv', name: 'Svenska' },
{ code: 'ru', name: 'Русский' },
{ code: 'nl', name: 'Nederlands' },
{ code: 'it', name: 'Italiano' },
];
export function getLanguage(): string { export function getLanguage(): string {
let user = UserService.Instance.user; let user = UserService.Instance.user;
let lang = user && user.lang ? user.lang : 'browser'; let lang = user && user.lang ? user.lang : 'browser';
@ -344,21 +359,6 @@ export function getMomentLanguage(): string {
return lang; return lang;
} }
export const themes = [
'litera',
'materia',
'minty',
'solar',
'united',
'cyborg',
'darkly',
'journal',
'sketchy',
'vaporwave',
'vaporwave-dark',
'i386',
];
export function setTheme(theme: string = 'darkly') { export function setTheme(theme: string = 'darkly') {
// unload all the other themes // unload all the other themes
for (var i = 0; i < themes.length; i++) { for (var i = 0; i < themes.length; i++) {
@ -668,15 +668,15 @@ export function editPostRes(data: PostResponse, post: Post) {
export function commentsToFlatNodes( export function commentsToFlatNodes(
comments: Array<Comment> comments: Array<Comment>
): Array<CommentNode> { ): Array<CommentNodeI> {
let nodes: Array<CommentNode> = []; let nodes: Array<CommentNodeI> = [];
for (let comment of comments) { for (let comment of comments) {
nodes.push({ comment: comment }); nodes.push({ comment: comment });
} }
return nodes; return nodes;
} }
export function commentSort(tree: Array<CommentNode>, sort: CommentSortType) { export function commentSort(tree: Array<CommentNodeI>, sort: CommentSortType) {
// First, put removed and deleted comments at the bottom, then do your other sorts // First, put removed and deleted comments at the bottom, then do your other sorts
if (sort == CommentSortType.Top) { if (sort == CommentSortType.Top) {
tree.sort( tree.sort(
@ -716,7 +716,7 @@ export function commentSort(tree: Array<CommentNode>, sort: CommentSortType) {
} }
} }
export function commentSortSortType(tree: Array<CommentNode>, sort: SortType) { export function commentSortSortType(tree: Array<CommentNodeI>, sort: SortType) {
commentSort(tree, convertCommentSortType(sort)); commentSort(tree, convertCommentSortType(sort));
} }

View file

@ -155,7 +155,11 @@
"downvotes_disabled": "Downvotes disabled", "downvotes_disabled": "Downvotes disabled",
"enable_downvotes": "Enable Downvotes", "enable_downvotes": "Enable Downvotes",
"upvote": "Upvote", "upvote": "Upvote",
"number_of_upvotes": "{{count}} Upvote",
"number_of_upvotes_plural": "{{count}} Upvotes",
"downvote": "Downvote", "downvote": "Downvote",
"number_of_downvotes": "{{count}} Downvote",
"number_of_downvotes_plural": "{{count}} Downvotes",
"open_registration": "Open Registration", "open_registration": "Open Registration",
"registration_closed": "Registration closed", "registration_closed": "Registration closed",
"enable_nsfw": "Enable NSFW", "enable_nsfw": "Enable NSFW",

6
ui/yarn.lock vendored
View file

@ -258,9 +258,9 @@ acorn-jsx@^5.1.0:
integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
acorn@^5.0.3, acorn@^5.7.3: acorn@^5.0.3, acorn@^5.7.3:
version "5.7.3" version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
acorn@^7.1.0: acorn@^7.1.0:
version "7.1.0" version "7.1.0"