Add more checks in inbox, plus some refactoring #76

Merged
dessalines merged 6 commits from more-inbox-permissions into main 2020-08-04 14:39:56 +00:00
22 changed files with 109 additions and 52 deletions
Showing only changes of commit 3db2536a1f - Show all commits

2
ansible/VERSION vendored
View File

@ -1 +1 @@
v0.7.35 v0.7.39

View File

@ -60,6 +60,9 @@ server {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Cuts off the trailing slash on URLs to make them valid
rewrite ^(.+)/+$ $1 permanent;
# WebSocket support # WebSocket support
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;

View File

@ -17,6 +17,9 @@ http {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Cuts off the trailing slash on URLs to make them valid
rewrite ^(.+)/+$ $1 permanent;
# WebSocket support # WebSocket support
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -57,6 +60,9 @@ http {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Cuts off the trailing slash on URLs to make them valid
rewrite ^(.+)/+$ $1 permanent;
# WebSocket support # WebSocket support
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -97,6 +103,9 @@ http {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Cuts off the trailing slash on URLs to make them valid
rewrite ^(.+)/+$ $1 permanent;
# WebSocket support # WebSocket support
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;

View File

@ -12,7 +12,7 @@ services:
restart: always restart: always
lemmy: lemmy:
image: dessalines/lemmy:v0.7.35 image: dessalines/lemmy:v0.7.39
ports: ports:
- "127.0.0.1:8536:8536" - "127.0.0.1:8536:8536"
restart: always restart: always

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
docker tag dessalines/lemmy:travis \ docker tag dessalines/lemmy:travis \
dessalines/lemmy:v0.7.35 dessalines/lemmy:v0.7.39
docker push dessalines/lemmy:v0.7.35 docker push dessalines/lemmy:v0.7.39

View File

@ -1,7 +1,7 @@
### Install build requirements ### Install build requirements
#### Ubuntu #### Ubuntu
``` ```
sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 espeak
# install yarn # install yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

View File

@ -1 +1 @@
pub const VERSION: &str = "v0.7.35"; pub const VERSION: &str = "v0.7.39";

View File

@ -87,6 +87,10 @@
line-height: 1.0; line-height: 1.0;
} }
.post-title a:visited {
color: var(--gray) !important;
}
.icon { .icon {
display: inline-flex; display: inline-flex;
width: 1em; width: 1em;

View File

@ -144,6 +144,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
// This only finishes this form, if the randomly generated form_id matches the one received // This only finishes this form, if the randomly generated form_id matches the one received
if (this.state.commentForm.form_id == data.form_id) { if (this.state.commentForm.form_id == data.form_id) {
this.setState({ finished: true }); this.setState({ finished: true });
// Necessary because it broke tribute for some reaso
this.setState({ finished: false });
} }
} }
} }

View File

@ -275,21 +275,21 @@ export class Inbox extends Component<any, InboxState> {
); );
} }
combined(): Array<ReplyType> {
return [
...this.state.replies,
...this.state.mentions,
...this.state.messages,
].sort((a, b) => b.published.localeCompare(a.published));
}
all() { all() {
let combined: Array<ReplyType> = [];
combined.push(...this.state.replies);
combined.push(...this.state.mentions);
combined.push(...this.state.messages);
// Sort it
combined.sort((a, b) => b.published.localeCompare(a.published));
return ( return (
<div> <div>
{combined.map(i => {this.combined().map(i =>
isCommentType(i) ? ( isCommentType(i) ? (
<CommentNodes <CommentNodes
key={i.id}
nodes={[{ comment: i }]} nodes={[{ comment: i }]}
noIndent noIndent
markable markable
@ -298,7 +298,7 @@ export class Inbox extends Component<any, InboxState> {
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site.enable_downvotes}
/> />
) : ( ) : (
<PrivateMessage privateMessage={i} /> <PrivateMessage key={i.id} privateMessage={i} />
) )
)} )}
</div> </div>
@ -325,6 +325,7 @@ export class Inbox extends Component<any, InboxState> {
<div> <div>
{this.state.mentions.map(mention => ( {this.state.mentions.map(mention => (
<CommentNodes <CommentNodes
key={mention.id}
nodes={[{ comment: mention }]} nodes={[{ comment: mention }]}
noIndent noIndent
markable markable
@ -341,7 +342,7 @@ export class Inbox extends Component<any, InboxState> {
return ( return (
<div> <div>
{this.state.messages.map(message => ( {this.state.messages.map(message => (
<PrivateMessage privateMessage={message} /> <PrivateMessage key={message.id} privateMessage={message} />
))} ))}
</div> </div>
); );
@ -565,7 +566,6 @@ export class Inbox extends Component<any, InboxState> {
} else if (data.comment.creator_id == UserService.Instance.user.id) { } else if (data.comment.creator_id == UserService.Instance.user.id) {
toast(i18n.t('reply_sent')); toast(i18n.t('reply_sent'));
} }
this.setState(this.state);
} else if (res.op == UserOperation.CreatePrivateMessage) { } else if (res.op == UserOperation.CreatePrivateMessage) {
let data = res.data as PrivateMessageResponse; let data = res.data as PrivateMessageResponse;
if (data.message.recipient_id == UserService.Instance.user.id) { if (data.message.recipient_id == UserService.Instance.user.id) {
@ -597,7 +597,10 @@ export class Inbox extends Component<any, InboxState> {
this.state.replies.filter(r => !r.read).length + this.state.replies.filter(r => !r.read).length +
this.state.mentions.filter(r => !r.read).length + this.state.mentions.filter(r => !r.read).length +
this.state.messages.filter( this.state.messages.filter(
r => !r.read && r.creator_id !== UserService.Instance.user.id r =>
UserService.Instance.user &&
!r.read &&
r.creator_id !== UserService.Instance.user.id
).length ).length
); );
} }

View File

@ -25,6 +25,7 @@ interface MarkdownTextAreaProps {
onSubmit?(msg: { val: string; formId: string }): any; onSubmit?(msg: { val: string; formId: string }): any;
onContentChange?(val: string): any; onContentChange?(val: string): any;
onReplyCancel?(): any; onReplyCancel?(): any;
hideNavigationWarnings?: boolean;
} }
interface MarkdownTextAreaState { interface MarkdownTextAreaState {
@ -78,7 +79,7 @@ export class MarkdownTextArea extends Component<
} }
componentDidUpdate() { componentDidUpdate() {
if (this.state.content) { if (!this.props.hideNavigationWarnings && this.state.content) {
window.onbeforeunload = () => true; window.onbeforeunload = () => true;
} else { } else {
window.onbeforeunload = undefined; window.onbeforeunload = undefined;
@ -110,7 +111,10 @@ export class MarkdownTextArea extends Component<
render() { render() {
return ( return (
<form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}> <form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
<Prompt when={this.state.content} message={i18n.t('block_leaving')} /> <Prompt
when={!this.props.hideNavigationWarnings && this.state.content}
message={i18n.t('block_leaving')}
/>
<div class="form-group row"> <div class="form-group row">
<div className={`col-sm-12`}> <div className={`col-sm-12`}>
<textarea <textarea

View File

@ -431,6 +431,7 @@ export class Navbar extends Component<any, NavbarState> {
// The login // The login
if (data.my_user) { if (data.my_user) {
UserService.Instance.user = data.my_user; UserService.Instance.user = data.my_user;
WebSocketService.Instance.userJoin();
// On the first load, check the unreads // On the first load, check the unreads
if (this.state.isLoggedIn == false) { if (this.state.isLoggedIn == false) {
this.requestNotificationPermission(); this.requestNotificationPermission();

View File

@ -315,7 +315,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<h5 className="mb-1 d-inline-block"> <h5 className="mb-1 d-inline-block">
{this.props.showBody && post.url ? ( {this.props.showBody && post.url ? (
<a <a
className="text-body" className={!post.stickied ? 'text-body' : 'text-primary'}
href={post.url} href={post.url}
target="_blank" target="_blank"
title={post.url} title={post.url}
@ -325,7 +325,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</a> </a>
) : ( ) : (
<Link <Link
className="text-body" className={!post.stickied ? 'text-body' : 'text-primary'}
to={`/post/${post.id}`} to={`/post/${post.id}`}
title={i18n.t('comments')} title={i18n.t('comments')}
> >
@ -419,7 +419,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="unselectable pointer ml-2 text-muted font-italic" className="unselectable pointer ml-2 text-muted font-italic"
data-tippy-content={i18n.t('stickied')} data-tippy-content={i18n.t('stickied')}
> >
<svg class={`icon icon-inline text-success`}> <svg class={`icon icon-inline text-primary`}>
<use xlinkHref="#icon-pin"></use> <use xlinkHref="#icon-pin"></use>
</svg> </svg>
</small> </small>

View File

@ -45,7 +45,10 @@ export class PrivateMessage extends Component<
} }
get mine(): boolean { get mine(): boolean {
return UserService.Instance.user.id == this.props.privateMessage.creator_id; return (
UserService.Instance.user &&
UserService.Instance.user.id == this.props.privateMessage.creator_id
);
} }
render() { render() {
@ -113,6 +116,7 @@ export class PrivateMessage extends Component<
<PrivateMessageForm <PrivateMessageForm
privateMessage={message} privateMessage={message}
onEdit={this.handlePrivateMessageEdit} onEdit={this.handlePrivateMessageEdit}
onCreate={this.handlePrivateMessageCreate}
onCancel={this.handleReplyCancel} onCancel={this.handleReplyCancel}
/> />
)} )}
@ -280,9 +284,14 @@ export class PrivateMessage extends Component<
this.setState(this.state); this.setState(this.state);
} }
handlePrivateMessageCreate() { handlePrivateMessageCreate(message: PrivateMessageI) {
this.state.showReply = false; if (
this.setState(this.state); UserService.Instance.user &&
toast(i18n.t('message_sent')); message.creator_id == UserService.Instance.user.id
) {
this.state.showReply = false;
this.setState(this.state);
toast(i18n.t('message_sent'));
}
} }
} }

View File

@ -289,6 +289,7 @@ export class Search extends Component<any, SearchState> {
<div class="col-12"> <div class="col-12">
{i.type_ == 'posts' && ( {i.type_ == 'posts' && (
<PostListing <PostListing
key={(i.data as Post).id}
post={i.data as Post} post={i.data as Post}
showCommunity showCommunity
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site.enable_downvotes}
@ -297,6 +298,7 @@ export class Search extends Component<any, SearchState> {
)} )}
{i.type_ == 'comments' && ( {i.type_ == 'comments' && (
<CommentNodes <CommentNodes
key={(i.data as Comment).id}
nodes={[{ comment: i.data as Comment }]} nodes={[{ comment: i.data as Comment }]}
locked locked
noIndent noIndent

View File

@ -111,6 +111,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
<MarkdownTextArea <MarkdownTextArea
initialContent={this.state.siteForm.description} initialContent={this.state.siteForm.description}
onContentChange={this.handleSiteDescriptionChange} onContentChange={this.handleSiteDescriptionChange}
hideNavigationWarnings
/> />
</div> </div>
</div> </div>

View File

@ -65,6 +65,6 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
} }
handleSortChange(i: SortSelect, event: any) { handleSortChange(i: SortSelect, event: any) {
i.props.onChange(event.target.value); i.props.onChange(Number(event.target.value));
} }
} }

View File

@ -150,6 +150,7 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
<div> <div>
{i.type === 'posts' ? ( {i.type === 'posts' ? (
<PostListing <PostListing
key={(i.data as Post).id}
post={i.data as Post} post={i.data as Post}
admins={this.props.admins} admins={this.props.admins}
showCommunity showCommunity
@ -158,6 +159,7 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
/> />
) : ( ) : (
<CommentNodes <CommentNodes
key={(i.data as Comment).id}
nodes={[{ comment: i.data as Comment }]} nodes={[{ comment: i.data as Comment }]}
admins={this.props.admins} admins={this.props.admins}
noBorder noBorder

View File

@ -152,9 +152,6 @@ export class User extends Component<any, UserState> {
this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind( this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
this this
); );
this.handleUserSettingsListingTypeChange = this.handleUserSettingsListingTypeChange.bind(
this
);
this.handlePageChange = this.handlePageChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this);
this.handleUserSettingsBioChange = this.handleUserSettingsBioChange.bind( this.handleUserSettingsBioChange = this.handleUserSettingsBioChange.bind(
this this
@ -384,12 +381,14 @@ export class User extends Component<any, UserState> {
)} )}
</ul> </ul>
</h5> </h5>
<div className="d-flex align-items-center mb-2"> {user.bio && (
<div <div className="d-flex align-items-center mb-2">
className="md-div" <div
dangerouslySetInnerHTML={mdToHtml(user.bio)} className="md-div"
/> dangerouslySetInnerHTML={mdToHtml(user.bio)}
</div> />
</div>
)}
<div className="d-flex align-items-center mb-2"> <div className="d-flex align-items-center mb-2">
<svg class="icon"> <svg class="icon">
<use xlinkHref="#icon-cake"></use> <use xlinkHref="#icon-cake"></use>
@ -594,6 +593,7 @@ export class User extends Component<any, UserState> {
initialContent={this.state.userSettingsForm.bio} initialContent={this.state.userSettingsForm.bio}
onContentChange={this.handleUserSettingsBioChange} onContentChange={this.handleUserSettingsBioChange}
maxLength={300} maxLength={300}
hideNavigationWarnings
/> />
</div> </div>
</div> </div>

View File

@ -82,10 +82,6 @@ export class WebSocketService {
this.ws.onopen = () => { this.ws.onopen = () => {
console.log(`Connected to ${wsUri}`); console.log(`Connected to ${wsUri}`);
if (UserService.Instance.user) {
this.userJoin();
}
if (!firstConnect) { if (!firstConnect) {
let res: WebSocketJsonResponse = { let res: WebSocketJsonResponse = {
reconnect: true, reconnect: true,

11
ui/src/utils.ts vendored
View File

@ -588,6 +588,9 @@ export function messageToastify(
export function setupTribute(): Tribute { export function setupTribute(): Tribute {
return new Tribute({ return new Tribute({
noMatchTemplate: function () {
return '';
},
collection: [ collection: [
// Emojis // Emojis
{ {
@ -669,7 +672,7 @@ function userSearch(text: string, cb: any) {
WebSocketService.Instance.search(form); WebSocketService.Instance.search(form);
this.userSub = WebSocketService.Instance.subject.subscribe( let userSub = WebSocketService.Instance.subject.subscribe(
msg => { msg => {
let res = wsJsonToRes(msg); let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) { if (res.op == UserOperation.Search) {
@ -683,7 +686,7 @@ function userSearch(text: string, cb: any) {
}; };
}); });
cb(users); cb(users);
this.userSub.unsubscribe(); userSub.unsubscribe();
} }
}, },
err => console.error(err), err => console.error(err),
@ -706,7 +709,7 @@ function communitySearch(text: string, cb: any) {
WebSocketService.Instance.search(form); WebSocketService.Instance.search(form);
this.communitySub = WebSocketService.Instance.subject.subscribe( let communitySub = WebSocketService.Instance.subject.subscribe(
msg => { msg => {
let res = wsJsonToRes(msg); let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) { if (res.op == UserOperation.Search) {
@ -720,7 +723,7 @@ function communitySearch(text: string, cb: any) {
}; };
}); });
cb(communities); cb(communities);
this.communitySub.unsubscribe(); communitySub.unsubscribe();
} }
}, },
err => console.error(err), err => console.error(err),

View File

@ -14,7 +14,7 @@
"number_of_comments": "{{count}} Kommentar", "number_of_comments": "{{count}} Kommentar",
"number_of_comments_plural": "{{count}} Kommentare", "number_of_comments_plural": "{{count}} Kommentare",
"remove_comment": "Kommentar löschen", "remove_comment": "Kommentar löschen",
"communities": "Communitys", "communities": "Communities",
"users": "Benutzer", "users": "Benutzer",
"create_a_community": "Eine Community anlegen", "create_a_community": "Eine Community anlegen",
"create_community": "Community erstellen", "create_community": "Community erstellen",
@ -168,7 +168,7 @@
"yes": "Ja", "yes": "Ja",
"no": "Nein", "no": "Nein",
"powered_by": "Bereitgestellt durch", "powered_by": "Bereitgestellt durch",
"landing_0": "Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.", "landing": "Lemmy ist ein <1>Link-Aggregator</1> / Reddit Alternative im <2>Fediverse</2>.<3></3>Es ist selbst-hostbar, hat live-updates von Kommentar-threads und ist winzig (<4>~80kB</4>). Federation in das ActivityPub Netzwerk ist geplant. <5></5>Dies ist eine <6>sehr frühe Beta Version</6>, und viele Features funktionieren zurzeit nicht richtig oder fehlen. <7></7>Schlage neue Features vor oder melde Bugs <8>hier.</8><9></9>Gebaut mit <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>. <14></14> <15>Vielen Dank an unsere Mitwirkenden: </15> dessalines, Nutomic, asonix, zacanger, and iav.",
"not_logged_in": "Nicht eingeloggt.", "not_logged_in": "Nicht eingeloggt.",
"community_ban": "Du wurdest von dieser Community gebannt.", "community_ban": "Du wurdest von dieser Community gebannt.",
"site_ban": "Du wurdest von dieser Seite gebannt", "site_ban": "Du wurdest von dieser Seite gebannt",
@ -216,7 +216,7 @@
"messages": "Nachrichten", "messages": "Nachrichten",
"old_password": "Letztes Passwort", "old_password": "Letztes Passwort",
"matrix_user_id": "Matrix Benutzer", "matrix_user_id": "Matrix Benutzer",
"private_message_disclaimer": "Achtung: Private Nachrichten sind in Lemmy nicht sicher. Bitte erstelle einen <1>Riot.im</1> Account für sicheren Nachrichtenverkehr.", "private_message_disclaimer": "Achtung: Private Nachrichten sind in Lemmy nicht verschlüsselt. Bitte erstelle einen<1>Element.io</1> Account für sicheren Nachrichtenverkehr.",
"send_notifications_to_email": "Sende Benachrichtigungen per Email", "send_notifications_to_email": "Sende Benachrichtigungen per Email",
"downvotes_disabled": "Downvotes deaktiviert", "downvotes_disabled": "Downvotes deaktiviert",
"enable_downvotes": "Aktiviere Downvotes", "enable_downvotes": "Aktiviere Downvotes",
@ -256,5 +256,22 @@
"click_to_delete_picture": "Klicke, um das Bild zu löschen.", "click_to_delete_picture": "Klicke, um das Bild zu löschen.",
"picture_deleted": "Bild gelöscht.", "picture_deleted": "Bild gelöscht.",
"select_a_community": "Wähle eine Community aus", "select_a_community": "Wähle eine Community aus",
"invalid_username": "Ungültiger Benutzername." "invalid_username": "Ungültiger Benutzername.",
"bold": "fett",
"italic": "kursiv",
"subscript": "Tiefzeichen",
"superscript": "Hochzeichen",
"header": "Header",
"strikethrough": "durchgestrichen",
"quote": "Zitat",
"spoiler": "Spoiler",
"list": "Liste",
"not_a_moderator": "Kein Moderator.",
"invalid_url": "Ungültige URL.",
"must_login": "Du musst <1>eingeloggt oder registriert</1> sein um zu Kommentieren.",
"no_password_reset": "Du kannst dein Passwort ohne E-Mail nicht zurücksetzen.",
"cake_day_info": "Heute ist {{ creator_name }}'s cake day!",
"invalid_post_title": "Ungültiger Post Titel",
"cake_day_title": "Cake day:",
"what_is": "Was ist"
} }