Merge branch 'federation' of https://yerbamate.dev/nutomic/lemmy into federation
This commit is contained in:
commit
56e4bfd556
17 changed files with 143 additions and 78 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -14,4 +14,4 @@ build/
|
||||||
ui/src/translations
|
ui/src/translations
|
||||||
|
|
||||||
# ide config
|
# ide config
|
||||||
.idea/
|
.idea/
|
||||||
|
|
4
ansible/templates/docker-compose.yml
vendored
4
ansible/templates/docker-compose.yml
vendored
|
@ -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:
|
||||||
|
@ -43,4 +45,4 @@ services:
|
||||||
image: mwader/postfix-relay
|
image: mwader/postfix-relay
|
||||||
environment:
|
environment:
|
||||||
- POSTFIX_myhostname={{ domain }}
|
- POSTFIX_myhostname={{ domain }}
|
||||||
restart: "always"
|
restart: "always"
|
||||||
|
|
2
docker/dev/docker-compose.yml
vendored
2
docker/dev/docker-compose.yml
vendored
|
@ -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:
|
||||||
|
|
2
docker/prod/docker-compose.yml
vendored
2
docker/prod/docker-compose.yml
vendored
|
@ -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
1
docs/src/SUMMARY.md
vendored
|
@ -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
18
docs/src/contributing_theming.md
vendored
Normal 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
4
install.sh
vendored
|
@ -16,7 +16,7 @@ init_db_final=0
|
||||||
while [ "$init_db_valid" == 0 ]
|
while [ "$init_db_valid" == 0 ]
|
||||||
do
|
do
|
||||||
read -p "Initialize database (y/n)? " init_db
|
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;;
|
y|yes ) init_db_valid=1; init_db_final=1;;
|
||||||
n|no ) init_db_valid=1; init_db_final=0;;
|
n|no ) init_db_valid=1; init_db_final=0;;
|
||||||
* ) echo "Invalid input" 1>&2;;
|
* ) echo "Invalid input" 1>&2;;
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
ui/src/components/comment-node.tsx
vendored
22
ui/src/components/comment-node.tsx
vendored
|
@ -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}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
26
ui/src/components/post-listing.tsx
vendored
26
ui/src/components/post-listing.tsx
vendored
|
@ -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
76
ui/src/utils.ts
vendored
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
ui/translations/en.json
vendored
4
ui/translations/en.json
vendored
|
@ -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
6
ui/yarn.lock
vendored
|
@ -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"
|
||||||
|
|
Reference in a new issue