Merge branch 'master' into federation

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

2
.gitignore vendored
View file

@ -14,4 +14,4 @@ build/
ui/src/translations
# ide config
.idea/
.idea/

View file

@ -6,6 +6,8 @@ services:
ports:
- "127.0.0.1:8536:8536"
restart: always
environment:
- RUST_LOG=debug
volumes:
- ./lemmy.hjson:/config/config.hjson:ro
depends_on:
@ -43,4 +45,4 @@ services:
image: mwader/postfix-relay
environment:
- POSTFIX_myhostname={{ domain }}
restart: "always"
restart: "always"

View file

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

View file

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

1
docs/src/SUMMARY.md vendored
View file

@ -15,4 +15,5 @@
- [Local Development](contributing_local_development.md)
- [Websocket/HTTP API](contributing_websocket_http_api.md)
- [ActivityPub API Outline](contributing_apub_api_outline.md)
- [Theming Guide](contributing_theming.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.

4
install.sh vendored
View file

@ -16,7 +16,7 @@ init_db_final=0
while [ "$init_db_valid" == 0 ]
do
read -p "Initialize database (y/n)? " init_db
case "${init_db,,}" in
case "${init_db,,}" in
y|yes ) init_db_valid=1; init_db_final=1;;
n|no ) init_db_valid=1; init_db_final=0;;
* ) echo "Invalid input" 1>&2;;
@ -37,7 +37,7 @@ yarn build
# Build and run the backend
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:
# cd ui && yarn start

View file

@ -2,6 +2,7 @@ use super::*;
use crate::send_email;
use crate::settings::Settings;
use diesel::PgConnection;
use log::error;
use std::str::FromStr;
#[derive(Serialize, Deserialize)]
@ -128,7 +129,7 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
// Let the uniqueness handle this fail
match UserMention::create(&conn, &user_mention_form) {
Ok(_mention) => (),
Err(_e) => eprintln!("{}", &_e),
Err(_e) => error!("{}", &_e),
};
// 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) {
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) {
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) {
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
match UserMention::create(&conn, &user_mention_form) {
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 bcrypt::verify;
use diesel::PgConnection;
use log::error;
use std::str::FromStr;
#[derive(Serialize, Deserialize, Debug)]
@ -1008,7 +1009,7 @@ impl Perform<PrivateMessageResponse> for Oper<CreatePrivateMessage> {
);
match send_email(subject, &email, &recipient_user.name, html) {
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::{ClientSecurity, SmtpClient, Transport};
use lettre_email::Email;
use log::error;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
@ -192,7 +193,7 @@ fn fetch_iframely_and_pictshare_data(
Some(url) => match fetch_iframely(&url) {
Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
Err(e) => {
eprintln!("iframely err: {}", e);
error!("iframely err: {}", e);
(None, None, None, None)
}
},
@ -204,7 +205,7 @@ fn fetch_iframely_and_pictshare_data(
Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) {
Ok(res) => Some(res.url),
Err(e) => {
eprintln!("pictshare err: {}", e);
error!("pictshare err: {}", e);
None
}
},

View file

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

View file

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

View file

@ -198,9 +198,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<span
className={`unselectable pointer ${this.scoreColor}`}
onClick={linkEvent(node, this.handleCommentUpvote)}
data-tippy-content={i18n.t('number_of_points', {
count: this.state.score,
})}
data-tippy-content={this.pointsTippy}
>
<svg class="icon icon-inline mr-1">
<use xlinkHref="#icon-zap"></use>
@ -916,6 +914,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
WebSocketService.Instance.likeComment(form);
this.setState(this.state);
setupTippy();
}
handleCommentDownvote(i: CommentNodeI) {
@ -943,6 +942,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
WebSocketService.Instance.likeComment(form);
this.setState(this.state);
setupTippy();
}
handleModRemoveShow(i: CommentNode) {
@ -1166,4 +1166,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
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>
<div
class={`unselectable pointer font-weight-bold text-muted px-1`}
data-tippy-content={i18n.t('number_of_points', {
count: this.state.score,
})}
data-tippy-content={this.pointsTippy}
>
{this.state.score}
</div>
@ -466,9 +464,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<>
<span
class="unselectable pointer mr-2"
data-tippy-content={i18n.t('number_of_points', {
count: this.state.score,
})}
data-tippy-content={this.pointsTippy}
>
<li className="list-inline-item">
<span className="text-muted">
@ -1022,6 +1018,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
WebSocketService.Instance.likePost(form);
i.setState(i.state);
setupTippy();
}
handlePostDisLike(i: PostListing) {
@ -1048,6 +1045,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
WebSocketService.Instance.likePost(form);
i.setState(i.state);
setupTippy();
}
handleEditClick(i: PostListing) {
@ -1291,4 +1289,20 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.setState(i.state);
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 {
UserOperation,
Comment,
CommentNode,
CommentNode as CommentNodeI,
Post,
PrivateMessage,
User,
@ -53,6 +53,39 @@ export const postRefetchSeconds: number = 60 * 1000;
export const fetchLimit: number = 20;
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() {
return Math.random()
.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 {
let user = UserService.Instance.user;
let lang = user && user.lang ? user.lang : 'browser';
@ -344,21 +359,6 @@ export function getMomentLanguage(): string {
return lang;
}
export const themes = [
'litera',
'materia',
'minty',
'solar',
'united',
'cyborg',
'darkly',
'journal',
'sketchy',
'vaporwave',
'vaporwave-dark',
'i386',
];
export function setTheme(theme: string = 'darkly') {
// unload all the other themes
for (var i = 0; i < themes.length; i++) {
@ -668,15 +668,15 @@ export function editPostRes(data: PostResponse, post: Post) {
export function commentsToFlatNodes(
comments: Array<Comment>
): Array<CommentNode> {
let nodes: Array<CommentNode> = [];
): Array<CommentNodeI> {
let nodes: Array<CommentNodeI> = [];
for (let comment of comments) {
nodes.push({ comment: comment });
}
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
if (sort == CommentSortType.Top) {
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));
}

View file

@ -155,7 +155,11 @@
"downvotes_disabled": "Downvotes disabled",
"enable_downvotes": "Enable Downvotes",
"upvote": "Upvote",
"number_of_upvotes": "{{count}} Upvote",
"number_of_upvotes_plural": "{{count}} Upvotes",
"downvote": "Downvote",
"number_of_downvotes": "{{count}} Downvote",
"number_of_downvotes_plural": "{{count}} Downvotes",
"open_registration": "Open Registration",
"registration_closed": "Registration closed",
"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==
acorn@^5.0.3, acorn@^5.7.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
acorn@^7.1.0:
version "7.1.0"