diff --git a/.gitignore b/.gitignore index 919358cf1..5e221b038 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ build/ ui/src/translations # ide config -.idea/ \ No newline at end of file +.idea/ diff --git a/ansible/templates/docker-compose.yml b/ansible/templates/docker-compose.yml index 57ee142a6..ffd5b9c2b 100644 --- a/ansible/templates/docker-compose.yml +++ b/ansible/templates/docker-compose.yml @@ -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" \ No newline at end of file + restart: "always" diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 64ba1db43..a7d289b21 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -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: diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 70c6bfe70..9f5e89259 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -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: diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 9a2ecde8d..10a6153ea 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -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) diff --git a/docs/src/contributing_theming.md b/docs/src/contributing_theming.md new file mode 100644 index 000000000..25c8ca6d1 --- /dev/null +++ b/docs/src/contributing_theming.md @@ -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. diff --git a/install.sh b/install.sh index b368891cf..ad3e4ab3a 100755 --- a/install.sh +++ b/install.sh @@ -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 diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 5c6149666..8373a338b 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -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 for Oper { // 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 for Oper { ); 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 for Oper { ); 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 for Oper { ); 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 for Oper { // Let the uniqueness handle this fail match UserMention::create(&conn, &user_mention_form) { Ok(_mention) => (), - Err(_e) => eprintln!("{}", &_e), + Err(_e) => error!("{}", &_e), } } } diff --git a/server/src/api/user.rs b/server/src/api/user.rs index f73138953..333fd9494 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -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 for Oper { ); match send_email(subject, &email, &recipient_user.name, html) { Ok(_o) => _o, - Err(e) => eprintln!("{}", e), + Err(e) => error!("{}", e), }; } } diff --git a/server/src/lib.rs b/server/src/lib.rs index bf3c3c0ac..2507819dc 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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 } }, diff --git a/server/src/routes/websocket.rs b/server/src/routes/websocket.rs index a68b2bcde..6c4326fd3 100644 --- a/server/src/routes/websocket.rs +++ b/server/src/routes/websocket.rs @@ -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 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 for WSSession { /// WebSocket message handler impl StreamHandler> for WSSession { fn handle(&mut self, result: Result, 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> 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> 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 { diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 8a8ccca03..f205c91e6 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -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 for ChatServer { fn handle(&mut self, msg: Connect, _ctx: &mut Context) -> Self::Result { // register session with random id let id = self.rng.gen::(); - println!("{} joined", &msg.ip); + info!("{} joined", &msg.ip); self.sessions.insert( id, @@ -448,13 +449,16 @@ impl Handler for ChatServer { type Result = MessageResult; fn handle(&mut self, msg: StandardMessage, _: &mut Context) -> 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()) + } + } } } diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 524367bce..32e43fdc7 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -198,9 +198,7 @@ export class CommentNode extends Component { @@ -916,6 +914,7 @@ export class CommentNode extends Component { WebSocketService.Instance.likeComment(form); this.setState(this.state); + setupTippy(); } handleCommentDownvote(i: CommentNodeI) { @@ -943,6 +942,7 @@ export class CommentNode extends Component { WebSocketService.Instance.likeComment(form); this.setState(this.state); + setupTippy(); } handleModRemoveShow(i: CommentNode) { @@ -1166,4 +1166,20 @@ export class CommentNode extends Component { 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}`; + } } diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index bef74999f..fd34875d7 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -262,9 +262,7 @@ export class PostListing extends Component {
{this.state.score}
@@ -466,9 +464,7 @@ export class PostListing extends Component { <>
  • @@ -1022,6 +1018,7 @@ export class PostListing extends Component { WebSocketService.Instance.likePost(form); i.setState(i.state); + setupTippy(); } handlePostDisLike(i: PostListing) { @@ -1048,6 +1045,7 @@ export class PostListing extends Component { WebSocketService.Instance.likePost(form); i.setState(i.state); + setupTippy(); } handleEditClick(i: PostListing) { @@ -1291,4 +1289,20 @@ export class PostListing extends Component { 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}`; + } } diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 27dbfb500..89fbe51c8 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -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 -): Array { - let nodes: Array = []; +): Array { + let nodes: Array = []; for (let comment of comments) { nodes.push({ comment: comment }); } return nodes; } -export function commentSort(tree: Array, sort: CommentSortType) { +export function commentSort(tree: Array, 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, sort: CommentSortType) { } } -export function commentSortSortType(tree: Array, sort: SortType) { +export function commentSortSortType(tree: Array, sort: SortType) { commentSort(tree, convertCommentSortType(sort)); } diff --git a/ui/translations/en.json b/ui/translations/en.json index afee21bbe..e9df20ba3 100644 --- a/ui/translations/en.json +++ b/ui/translations/en.json @@ -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", diff --git a/ui/yarn.lock b/ui/yarn.lock index d36bef04e..0d382f8f1 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -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"