]> Untitled Git - lemmy.git/commitdiff
Adding preview, image upload, and formatting help to comment and post
authorDessalines <tyhou13@gmx.com>
Sun, 1 Sep 2019 04:10:48 +0000 (21:10 -0700)
committerDessalines <tyhou13@gmx.com>
Sun, 1 Sep 2019 04:10:48 +0000 (21:10 -0700)
forms.

- Fixes #253

README.md
ui/src/components/comment-form.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/translations/en.ts
ui/src/utils.ts

index e1bcebce2c6891e77280b1771aab89ffa9cf3ee3..22e39b7db109a94370d91bd63e495e229d8ad9f1 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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
 
index 396ac9cc70e09f67e778d4e460aa95fc22f06d5b..93ad69ea7dd785fe0c7c6343de81e57aac6a8ee3 100644 (file)
@@ -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);
index f502e7f3ef1ee093b883ffcad5218d6077c31236..2e94e17720b35c8ad523163d45a048ed67702d3d 100644 (file)
@@ -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) {
index e3b6e76a96df26b3ff06944bcbf530abe06aaa6e..4a3b744a2734cb02af3b468fd943370d9508fa44 100644 (file)
@@ -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>
     )
index 10c1540c67e8280d42d800fed23b02264ef93c76..1ddf087da6db5336f3e4af5d4e0d240366260fed 100644 (file)
@@ -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',
index 40892ec8e18b5ba5aa4473c9ad2b2ce8eb2c7b2f..fe1b2b55455f90e5a57d743266ed944a060ae75d 100644 (file)
@@ -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);
 }