Adding 8 different themes.

- Fixes #276
pull/296/head
Dessalines 2019-10-15 12:21:27 -07:00
parent 62ae25f90d
commit 272ab3948c
35 changed files with 372 additions and 87 deletions

View File

@ -0,0 +1 @@
alter table user_ drop column theme;

View File

@ -0,0 +1 @@
alter table user_ add column theme varchar(20) default 'darkly' not null;

View File

@ -21,6 +21,7 @@ pub struct Register {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SaveUserSettings { pub struct SaveUserSettings {
show_nsfw: bool, show_nsfw: bool,
theme: String,
auth: String, auth: String,
} }
@ -162,6 +163,7 @@ impl Perform<LoginResponse> for Oper<Register> {
admin: data.admin, admin: data.admin,
banned: false, banned: false,
show_nsfw: data.show_nsfw, show_nsfw: data.show_nsfw,
theme: "darkly".into(),
}; };
// Create the user // Create the user
@ -252,6 +254,7 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
admin: read_user.admin, admin: read_user.admin,
banned: read_user.banned, banned: read_user.banned,
show_nsfw: data.show_nsfw, show_nsfw: data.show_nsfw,
theme: data.theme.to_owned(),
}; };
let updated_user = match User_::update(&conn, user_id, &user_form) { let updated_user = match User_::update(&conn, user_id, &user_form) {
@ -416,6 +419,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
admin: data.added, admin: data.added,
banned: read_user.banned, banned: read_user.banned,
show_nsfw: read_user.show_nsfw, show_nsfw: read_user.show_nsfw,
theme: read_user.theme,
}; };
match User_::update(&conn, data.user_id, &user_form) { match User_::update(&conn, data.user_id, &user_form) {
@ -474,6 +478,7 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
admin: read_user.admin, admin: read_user.admin,
banned: data.ban, banned: data.ban,
show_nsfw: read_user.show_nsfw, show_nsfw: read_user.show_nsfw,
theme: read_user.theme,
}; };
match User_::update(&conn, data.user_id, &user_form) { match User_::update(&conn, data.user_id, &user_form) {

View File

@ -72,6 +72,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let person = expected_user.person(); let person = expected_user.person();

View File

@ -178,6 +178,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View File

@ -264,6 +264,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View File

@ -264,6 +264,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View File

@ -446,6 +446,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_mod = User_::create(&conn, &new_mod).unwrap(); let inserted_mod = User_::create(&conn, &new_mod).unwrap();
@ -460,6 +461,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View File

@ -191,6 +191,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View File

@ -225,6 +225,7 @@ mod tests {
admin: false, admin: false,
banned: false, banned: false,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();

View File

@ -20,6 +20,7 @@ pub struct User_ {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool, pub show_nsfw: bool,
pub theme: String,
} }
#[derive(Insertable, AsChangeset, Clone)] #[derive(Insertable, AsChangeset, Clone)]
@ -34,6 +35,7 @@ pub struct UserForm {
pub email: Option<String>, pub email: Option<String>,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool, pub show_nsfw: bool,
pub theme: String,
} }
impl Crud<UserForm> for User_ { impl Crud<UserForm> for User_ {
@ -74,6 +76,7 @@ pub struct Claims {
pub username: String, pub username: String,
pub iss: String, pub iss: String,
pub show_nsfw: bool, pub show_nsfw: bool,
pub theme: String,
} }
impl Claims { impl Claims {
@ -94,6 +97,7 @@ impl User_ {
username: self.name.to_owned(), username: self.name.to_owned(),
iss: self.fedi_name.to_owned(), iss: self.fedi_name.to_owned(),
show_nsfw: self.show_nsfw, show_nsfw: self.show_nsfw,
theme: self.theme.to_owned(),
}; };
encode( encode(
&Header::default(), &Header::default(),
@ -141,6 +145,7 @@ mod tests {
banned: false, banned: false,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let inserted_user = User_::create(&conn, &new_user).unwrap(); let inserted_user = User_::create(&conn, &new_user).unwrap();
@ -158,6 +163,7 @@ mod tests {
published: inserted_user.published, published: inserted_user.published,
updated: None, updated: None,
show_nsfw: false, show_nsfw: false,
theme: "darkly".into(),
}; };
let read_user = User_::read(&conn, inserted_user.id).unwrap(); let read_user = User_::read(&conn, inserted_user.id).unwrap();

View File

@ -254,6 +254,7 @@ table! {
published -> Timestamp, published -> Timestamp,
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
show_nsfw -> Bool, show_nsfw -> Bool,
theme -> Varchar,
} }
} }
@ -304,28 +305,28 @@ joinable!(site -> user_ (creator_id));
joinable!(user_ban -> user_ (user_id)); joinable!(user_ban -> user_ (user_id));
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(
category, category,
comment, comment,
comment_like, comment_like,
comment_saved, comment_saved,
community, community,
community_follower, community_follower,
community_moderator, community_moderator,
community_user_ban, community_user_ban,
mod_add, mod_add,
mod_add_community, mod_add_community,
mod_ban, mod_ban,
mod_ban_from_community, mod_ban_from_community,
mod_lock_post, mod_lock_post,
mod_remove_comment, mod_remove_comment,
mod_remove_community, mod_remove_community,
mod_remove_post, mod_remove_post,
mod_sticky_post, mod_sticky_post,
post, post,
post_like, post_like,
post_read, post_read,
post_saved, post_saved,
site, site,
user_, user_,
user_ban, user_ban,
); );

View File

@ -1,7 +1,3 @@
body, .text-white, .navbar-brand, .badge-light, .btn-secondary {
color: #dedede !important;
}
.navbar-toggler { .navbar-toggler {
border: 0px; border: 0px;
} }
@ -31,29 +27,6 @@ body, .text-white, .navbar-brand, .badge-light, .btn-secondary {
margin-top: -10px; margin-top: -10px;
} }
.form-control, .form-control:focus {
background-color: var(--secondary);
color: #fff;
}
.form-control:disabled {
background-color: var(--secondary);
opacity: .5;
}
.custom-select {
color: #fff;
background-color: var(--secondary);
}
.mark {
background-color: #333;
}
.mark-two {
background-color: #444 !important;
}
.md-div p:last-child { .md-div p:last-child {
margin-bottom: 0px; margin-bottom: 0px;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
ui/package.json vendored
View File

@ -21,6 +21,7 @@
"@types/markdown-it": "^0.0.7", "@types/markdown-it": "^0.0.7",
"@types/markdown-it-container": "^2.0.2", "@types/markdown-it-container": "^2.0.2",
"autosize": "^4.0.2", "autosize": "^4.0.2",
"bootswatch": "^4.3.1",
"classcat": "^1.1.3", "classcat": "^1.1.3",
"dotenv": "^6.1.0", "dotenv": "^6.1.0",
"emoji-short-name": "^0.1.0", "emoji-short-name": "^0.1.0",

View File

@ -13,7 +13,7 @@ export class Footer extends Component<any, any> {
render() { render() {
return ( return (
<nav class="container navbar navbar-expand-md navbar-light navbar-bg p-0 px-3 my-2"> <nav class="container navbar navbar-expand-md navbar-light navbar-bg p-0 px-3 mt-2">
<div className="navbar-collapse"> <div className="navbar-collapse">
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
<li class="nav-item"> <li class="nav-item">

View File

@ -96,8 +96,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div className="post-title"> <div className="post-title">
<h5 className="mb-0 d-inline"> <h5 className="mb-0 d-inline">
{post.url ? {post.url ?
<a className="text-white" href={post.url} target="_blank" title={post.url}>{post.name}</a> : <a className="text-body" href={post.url} target="_blank" title={post.url}>{post.name}</a> :
<Link className="text-white" to={`/post/${post.id}`} title={i18n.t('comments')}>{post.name}</Link> <Link className="text-body" to={`/post/${post.id}`} title={i18n.t('comments')}>{post.name}</Link>
} }
</h5> </h5>
{post.url && {post.url &&

View File

@ -74,7 +74,7 @@ export class Post extends Component<any, PostState> {
if (this.state.scrolled_comment_id && !this.state.scrolled && lastState.comments.length > 0) { if (this.state.scrolled_comment_id && !this.state.scrolled && lastState.comments.length > 0) {
var elmnt = document.getElementById(`comment-${this.state.scrolled_comment_id}`); var elmnt = document.getElementById(`comment-${this.state.scrolled_comment_id}`);
elmnt.scrollIntoView(); elmnt.scrollIntoView();
elmnt.classList.add("mark-two"); elmnt.classList.add("mark");
this.state.scrolled = true; this.state.scrolled = true;
this.markScrolledAsRead(this.state.scrolled_comment_id); this.markScrolledAsRead(this.state.scrolled_comment_id);
} }

View File

@ -4,7 +4,7 @@ import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse, UserSettingsForm, LoginResponse, BanUserResponse, AddAdminResponse } from '../interfaces'; import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse, UserSettingsForm, LoginResponse, BanUserResponse, AddAdminResponse } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter } from '../utils'; import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter, themes, setTheme } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
@ -61,6 +61,7 @@ export class User extends Component<any, UserState> {
page: this.getPageFromProps(this.props), page: this.getPageFromProps(this.props),
userSettingsForm: { userSettingsForm: {
show_nsfw: null, show_nsfw: null,
theme: null,
auth: null, auth: null,
}, },
userSettingsLoading: null, userSettingsLoading: null,
@ -285,7 +286,18 @@ export class User extends Component<any, UserState> {
<div class="card-body"> <div class="card-body">
<h5><T i18nKey="settings">#</T></h5> <h5><T i18nKey="settings">#</T></h5>
<form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}> <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
<div class="form-group row"> <div class="form-group">
<div class="col-12">
<label><T i18nKey="theme">#</T></label>
<select value={this.state.userSettingsForm.theme} onChange={linkEvent(this, this.handleUserSettingsThemeChange)} class="ml-2 custom-select custom-select-sm w-auto">
<option disabled><T i18nKey="theme">#</T></option>
{themes.map(theme =>
<option value={theme}>{theme}</option>
)}
</select>
</div>
</div>
<div class="form-group">
<div class="col-12"> <div class="col-12">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" checked={this.state.userSettingsForm.show_nsfw} onChange={linkEvent(this, this.handleUserSettingsShowNsfwChange)}/> <input class="form-check-input" type="checkbox" checked={this.state.userSettingsForm.show_nsfw} onChange={linkEvent(this, this.handleUserSettingsShowNsfwChange)}/>
@ -309,20 +321,18 @@ export class User extends Component<any, UserState> {
moderates() { moderates() {
return ( return (
<div> <div>
<div class="card border-secondary mb-3"> {this.state.moderates.length > 0 &&
<div class="card-body"> <div class="card border-secondary mb-3">
{this.state.moderates.length > 0 && <div class="card-body">
<div> <h5><T i18nKey="moderates">#</T></h5>
<h5><T i18nKey="moderates">#</T></h5> <ul class="list-unstyled mb-0">
<ul class="list-unstyled mb-0"> {this.state.moderates.map(community =>
{this.state.moderates.map(community => <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
<li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li> )}
)} </ul>
</ul> </div>
</div>
}
</div> </div>
</div> }
</div> </div>
) )
} }
@ -410,6 +420,12 @@ export class User extends Component<any, UserState> {
i.setState(i.state); i.setState(i.state);
} }
handleUserSettingsThemeChange(i: User, event: any) {
i.state.userSettingsForm.theme = event.target.value;
setTheme(event.target.value);
i.setState(i.state);
}
handleUserSettingsSubmit(i: User, event: any) { handleUserSettingsSubmit(i: User, event: any) {
event.preventDefault(); event.preventDefault();
i.state.userSettingsLoading = true; i.state.userSettingsLoading = true;
@ -435,6 +451,7 @@ export class User extends Component<any, UserState> {
this.state.loading = false; this.state.loading = false;
if (this.isCurrentUser) { if (this.isCurrentUser) {
this.state.userSettingsForm.show_nsfw = UserService.Instance.user.show_nsfw; this.state.userSettingsForm.show_nsfw = UserService.Instance.user.show_nsfw;
this.state.userSettingsForm.theme = UserService.Instance.user.theme ? UserService.Instance.user.theme : 'darkly';
} }
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`; document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
window.scrollTo(0,0); window.scrollTo(0,0);

File diff suppressed because one or more lines are too long

16
ui/src/index.html vendored
View File

@ -6,8 +6,24 @@
<meta name="Description" content="Lemmy"> <meta name="Description" content="Lemmy">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Icons -->
<link rel="shortcut icon" type="image/svg+xml" href="/static/assets/favicon.svg" /> <link rel="shortcut icon" type="image/svg+xml" href="/static/assets/favicon.svg" />
<link rel="apple-touch-icon" href="/static/assets/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/static/assets/apple-touch-icon.png" />
<!-- Styles -->
<link rel="stylesheet" type="text/css" href="/static/assets/css/tribute.css" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/litera.min.css" id="litera" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/minty.min.css" id="minty" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/solar.min.css" id="solar" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/united.min.css" id="united" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/cyborg.min.css" id="cyborg" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/darkly.min.css" id="darkly" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/journal.min.css" id="journal" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/themes/sketchy.min.css" id="sketchy" />
<link rel="stylesheet" type="text/css" href="/static/assets/css/main.css" />
<!-- Scripts -->
<script async src="/static/assets/libs/sortable/sortable.min.js"></script> <script async src="/static/assets/libs/sortable/sortable.min.js"></script>
</head> </head>

4
ui/src/index.tsx vendored
View File

@ -19,10 +19,6 @@ import { Sponsors } from './components/sponsors';
import { Symbols } from './components/symbols'; import { Symbols } from './components/symbols';
import { i18n } from './i18next'; import { i18n } from './i18next';
import './css/tribute.css';
import './css/bootstrap.min.css';
import './css/main.css';
import { WebSocketService, UserService } from './services'; import { WebSocketService, UserService } from './services';
const container = document.getElementById('app'); const container = document.getElementById('app');

View File

@ -23,6 +23,7 @@ export interface User {
iss: string; iss: string;
username: string; username: string;
show_nsfw: boolean; show_nsfw: boolean;
theme: string;
} }
export interface UserView { export interface UserView {
@ -381,6 +382,7 @@ export interface LoginResponse {
export interface UserSettingsForm { export interface UserSettingsForm {
show_nsfw: boolean; show_nsfw: boolean;
theme: string;
auth: string; auth: string;
} }

View File

@ -1,5 +1,6 @@
import * as Cookies from 'js-cookie'; import * as Cookies from 'js-cookie';
import { User, LoginResponse } from '../interfaces'; import { User, LoginResponse } from '../interfaces';
import { setTheme } from '../utils';
import * as jwt_decode from 'jwt-decode'; import * as jwt_decode from 'jwt-decode';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -14,6 +15,7 @@ export class UserService {
if (jwt) { if (jwt) {
this.setUser(jwt); this.setUser(jwt);
} else { } else {
setTheme();
console.log('No JWT cookie found.'); console.log('No JWT cookie found.');
} }
} }
@ -27,8 +29,9 @@ export class UserService {
public logout() { public logout() {
this.user = undefined; this.user = undefined;
Cookies.remove("jwt"); Cookies.remove("jwt");
console.log("Logged out."); setTheme();
this.sub.next({user: undefined, unreadCount: 0}); this.sub.next({user: undefined, unreadCount: 0});
console.log("Logged out.");
} }
public get auth(): string { public get auth(): string {
@ -37,6 +40,7 @@ export class UserService {
private setUser(jwt: string) { private setUser(jwt: string) {
this.user = jwt_decode(jwt); this.user = jwt_decode(jwt);
setTheme(this.user.theme);
this.sub.next({user: this.user, unreadCount: 0}); this.sub.next({user: this.user, unreadCount: 0});
console.log(this.user); console.log(this.user);
} }

View File

@ -129,6 +129,7 @@ export const en = {
modified: 'modified', modified: 'modified',
nsfw: 'NSFW', nsfw: 'NSFW',
show_nsfw: 'Show NSFW content', show_nsfw: 'Show NSFW content',
theme: 'Theme',
sponsors: 'Sponsors', sponsors: 'Sponsors',
sponsors_of_lemmy: 'Sponsors of Lemmy', sponsors_of_lemmy: 'Sponsors of Lemmy',
sponsor_message: 'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:', sponsor_message: 'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:',

14
ui/src/utils.ts vendored
View File

@ -238,3 +238,17 @@ export function getMomentLanguage(): string {
} }
return lang; return lang;
} }
export const themes = ['litera', 'minty', 'solar', 'united', 'cyborg','darkly', 'journal', 'sketchy'];
export function setTheme(theme: string = 'darkly') {
for (var i=0; i < themes.length; i++) {
let styleSheet = document.getElementById(themes[i]);
if (themes[i] == theme) {
styleSheet.removeAttribute("disabled");
} else {
styleSheet.setAttribute("disabled", "disabled");
}
}
}

5
ui/yarn.lock vendored
View File

@ -381,6 +381,11 @@ body@^5.1.0:
raw-body "~1.1.0" raw-body "~1.1.0"
safe-json-parse "~1.0.1" safe-json-parse "~1.0.1"
bootswatch@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-4.3.1.tgz#be54748b420a1962dbcf9782605aac092f842e38"
integrity sha512-kNdpo/TnhO++aic1IODLIe1V0lx6pXwHMpwXMacpANDnuVDtgU1MUgUbVMC3rSWm4UcbImfwPraNYgjKDT0BtA==
bowser@^2.0.0-beta.3: bowser@^2.0.0-beta.3:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.6.1.tgz#196599588af6f0413449c79ab3bf7a5a1bb3384f" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.6.1.tgz#196599588af6f0413449c79ab3bf7a5a1bb3384f"