import {
CommentNode as CommentNodeI,
CommentForm as CommentFormI,
- SearchForm,
- SearchType,
- SortType,
- UserOperation,
- SearchResponse,
} from '../interfaces';
-import { Subscription } from 'rxjs';
import {
- wsJsonToRes,
capitalizeFirstLetter,
- mentionDropdownFetchLimit,
mdToHtml,
randomStr,
markdownHelpUrl,
toast,
+ setupTribute,
} from '../utils';
import { WebSocketService, UserService } from '../services';
import autosize from 'autosize';
+import Tribute from 'tributejs/src/Tribute.js';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
-import Tribute from 'tributejs/src/Tribute.js';
-import emojiShortName from 'emoji-short-name';
interface CommentFormProps {
postId?: number;
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
private id = `comment-form-${randomStr()}`;
- private userSub: Subscription;
- private communitySub: Subscription;
- private tribute: any;
+ private tribute: Tribute;
private emptyState: CommentFormState = {
commentForm: {
auth: null,
constructor(props: any, context: any) {
super(props, context);
- this.tribute = new Tribute({
- collection: [
- // Emojis
- {
- trigger: ':',
- menuItemTemplate: (item: any) => {
- let emoji = `:${item.original.key}:`;
- return `${item.original.val} ${emoji}`;
- },
- selectTemplate: (item: any) => {
- return `:${item.original.key}:`;
- },
- values: Object.entries(emojiShortName).map(e => {
- return { key: e[1], val: e[0] };
- }),
- allowSpaces: false,
- autocompleteMode: true,
- menuItemLimit: mentionDropdownFetchLimit,
- },
- // Users
- {
- trigger: '@',
- selectTemplate: (item: any) => {
- return `[/u/${item.original.key}](/u/${item.original.key})`;
- },
- values: (text: string, cb: any) => {
- this.userSearch(text, (users: any) => cb(users));
- },
- allowSpaces: false,
- autocompleteMode: true,
- menuItemLimit: mentionDropdownFetchLimit,
- },
-
- // Communities
- {
- trigger: '#',
- selectTemplate: (item: any) => {
- return `[/c/${item.original.key}](/c/${item.original.key})`;
- },
- values: (text: string, cb: any) => {
- this.communitySearch(text, (communities: any) => cb(communities));
- },
- allowSpaces: false,
- autocompleteMode: true,
- menuItemLimit: mentionDropdownFetchLimit,
- },
- ],
- });
-
+ this.tribute = setupTribute();
this.state = this.emptyState;
if (this.props.node) {
toast(error, 'danger');
});
}
-
- userSearch(text: string, cb: any) {
- if (text) {
- let form: SearchForm = {
- q: text,
- type_: SearchType[SearchType.Users],
- sort: SortType[SortType.TopAll],
- page: 1,
- limit: mentionDropdownFetchLimit,
- };
-
- WebSocketService.Instance.search(form);
-
- this.userSub = WebSocketService.Instance.subject.subscribe(
- msg => {
- let res = wsJsonToRes(msg);
- if (res.op == UserOperation.Search) {
- let data = res.data as SearchResponse;
- let users = data.users.map(u => {
- return { key: u.name };
- });
- cb(users);
- this.userSub.unsubscribe();
- }
- },
- err => console.error(err),
- () => console.log('complete')
- );
- } else {
- cb([]);
- }
- }
-
- communitySearch(text: string, cb: any) {
- if (text) {
- let form: SearchForm = {
- q: text,
- type_: SearchType[SearchType.Communities],
- sort: SortType[SortType.TopAll],
- page: 1,
- limit: mentionDropdownFetchLimit,
- };
-
- WebSocketService.Instance.search(form);
-
- this.communitySub = WebSocketService.Instance.subject.subscribe(
- msg => {
- let res = wsJsonToRes(msg);
- if (res.op == UserOperation.Search) {
- let data = res.data as SearchResponse;
- let communities = data.communities.map(u => {
- return { key: u.name };
- });
- cb(communities);
- this.communitySub.unsubscribe();
- }
- },
- err => console.error(err),
- () => console.log('complete')
- );
- } else {
- cb([]);
- }
- }
}
WebSocketJsonResponse,
} from '../interfaces';
import { WebSocketService } from '../services';
-import { wsJsonToRes, capitalizeFirstLetter, toast } from '../utils';
+import {
+ wsJsonToRes,
+ capitalizeFirstLetter,
+ toast,
+ randomStr,
+ setupTribute,
+} from '../utils';
+import Tribute from 'tributejs/src/Tribute.js';
import autosize from 'autosize';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
CommunityFormProps,
CommunityFormState
> {
+ private id = `community-form-${randomStr()}`;
+ private tribute: Tribute;
private subscription: Subscription;
private emptyState: CommunityFormState = {
constructor(props: any, context: any) {
super(props, context);
+ this.tribute = setupTribute();
this.state = this.emptyState;
if (this.props.community) {
}
componentDidMount() {
- autosize(document.querySelectorAll('textarea'));
+ var textarea: any = document.getElementById(this.id);
+ autosize(textarea);
+ this.tribute.attach(textarea);
+ textarea.addEventListener('tribute-replaced', () => {
+ this.state.communityForm.description = textarea.value;
+ this.setState(this.state);
+ autosize.update(textarea);
+ });
}
componentWillUnmount() {
</label>
<div class="col-12">
<textarea
+ id={this.id}
value={this.state.communityForm.description}
onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
class="form-control"
debounce,
isImage,
toast,
+ randomStr,
+ setupTribute,
} from '../utils';
import autosize from 'autosize';
+import Tribute from 'tributejs/src/Tribute.js';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
}
export class PostForm extends Component<PostFormProps, PostFormState> {
+ private id = `post-form-${randomStr()}`;
+ private tribute: Tribute;
private subscription: Subscription;
private emptyState: PostFormState = {
postForm: {
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts).bind(this);
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
+ this.tribute = setupTribute();
this.state = this.emptyState;
if (this.props.post) {
}
componentDidMount() {
- autosize(document.querySelectorAll('textarea'));
+ var textarea: any = document.getElementById(this.id);
+ autosize(textarea);
+ this.tribute.attach(textarea);
+ textarea.addEventListener('tribute-replaced', () => {
+ this.state.postForm.body = textarea.value;
+ this.setState(this.state);
+ autosize.update(textarea);
+ });
}
componentWillUnmount() {
</label>
<div class="col-sm-10">
<textarea
+ id={this.id}
value={this.state.postForm.body}
onInput={linkEvent(this, this.handlePostBodyChange)}
className={`form-control ${this.state.previewMode && 'd-none'}`}
pictshareAvatarThumbnail,
wsJsonToRes,
toast,
+ randomStr,
+ setupTribute,
} from '../utils';
+import Tribute from 'tributejs/src/Tribute.js';
import autosize from 'autosize';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
PrivateMessageFormProps,
PrivateMessageFormState
> {
+ private id = `message-form-${randomStr()}`;
+ private tribute: Tribute;
private subscription: Subscription;
private emptyState: PrivateMessageFormState = {
privateMessageForm: {
constructor(props: any, context: any) {
super(props, context);
+ this.tribute = setupTribute();
this.state = this.emptyState;
if (this.props.privateMessage) {
}
componentDidMount() {
- autosize(document.querySelectorAll('textarea'));
+ var textarea: any = document.getElementById(this.id);
+ autosize(textarea);
+ this.tribute.attach(textarea);
+ textarea.addEventListener('tribute-replaced', () => {
+ this.state.privateMessageForm.content = textarea.value;
+ this.setState(this.state);
+ autosize.update(textarea);
+ });
}
componentWillUnmount() {
<label class="col-sm-2 col-form-label">{i18n.t('message')}</label>
<div class="col-sm-10">
<textarea
+ id={this.id}
value={this.state.privateMessageForm.content}
onInput={linkEvent(this, this.handleContentChange)}
className={`form-control ${this.state.previewMode && 'd-none'}`}
import { Component, linkEvent } from 'inferno';
import { Site, SiteForm as SiteFormI } from '../interfaces';
import { WebSocketService } from '../services';
-import { capitalizeFirstLetter } from '../utils';
+import { capitalizeFirstLetter, randomStr, setupTribute } from '../utils';
import autosize from 'autosize';
+import Tribute from 'tributejs/src/Tribute.js';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
}
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
+ private id = `site-form-${randomStr()}`;
+ private tribute: Tribute;
private emptyState: SiteFormState = {
siteForm: {
enable_downvotes: true,
constructor(props: any, context: any) {
super(props, context);
+
+ this.tribute = setupTribute();
this.state = this.emptyState;
+
if (this.props.site) {
this.state.siteForm = {
name: this.props.site.name,
}
componentDidMount() {
- autosize(document.querySelectorAll('textarea'));
+ var textarea: any = document.getElementById(this.id);
+ autosize(textarea);
+ this.tribute.attach(textarea);
+ textarea.addEventListener('tribute-replaced', () => {
+ this.state.siteForm.description = textarea.value;
+ this.setState(this.state);
+ autosize.update(textarea);
+ });
}
render() {
</label>
<div class="col-12">
<textarea
+ id={this.id}
value={this.state.siteForm.description}
onInput={linkEvent(this, this.handleSiteDescriptionChange)}
class="form-control"
SearchType,
WebSocketResponse,
WebSocketJsonResponse,
+ SearchForm,
+ SearchResponse,
} from './interfaces';
-import { UserService } from './services/UserService';
+import { UserService, WebSocketService } from './services';
+
+import Tribute from 'tributejs/src/Tribute.js';
import markdown_it from 'markdown-it';
import markdownitEmoji from 'markdown-it-emoji/light';
import markdown_it_container from 'markdown-it-container';
-import * as twemoji from 'twemoji';
-import * as emojiShortName from 'emoji-short-name';
+import twemoji from 'twemoji';
+import emojiShortName from 'emoji-short-name';
import Toastify from 'toastify-js';
export const repoUrl = 'https://github.com/dessalines/lemmy';
export const postRefetchSeconds: number = 60 * 1000;
export const fetchLimit: number = 20;
-export const mentionDropdownFetchLimit = 6;
+export const mentionDropdownFetchLimit = 10;
export function randomStr() {
return Math.random()
backgroundColor: backgroundColor,
}).showToast();
}
+
+export function setupTribute(): Tribute {
+ return new Tribute({
+ collection: [
+ // Emojis
+ {
+ trigger: ':',
+ menuItemTemplate: (item: any) => {
+ let emoji = `:${item.original.key}:`;
+ return `${item.original.val} ${emoji}`;
+ },
+ selectTemplate: (item: any) => {
+ return `:${item.original.key}:`;
+ },
+ values: Object.entries(emojiShortName).map(e => {
+ return { key: e[1], val: e[0] };
+ }),
+ allowSpaces: false,
+ autocompleteMode: true,
+ menuItemLimit: mentionDropdownFetchLimit,
+ },
+ // Users
+ {
+ trigger: '@',
+ selectTemplate: (item: any) => {
+ return `[/u/${item.original.key}](/u/${item.original.key})`;
+ },
+ values: (text: string, cb: any) => {
+ userSearch(text, (users: any) => cb(users));
+ },
+ allowSpaces: false,
+ autocompleteMode: true,
+ menuItemLimit: mentionDropdownFetchLimit,
+ },
+
+ // Communities
+ {
+ trigger: '#',
+ selectTemplate: (item: any) => {
+ return `[/c/${item.original.key}](/c/${item.original.key})`;
+ },
+ values: (text: string, cb: any) => {
+ communitySearch(text, (communities: any) => cb(communities));
+ },
+ allowSpaces: false,
+ autocompleteMode: true,
+ menuItemLimit: mentionDropdownFetchLimit,
+ },
+ ],
+ });
+}
+
+function userSearch(text: string, cb: any) {
+ if (text) {
+ let form: SearchForm = {
+ q: text,
+ type_: SearchType[SearchType.Users],
+ sort: SortType[SortType.TopAll],
+ page: 1,
+ limit: mentionDropdownFetchLimit,
+ };
+
+ WebSocketService.Instance.search(form);
+
+ this.userSub = WebSocketService.Instance.subject.subscribe(
+ msg => {
+ let res = wsJsonToRes(msg);
+ if (res.op == UserOperation.Search) {
+ let data = res.data as SearchResponse;
+ let users = data.users.map(u => {
+ return { key: u.name };
+ });
+ cb(users);
+ this.userSub.unsubscribe();
+ }
+ },
+ err => console.error(err),
+ () => console.log('complete')
+ );
+ } else {
+ cb([]);
+ }
+}
+
+function communitySearch(text: string, cb: any) {
+ if (text) {
+ let form: SearchForm = {
+ q: text,
+ type_: SearchType[SearchType.Communities],
+ sort: SortType[SortType.TopAll],
+ page: 1,
+ limit: mentionDropdownFetchLimit,
+ };
+
+ WebSocketService.Instance.search(form);
+
+ this.communitySub = WebSocketService.Instance.subject.subscribe(
+ msg => {
+ let res = wsJsonToRes(msg);
+ if (res.op == UserOperation.Search) {
+ let data = res.data as SearchResponse;
+ let communities = data.communities.map(u => {
+ return { key: u.name };
+ });
+ cb(communities);
+ this.communitySub.unsubscribe();
+ }
+ },
+ err => console.error(err),
+ () => console.log('complete')
+ );
+ } else {
+ cb([]);
+ }
+}