forked from nutomic/lemmy
Adding autocomplete to post, community, message, and site forms. Fixes #453
This commit is contained in:
parent
351cd84ab8
commit
ada50fc3de
6 changed files with 193 additions and 136 deletions
130
ui/src/components/comment-form.tsx
vendored
130
ui/src/components/comment-form.tsx
vendored
|
@ -2,28 +2,20 @@ import { Component, linkEvent } from 'inferno';
|
||||||
import {
|
import {
|
||||||
CommentNode as CommentNodeI,
|
CommentNode as CommentNodeI,
|
||||||
CommentForm as CommentFormI,
|
CommentForm as CommentFormI,
|
||||||
SearchForm,
|
|
||||||
SearchType,
|
|
||||||
SortType,
|
|
||||||
UserOperation,
|
|
||||||
SearchResponse,
|
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import {
|
import {
|
||||||
wsJsonToRes,
|
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
mentionDropdownFetchLimit,
|
|
||||||
mdToHtml,
|
mdToHtml,
|
||||||
randomStr,
|
randomStr,
|
||||||
markdownHelpUrl,
|
markdownHelpUrl,
|
||||||
toast,
|
toast,
|
||||||
|
setupTribute,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
import Tribute from 'tributejs/src/Tribute.js';
|
|
||||||
import emojiShortName from 'emoji-short-name';
|
|
||||||
|
|
||||||
interface CommentFormProps {
|
interface CommentFormProps {
|
||||||
postId?: number;
|
postId?: number;
|
||||||
|
@ -42,9 +34,7 @@ interface CommentFormState {
|
||||||
|
|
||||||
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
private id = `comment-form-${randomStr()}`;
|
private id = `comment-form-${randomStr()}`;
|
||||||
private userSub: Subscription;
|
private tribute: Tribute;
|
||||||
private communitySub: Subscription;
|
|
||||||
private tribute: any;
|
|
||||||
private emptyState: CommentFormState = {
|
private emptyState: CommentFormState = {
|
||||||
commentForm: {
|
commentForm: {
|
||||||
auth: null,
|
auth: null,
|
||||||
|
@ -68,55 +58,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.tribute = new Tribute({
|
this.tribute = setupTribute();
|
||||||
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.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
if (this.props.node) {
|
if (this.props.node) {
|
||||||
|
@ -297,68 +239,4 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
|
||||||
toast(error, 'danger');
|
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([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
22
ui/src/components/community-form.tsx
vendored
22
ui/src/components/community-form.tsx
vendored
|
@ -11,7 +11,14 @@ import {
|
||||||
WebSocketJsonResponse,
|
WebSocketJsonResponse,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
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 autosize from 'autosize';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -36,6 +43,8 @@ export class CommunityForm extends Component<
|
||||||
CommunityFormProps,
|
CommunityFormProps,
|
||||||
CommunityFormState
|
CommunityFormState
|
||||||
> {
|
> {
|
||||||
|
private id = `community-form-${randomStr()}`;
|
||||||
|
private tribute: Tribute;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
private emptyState: CommunityFormState = {
|
private emptyState: CommunityFormState = {
|
||||||
|
@ -53,6 +62,7 @@ export class CommunityForm extends Component<
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this.tribute = setupTribute();
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
if (this.props.community) {
|
if (this.props.community) {
|
||||||
|
@ -80,7 +90,14 @@ export class CommunityForm extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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() {
|
componentWillUnmount() {
|
||||||
|
@ -130,6 +147,7 @@ export class CommunityForm extends Component<
|
||||||
</label>
|
</label>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<textarea
|
<textarea
|
||||||
|
id={this.id}
|
||||||
value={this.state.communityForm.description}
|
value={this.state.communityForm.description}
|
||||||
onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
|
onInput={linkEvent(this, this.handleCommunityDescriptionChange)}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
|
16
ui/src/components/post-form.tsx
vendored
16
ui/src/components/post-form.tsx
vendored
|
@ -30,8 +30,11 @@ import {
|
||||||
debounce,
|
debounce,
|
||||||
isImage,
|
isImage,
|
||||||
toast,
|
toast,
|
||||||
|
randomStr,
|
||||||
|
setupTribute,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -56,6 +59,8 @@ interface PostFormState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostForm extends Component<PostFormProps, PostFormState> {
|
export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
|
private id = `post-form-${randomStr()}`;
|
||||||
|
private tribute: Tribute;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: PostFormState = {
|
private emptyState: PostFormState = {
|
||||||
postForm: {
|
postForm: {
|
||||||
|
@ -82,6 +87,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts).bind(this);
|
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts).bind(this);
|
||||||
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
|
this.fetchPageTitle = debounce(this.fetchPageTitle).bind(this);
|
||||||
|
|
||||||
|
this.tribute = setupTribute();
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
if (this.props.post) {
|
if (this.props.post) {
|
||||||
|
@ -126,7 +132,14 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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() {
|
componentWillUnmount() {
|
||||||
|
@ -238,6 +251,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea
|
<textarea
|
||||||
|
id={this.id}
|
||||||
value={this.state.postForm.body}
|
value={this.state.postForm.body}
|
||||||
onInput={linkEvent(this, this.handlePostBodyChange)}
|
onInput={linkEvent(this, this.handlePostBodyChange)}
|
||||||
className={`form-control ${this.state.previewMode && 'd-none'}`}
|
className={`form-control ${this.state.previewMode && 'd-none'}`}
|
||||||
|
|
16
ui/src/components/private-message-form.tsx
vendored
16
ui/src/components/private-message-form.tsx
vendored
|
@ -24,7 +24,10 @@ import {
|
||||||
pictshareAvatarThumbnail,
|
pictshareAvatarThumbnail,
|
||||||
wsJsonToRes,
|
wsJsonToRes,
|
||||||
toast,
|
toast,
|
||||||
|
randomStr,
|
||||||
|
setupTribute,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -49,6 +52,8 @@ export class PrivateMessageForm extends Component<
|
||||||
PrivateMessageFormProps,
|
PrivateMessageFormProps,
|
||||||
PrivateMessageFormState
|
PrivateMessageFormState
|
||||||
> {
|
> {
|
||||||
|
private id = `message-form-${randomStr()}`;
|
||||||
|
private tribute: Tribute;
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: PrivateMessageFormState = {
|
private emptyState: PrivateMessageFormState = {
|
||||||
privateMessageForm: {
|
privateMessageForm: {
|
||||||
|
@ -64,6 +69,7 @@ export class PrivateMessageForm extends Component<
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this.tribute = setupTribute();
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
if (this.props.privateMessage) {
|
if (this.props.privateMessage) {
|
||||||
|
@ -93,7 +99,14 @@ export class PrivateMessageForm extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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() {
|
componentWillUnmount() {
|
||||||
|
@ -136,6 +149,7 @@ export class PrivateMessageForm extends Component<
|
||||||
<label class="col-sm-2 col-form-label">{i18n.t('message')}</label>
|
<label class="col-sm-2 col-form-label">{i18n.t('message')}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea
|
<textarea
|
||||||
|
id={this.id}
|
||||||
value={this.state.privateMessageForm.content}
|
value={this.state.privateMessageForm.content}
|
||||||
onInput={linkEvent(this, this.handleContentChange)}
|
onInput={linkEvent(this, this.handleContentChange)}
|
||||||
className={`form-control ${this.state.previewMode && 'd-none'}`}
|
className={`form-control ${this.state.previewMode && 'd-none'}`}
|
||||||
|
|
18
ui/src/components/site-form.tsx
vendored
18
ui/src/components/site-form.tsx
vendored
|
@ -1,8 +1,9 @@
|
||||||
import { Component, linkEvent } from 'inferno';
|
import { Component, linkEvent } from 'inferno';
|
||||||
import { Site, SiteForm as SiteFormI } from '../interfaces';
|
import { Site, SiteForm as SiteFormI } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { capitalizeFirstLetter } from '../utils';
|
import { capitalizeFirstLetter, randomStr, setupTribute } from '../utils';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
|
import Tribute from 'tributejs/src/Tribute.js';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ interface SiteFormState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
|
private id = `site-form-${randomStr()}`;
|
||||||
|
private tribute: Tribute;
|
||||||
private emptyState: SiteFormState = {
|
private emptyState: SiteFormState = {
|
||||||
siteForm: {
|
siteForm: {
|
||||||
enable_downvotes: true,
|
enable_downvotes: true,
|
||||||
|
@ -29,7 +32,10 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this.tribute = setupTribute();
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
if (this.props.site) {
|
if (this.props.site) {
|
||||||
this.state.siteForm = {
|
this.state.siteForm = {
|
||||||
name: this.props.site.name,
|
name: this.props.site.name,
|
||||||
|
@ -42,7 +48,14 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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() {
|
render() {
|
||||||
|
@ -75,6 +88,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||||
</label>
|
</label>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<textarea
|
<textarea
|
||||||
|
id={this.id}
|
||||||
value={this.state.siteForm.description}
|
value={this.state.siteForm.description}
|
||||||
onInput={linkEvent(this, this.handleSiteDescriptionChange)}
|
onInput={linkEvent(this, this.handleSiteDescriptionChange)}
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
|
127
ui/src/utils.ts
vendored
127
ui/src/utils.ts
vendored
|
@ -18,13 +18,17 @@ import {
|
||||||
SearchType,
|
SearchType,
|
||||||
WebSocketResponse,
|
WebSocketResponse,
|
||||||
WebSocketJsonResponse,
|
WebSocketJsonResponse,
|
||||||
|
SearchForm,
|
||||||
|
SearchResponse,
|
||||||
} from './interfaces';
|
} 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 markdown_it from 'markdown-it';
|
||||||
import markdownitEmoji from 'markdown-it-emoji/light';
|
import markdownitEmoji from 'markdown-it-emoji/light';
|
||||||
import markdown_it_container from 'markdown-it-container';
|
import markdown_it_container from 'markdown-it-container';
|
||||||
import * as twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import * as emojiShortName from 'emoji-short-name';
|
import emojiShortName from 'emoji-short-name';
|
||||||
import Toastify from 'toastify-js';
|
import Toastify from 'toastify-js';
|
||||||
|
|
||||||
export const repoUrl = 'https://github.com/dessalines/lemmy';
|
export const repoUrl = 'https://github.com/dessalines/lemmy';
|
||||||
|
@ -33,7 +37,7 @@ export const archiveUrl = 'https://archive.is';
|
||||||
|
|
||||||
export const postRefetchSeconds: number = 60 * 1000;
|
export const postRefetchSeconds: number = 60 * 1000;
|
||||||
export const fetchLimit: number = 20;
|
export const fetchLimit: number = 20;
|
||||||
export const mentionDropdownFetchLimit = 6;
|
export const mentionDropdownFetchLimit = 10;
|
||||||
|
|
||||||
export function randomStr() {
|
export function randomStr() {
|
||||||
return Math.random()
|
return Math.random()
|
||||||
|
@ -380,3 +384,118 @@ export function toast(text: string, background: string = 'success') {
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
}).showToast();
|
}).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([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue