mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-24 13:33:05 +00:00
parent
62ae25f90d
commit
272ab3948c
35 changed files with 372 additions and 87 deletions
1
server/migrations/2019-10-15-181630_add_themes/down.sql
vendored
Normal file
1
server/migrations/2019-10-15-181630_add_themes/down.sql
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
alter table user_ drop column theme;
|
1
server/migrations/2019-10-15-181630_add_themes/up.sql
vendored
Normal file
1
server/migrations/2019-10-15-181630_add_themes/up.sql
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
alter table user_ add column theme varchar(20) default 'darkly' not null;
|
|
@ -21,6 +21,7 @@ pub struct Register {
|
|||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SaveUserSettings {
|
||||
show_nsfw: bool,
|
||||
theme: String,
|
||||
auth: String,
|
||||
}
|
||||
|
||||
|
@ -162,6 +163,7 @@ impl Perform<LoginResponse> for Oper<Register> {
|
|||
admin: data.admin,
|
||||
banned: false,
|
||||
show_nsfw: data.show_nsfw,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
// Create the user
|
||||
|
@ -252,6 +254,7 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
|
|||
admin: read_user.admin,
|
||||
banned: read_user.banned,
|
||||
show_nsfw: data.show_nsfw,
|
||||
theme: data.theme.to_owned(),
|
||||
};
|
||||
|
||||
let updated_user = match User_::update(&conn, user_id, &user_form) {
|
||||
|
@ -416,6 +419,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
|
|||
admin: data.added,
|
||||
banned: read_user.banned,
|
||||
show_nsfw: read_user.show_nsfw,
|
||||
theme: read_user.theme,
|
||||
};
|
||||
|
||||
match User_::update(&conn, data.user_id, &user_form) {
|
||||
|
@ -474,6 +478,7 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
|
|||
admin: read_user.admin,
|
||||
banned: data.ban,
|
||||
show_nsfw: read_user.show_nsfw,
|
||||
theme: read_user.theme,
|
||||
};
|
||||
|
||||
match User_::update(&conn, data.user_id, &user_form) {
|
||||
|
|
|
@ -72,6 +72,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let person = expected_user.person();
|
||||
|
|
|
@ -178,6 +178,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
|
|
@ -264,6 +264,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
|
|
@ -264,6 +264,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
|
|
@ -446,6 +446,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
|
||||
|
@ -460,6 +461,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
|
|
@ -191,6 +191,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
|
|
@ -225,6 +225,7 @@ mod tests {
|
|||
admin: false,
|
||||
banned: false,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
|
|
@ -20,6 +20,7 @@ pub struct User_ {
|
|||
pub published: chrono::NaiveDateTime,
|
||||
pub updated: Option<chrono::NaiveDateTime>,
|
||||
pub show_nsfw: bool,
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Clone)]
|
||||
|
@ -34,6 +35,7 @@ pub struct UserForm {
|
|||
pub email: Option<String>,
|
||||
pub updated: Option<chrono::NaiveDateTime>,
|
||||
pub show_nsfw: bool,
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
impl Crud<UserForm> for User_ {
|
||||
|
@ -74,6 +76,7 @@ pub struct Claims {
|
|||
pub username: String,
|
||||
pub iss: String,
|
||||
pub show_nsfw: bool,
|
||||
pub theme: String,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
|
@ -94,6 +97,7 @@ impl User_ {
|
|||
username: self.name.to_owned(),
|
||||
iss: self.fedi_name.to_owned(),
|
||||
show_nsfw: self.show_nsfw,
|
||||
theme: self.theme.to_owned(),
|
||||
};
|
||||
encode(
|
||||
&Header::default(),
|
||||
|
@ -141,6 +145,7 @@ mod tests {
|
|||
banned: false,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let inserted_user = User_::create(&conn, &new_user).unwrap();
|
||||
|
@ -158,6 +163,7 @@ mod tests {
|
|||
published: inserted_user.published,
|
||||
updated: None,
|
||||
show_nsfw: false,
|
||||
theme: "darkly".into(),
|
||||
};
|
||||
|
||||
let read_user = User_::read(&conn, inserted_user.id).unwrap();
|
||||
|
|
|
@ -254,6 +254,7 @@ table! {
|
|||
published -> Timestamp,
|
||||
updated -> Nullable<Timestamp>,
|
||||
show_nsfw -> Bool,
|
||||
theme -> Varchar,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
27
ui/src/css/main.css → ui/assets/css/main.css
vendored
27
ui/src/css/main.css → ui/assets/css/main.css
vendored
|
@ -1,7 +1,3 @@
|
|||
body, .text-white, .navbar-brand, .badge-light, .btn-secondary {
|
||||
color: #dedede !important;
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
border: 0px;
|
||||
}
|
||||
|
@ -31,29 +27,6 @@ body, .text-white, .navbar-brand, .badge-light, .btn-secondary {
|
|||
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 {
|
||||
margin-bottom: 0px;
|
||||
}
|
83
ui/assets/css/themes/cyborg.min.css
vendored
Normal file
83
ui/assets/css/themes/cyborg.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
35
ui/assets/css/themes/darkly.min.css
vendored
Normal file
35
ui/assets/css/themes/darkly.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
ui/assets/css/themes/journal.min.css
vendored
Normal file
12
ui/assets/css/themes/journal.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
ui/assets/css/themes/litera.min.css
vendored
Normal file
12
ui/assets/css/themes/litera.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
ui/assets/css/themes/minty.min.css
vendored
Normal file
12
ui/assets/css/themes/minty.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
ui/assets/css/themes/sketchy.min.css
vendored
Normal file
12
ui/assets/css/themes/sketchy.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
68
ui/assets/css/themes/solar.min.css
vendored
Normal file
68
ui/assets/css/themes/solar.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
ui/assets/css/themes/united.min.css
vendored
Normal file
12
ui/assets/css/themes/united.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/package.json
vendored
1
ui/package.json
vendored
|
@ -21,6 +21,7 @@
|
|||
"@types/markdown-it": "^0.0.7",
|
||||
"@types/markdown-it-container": "^2.0.2",
|
||||
"autosize": "^4.0.2",
|
||||
"bootswatch": "^4.3.1",
|
||||
"classcat": "^1.1.3",
|
||||
"dotenv": "^6.1.0",
|
||||
"emoji-short-name": "^0.1.0",
|
||||
|
|
2
ui/src/components/footer.tsx
vendored
2
ui/src/components/footer.tsx
vendored
|
@ -13,7 +13,7 @@ export class Footer extends Component<any, any> {
|
|||
|
||||
render() {
|
||||
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">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
|
|
4
ui/src/components/post-listing.tsx
vendored
4
ui/src/components/post-listing.tsx
vendored
|
@ -96,8 +96,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
<div className="post-title">
|
||||
<h5 className="mb-0 d-inline">
|
||||
{post.url ?
|
||||
<a className="text-white" 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>
|
||||
<a className="text-body" href={post.url} target="_blank" title={post.url}>{post.name}</a> :
|
||||
<Link className="text-body" to={`/post/${post.id}`} title={i18n.t('comments')}>{post.name}</Link>
|
||||
}
|
||||
</h5>
|
||||
{post.url &&
|
||||
|
|
2
ui/src/components/post.tsx
vendored
2
ui/src/components/post.tsx
vendored
|
@ -74,7 +74,7 @@ export class Post extends Component<any, PostState> {
|
|||
if (this.state.scrolled_comment_id && !this.state.scrolled && lastState.comments.length > 0) {
|
||||
var elmnt = document.getElementById(`comment-${this.state.scrolled_comment_id}`);
|
||||
elmnt.scrollIntoView();
|
||||
elmnt.classList.add("mark-two");
|
||||
elmnt.classList.add("mark");
|
||||
this.state.scrolled = true;
|
||||
this.markScrolledAsRead(this.state.scrolled_comment_id);
|
||||
}
|
||||
|
|
29
ui/src/components/user.tsx
vendored
29
ui/src/components/user.tsx
vendored
|
@ -4,7 +4,7 @@ import { Subscription } from "rxjs";
|
|||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse, UserSettingsForm, LoginResponse, BanUserResponse, AddAdminResponse } from '../interfaces';
|
||||
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 { CommentNodes } from './comment-nodes';
|
||||
import { MomentTime } from './moment-time';
|
||||
|
@ -61,6 +61,7 @@ export class User extends Component<any, UserState> {
|
|||
page: this.getPageFromProps(this.props),
|
||||
userSettingsForm: {
|
||||
show_nsfw: null,
|
||||
theme: null,
|
||||
auth: null,
|
||||
},
|
||||
userSettingsLoading: null,
|
||||
|
@ -285,7 +286,18 @@ export class User extends Component<any, UserState> {
|
|||
<div class="card-body">
|
||||
<h5><T i18nKey="settings">#</T></h5>
|
||||
<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="form-check">
|
||||
<input class="form-check-input" type="checkbox" checked={this.state.userSettingsForm.show_nsfw} onChange={linkEvent(this, this.handleUserSettingsShowNsfwChange)}/>
|
||||
|
@ -309,10 +321,9 @@ export class User extends Component<any, UserState> {
|
|||
moderates() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.moderates.length > 0 &&
|
||||
<div class="card border-secondary mb-3">
|
||||
<div class="card-body">
|
||||
{this.state.moderates.length > 0 &&
|
||||
<div>
|
||||
<h5><T i18nKey="moderates">#</T></h5>
|
||||
<ul class="list-unstyled mb-0">
|
||||
{this.state.moderates.map(community =>
|
||||
|
@ -320,10 +331,9 @@ export class User extends Component<any, UserState> {
|
|||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -410,6 +420,12 @@ export class User extends Component<any, UserState> {
|
|||
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) {
|
||||
event.preventDefault();
|
||||
i.state.userSettingsLoading = true;
|
||||
|
@ -435,6 +451,7 @@ export class User extends Component<any, UserState> {
|
|||
this.state.loading = false;
|
||||
if (this.isCurrentUser) {
|
||||
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}`;
|
||||
window.scrollTo(0,0);
|
||||
|
|
12
ui/src/css/bootstrap.min.css
vendored
12
ui/src/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
16
ui/src/index.html
vendored
16
ui/src/index.html
vendored
|
@ -6,8 +6,24 @@
|
|||
<meta name="Description" content="Lemmy">
|
||||
<meta charset="utf-8">
|
||||
<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="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>
|
||||
</head>
|
||||
|
||||
|
|
4
ui/src/index.tsx
vendored
4
ui/src/index.tsx
vendored
|
@ -19,10 +19,6 @@ import { Sponsors } from './components/sponsors';
|
|||
import { Symbols } from './components/symbols';
|
||||
import { i18n } from './i18next';
|
||||
|
||||
import './css/tribute.css';
|
||||
import './css/bootstrap.min.css';
|
||||
import './css/main.css';
|
||||
|
||||
import { WebSocketService, UserService } from './services';
|
||||
|
||||
const container = document.getElementById('app');
|
||||
|
|
2
ui/src/interfaces.ts
vendored
2
ui/src/interfaces.ts
vendored
|
@ -23,6 +23,7 @@ export interface User {
|
|||
iss: string;
|
||||
username: string;
|
||||
show_nsfw: boolean;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface UserView {
|
||||
|
@ -381,6 +382,7 @@ export interface LoginResponse {
|
|||
|
||||
export interface UserSettingsForm {
|
||||
show_nsfw: boolean;
|
||||
theme: string;
|
||||
auth: string;
|
||||
}
|
||||
|
||||
|
|
6
ui/src/services/UserService.ts
vendored
6
ui/src/services/UserService.ts
vendored
|
@ -1,5 +1,6 @@
|
|||
import * as Cookies from 'js-cookie';
|
||||
import { User, LoginResponse } from '../interfaces';
|
||||
import { setTheme } from '../utils';
|
||||
import * as jwt_decode from 'jwt-decode';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
|
@ -14,6 +15,7 @@ export class UserService {
|
|||
if (jwt) {
|
||||
this.setUser(jwt);
|
||||
} else {
|
||||
setTheme();
|
||||
console.log('No JWT cookie found.');
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +29,9 @@ export class UserService {
|
|||
public logout() {
|
||||
this.user = undefined;
|
||||
Cookies.remove("jwt");
|
||||
console.log("Logged out.");
|
||||
setTheme();
|
||||
this.sub.next({user: undefined, unreadCount: 0});
|
||||
console.log("Logged out.");
|
||||
}
|
||||
|
||||
public get auth(): string {
|
||||
|
@ -37,6 +40,7 @@ export class UserService {
|
|||
|
||||
private setUser(jwt: string) {
|
||||
this.user = jwt_decode(jwt);
|
||||
setTheme(this.user.theme);
|
||||
this.sub.next({user: this.user, unreadCount: 0});
|
||||
console.log(this.user);
|
||||
}
|
||||
|
|
1
ui/src/translations/en.ts
vendored
1
ui/src/translations/en.ts
vendored
|
@ -129,6 +129,7 @@ export const en = {
|
|||
modified: 'modified',
|
||||
nsfw: 'NSFW',
|
||||
show_nsfw: 'Show NSFW content',
|
||||
theme: 'Theme',
|
||||
sponsors: 'Sponsors',
|
||||
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:',
|
||||
|
|
14
ui/src/utils.ts
vendored
14
ui/src/utils.ts
vendored
|
@ -238,3 +238,17 @@ export function getMomentLanguage(): string {
|
|||
}
|
||||
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
5
ui/yarn.lock
vendored
|
@ -381,6 +381,11 @@ body@^5.1.0:
|
|||
raw-body "~1.1.0"
|
||||
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:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.6.1.tgz#196599588af6f0413449c79ab3bf7a5a1bb3384f"
|
||||
|
|
Loading…
Reference in a new issue