Adding preview, image upload, and formatting help to comment and post
forms. - Fixes #253
This commit is contained in:
parent
b6f6cd5c3e
commit
c568a83adf
6 changed files with 62 additions and 18 deletions
16
README.md
vendored
16
README.md
vendored
|
@ -169,14 +169,14 @@ If you'd like to add translations, take a look a look at the [english translatio
|
|||
|
||||
lang | done | missing
|
||||
--- | --- | ---
|
||||
de | 88% | cross_posts,cross_post,users,number_of_communities,settings,subscribed,expires,recent_comments,nsfw,show_nsfw,crypto,monero,joined,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
eo | 98% | number_of_communities,are_you_sure,yes,no
|
||||
es | 98% | number_of_communities,are_you_sure,yes,no
|
||||
fr | 91% | cross_posts,cross_post,users,number_of_communities,settings,recent_comments,nsfw,show_nsfw,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
nl | 100% |
|
||||
ru | 93% | cross_posts,cross_post,number_of_communities,recent_comments,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
sv | 100% |
|
||||
zh | 91% | cross_posts,cross_post,users,number_of_communities,settings,recent_comments,nsfw,show_nsfw,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
de | 87% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,settings,subscribed,expires,recent_comments,nsfw,show_nsfw,crypto,monero,joined,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
eo | 96% | number_of_communities,preview,upload_image,formatting_help,are_you_sure,yes,no
|
||||
es | 96% | number_of_communities,preview,upload_image,formatting_help,are_you_sure,yes,no
|
||||
fr | 89% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,settings,recent_comments,nsfw,show_nsfw,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
nl | 98% | preview,upload_image,formatting_help
|
||||
ru | 91% | cross_posts,cross_post,number_of_communities,preview,upload_image,formatting_help,recent_comments,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
sv | 98% | preview,upload_image,formatting_help
|
||||
zh | 89% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,settings,recent_comments,nsfw,show_nsfw,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
27
ui/src/components/comment-form.tsx
vendored
27
ui/src/components/comment-form.tsx
vendored
|
@ -1,7 +1,7 @@
|
|||
import { Component, linkEvent } from 'inferno';
|
||||
import { CommentNode as CommentNodeI, CommentForm as CommentFormI, SearchForm, SearchType, SortType, UserOperation, SearchResponse } from '../interfaces';
|
||||
import { Subscription } from "rxjs";
|
||||
import { capitalizeFirstLetter, fetchLimit, msgOp, md, emojiMentionList } from '../utils';
|
||||
import { capitalizeFirstLetter, mentionDropdownFetchLimit, msgOp, md, emojiMentionList, mdToHtml, randomStr, imageUploadUrl, markdownHelpUrl } from '../utils';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import * as autosize from 'autosize';
|
||||
import { i18n } from '../i18next';
|
||||
|
@ -19,11 +19,12 @@ interface CommentFormProps {
|
|||
interface CommentFormState {
|
||||
commentForm: CommentFormI;
|
||||
buttonTitle: string;
|
||||
previewMode: boolean;
|
||||
}
|
||||
|
||||
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||
|
||||
private id = `comment-form-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10)}`;
|
||||
private id = `comment-form-${randomStr()}`;
|
||||
private userSub: Subscription;
|
||||
private communitySub: Subscription;
|
||||
private tribute: any;
|
||||
|
@ -35,6 +36,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
creator_id: UserService.Instance.user ? UserService.Instance.user.id : null,
|
||||
},
|
||||
buttonTitle: !this.props.node ? capitalizeFirstLetter(i18n.t('post')) : this.props.edit ? capitalizeFirstLetter(i18n.t('edit')) : capitalizeFirstLetter(i18n.t('reply')),
|
||||
previewMode: false,
|
||||
}
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
|
@ -119,13 +121,21 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
<form onSubmit={linkEvent(this, this.handleCommentSubmit)}>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<textarea id={this.id} class="form-control" value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} required disabled={this.props.disabled} rows={2} maxLength={10000} />
|
||||
<textarea id={this.id} className={`form-control ${this.state.previewMode && 'd-none'}`} value={this.state.commentForm.content} onInput={linkEvent(this, this.handleCommentContentChange)} required disabled={this.props.disabled} rows={2} maxLength={10000} />
|
||||
{this.state.previewMode &&
|
||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(this.state.commentForm.content)} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-sm btn-secondary mr-2" disabled={this.props.disabled}>{this.state.buttonTitle}</button>
|
||||
{this.state.commentForm.content &&
|
||||
<button className={`btn btn-sm mr-2 btn-secondary ${this.state.previewMode && 'active'}`} onClick={linkEvent(this, this.handlePreviewToggle)}><T i18nKey="preview">#</T></button>
|
||||
}
|
||||
{this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}><T i18nKey="cancel">#</T></button>}
|
||||
<a href={markdownHelpUrl} target="_blank" class="d-inline-block float-right text-muted small font-weight-bold"><T i18nKey="formatting_help">#</T></a>
|
||||
<a href={imageUploadUrl} target="_blank" class="d-inline-block mr-2 float-right text-muted small font-weight-bold"><T i18nKey="upload_image">#</T></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -141,6 +151,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
WebSocketService.Instance.createComment(i.state.commentForm);
|
||||
}
|
||||
|
||||
i.state.previewMode = false;
|
||||
i.state.commentForm.content = undefined;
|
||||
i.setState(i.state);
|
||||
event.target.reset();
|
||||
|
@ -156,6 +167,12 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handlePreviewToggle(i: CommentForm, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.previewMode = !i.state.previewMode;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleReplyCancel(i: CommentForm) {
|
||||
i.props.onReplyCancel();
|
||||
}
|
||||
|
@ -167,7 +184,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
type_: SearchType[SearchType.Users],
|
||||
sort: SortType[SortType.TopAll],
|
||||
page: 1,
|
||||
limit: 6,
|
||||
limit: mentionDropdownFetchLimit,
|
||||
};
|
||||
|
||||
WebSocketService.Instance.search(form);
|
||||
|
@ -198,7 +215,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
|||
type_: SearchType[SearchType.Communities],
|
||||
sort: SortType[SortType.TopAll],
|
||||
page: 1,
|
||||
limit: 6,
|
||||
limit: mentionDropdownFetchLimit,
|
||||
};
|
||||
|
||||
WebSocketService.Instance.search(form);
|
||||
|
|
20
ui/src/components/post-form.tsx
vendored
20
ui/src/components/post-form.tsx
vendored
|
@ -4,7 +4,7 @@ import { Subscription } from "rxjs";
|
|||
import { retryWhen, delay, take } from 'rxjs/operators';
|
||||
import { PostForm as PostFormI, PostFormParams, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType, SearchForm, SearchType, SearchResponse } from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import { msgOp, getPageTitle, debounce, validURL, capitalizeFirstLetter } from '../utils';
|
||||
import { msgOp, getPageTitle, debounce, validURL, capitalizeFirstLetter, imageUploadUrl, markdownHelpUrl, mdToHtml } from '../utils';
|
||||
import * as autosize from 'autosize';
|
||||
import { i18n } from '../i18next';
|
||||
import { T } from 'inferno-i18next';
|
||||
|
@ -21,6 +21,7 @@ interface PostFormState {
|
|||
postForm: PostFormI;
|
||||
communities: Array<Community>;
|
||||
loading: boolean;
|
||||
previewMode: boolean;
|
||||
suggestedTitle: string;
|
||||
suggestedPosts: Array<Post>;
|
||||
crossPosts: Array<Post>;
|
||||
|
@ -39,6 +40,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
},
|
||||
communities: [],
|
||||
loading: false,
|
||||
previewMode: false,
|
||||
suggestedTitle: undefined,
|
||||
suggestedPosts: [],
|
||||
crossPosts: [],
|
||||
|
@ -107,6 +109,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
{this.state.suggestedTitle &&
|
||||
<div class="mt-1 text-muted small font-weight-bold pointer" onClick={linkEvent(this, this.copySuggestedTitle)}><T i18nKey="copy_suggested_title" interpolation={{title: this.state.suggestedTitle}}>#</T></div>
|
||||
}
|
||||
<a href={imageUploadUrl} target="_blank" class="d-inline-block mr-2 float-right text-muted small font-weight-bold"><T i18nKey="upload_image">#</T></a>
|
||||
{this.state.crossPosts.length > 0 &&
|
||||
<>
|
||||
<div class="my-1 text-muted small font-weight-bold"><T i18nKey="cross_posts">#</T></div>
|
||||
|
@ -130,7 +133,14 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label"><T i18nKey="body">#</T></label>
|
||||
<div class="col-sm-10">
|
||||
<textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={4} maxLength={10000} />
|
||||
<textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} className={`form-control ${this.state.previewMode && 'd-none'}`} rows={4} maxLength={10000} />
|
||||
{this.state.previewMode &&
|
||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(this.state.postForm.body)} />
|
||||
}
|
||||
{this.state.postForm.body &&
|
||||
<button className={`mt-1 mr-2 btn btn-sm btn-secondary ${this.state.previewMode && 'active'}`} onClick={linkEvent(this, this.handlePreviewToggle)}><T i18nKey="preview">#</T></button>
|
||||
}
|
||||
<a href={markdownHelpUrl} target="_blank" class="d-inline-block float-right text-muted small font-weight-bold"><T i18nKey="formatting_help">#</T></a>
|
||||
</div>
|
||||
</div>
|
||||
{!this.props.post &&
|
||||
|
@ -250,6 +260,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
i.props.onCancel();
|
||||
}
|
||||
|
||||
handlePreviewToggle(i: PostForm, event: any) {
|
||||
event.preventDefault();
|
||||
i.state.previewMode = !i.state.previewMode;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
let op: UserOperation = msgOp(msg);
|
||||
if (msg.error) {
|
||||
|
|
5
ui/src/components/post-listing.tsx
vendored
5
ui/src/components/post-listing.tsx
vendored
|
@ -49,7 +49,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
<div class="row">
|
||||
{!this.state.showEdit
|
||||
? this.listing()
|
||||
: <PostForm post={this.props.post} onEdit={this.handleEditPost} onCancel={this.handleEditCancel}/>
|
||||
:
|
||||
<div class="col-12">
|
||||
<PostForm post={this.props.post} onEdit={this.handleEditPost} onCancel={this.handleEditCancel}/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
|
3
ui/src/translations/en.ts
vendored
3
ui/src/translations/en.ts
vendored
|
@ -26,6 +26,9 @@ export const en = {
|
|||
edit: 'edit',
|
||||
reply: 'reply',
|
||||
cancel: 'Cancel',
|
||||
preview: 'Preview',
|
||||
upload_image: 'upload image',
|
||||
formatting_help: 'formatting help',
|
||||
unlock: 'unlock',
|
||||
lock: 'lock',
|
||||
link: 'link',
|
||||
|
|
9
ui/src/utils.ts
vendored
9
ui/src/utils.ts
vendored
|
@ -15,6 +15,13 @@ import { emoji_list } from './emoji_list';
|
|||
import * as twemoji from 'twemoji';
|
||||
|
||||
export const repoUrl = 'https://github.com/dessalines/lemmy';
|
||||
export const imageUploadUrl = 'https://postimages.org/';
|
||||
export const markdownHelpUrl = 'https://commonmark.org/help/';
|
||||
|
||||
export const fetchLimit: number = 20;
|
||||
export const mentionDropdownFetchLimit = 6;
|
||||
|
||||
export function randomStr() {return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10)}
|
||||
|
||||
export function msgOp(msg: any): UserOperation {
|
||||
let opStr: string = msg.op;
|
||||
|
@ -110,8 +117,6 @@ export function validURL(str: string) {
|
|||
return !!pattern.test(str);
|
||||
}
|
||||
|
||||
export let fetchLimit: number = 20;
|
||||
|
||||
export function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
|
Reference in a new issue