]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/actions/compose.js
Add emoji autosuggest (#5053)
[mastodon.git] / app / javascript / mastodon / actions / compose.js
1 import api from '../api';
2 import { emojiIndex } from 'emoji-mart';
3
4 import {
5 updateTimeline,
6 refreshHomeTimeline,
7 refreshCommunityTimeline,
8 refreshPublicTimeline,
9 } from './timelines';
10
11 export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
12 export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
13 export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
14 export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
15 export const COMPOSE_REPLY = 'COMPOSE_REPLY';
16 export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
17 export const COMPOSE_MENTION = 'COMPOSE_MENTION';
18 export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
19 export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
20 export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
21 export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
22 export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
23
24 export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
25 export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
26 export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
27
28 export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
29 export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
30
31 export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
32 export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
33 export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
34 export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
35 export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
36 export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
37
38 export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
39
40 export function changeCompose(text) {
41 return {
42 type: COMPOSE_CHANGE,
43 text: text,
44 };
45 };
46
47 export function replyCompose(status, router) {
48 return (dispatch, getState) => {
49 dispatch({
50 type: COMPOSE_REPLY,
51 status: status,
52 });
53
54 if (!getState().getIn(['compose', 'mounted'])) {
55 router.push('/statuses/new');
56 }
57 };
58 };
59
60 export function cancelReplyCompose() {
61 return {
62 type: COMPOSE_REPLY_CANCEL,
63 };
64 };
65
66 export function mentionCompose(account, router) {
67 return (dispatch, getState) => {
68 dispatch({
69 type: COMPOSE_MENTION,
70 account: account,
71 });
72
73 if (!getState().getIn(['compose', 'mounted'])) {
74 router.push('/statuses/new');
75 }
76 };
77 };
78
79 export function submitCompose() {
80 return function (dispatch, getState) {
81 const status = getState().getIn(['compose', 'text'], '');
82
83 if (!status || !status.length) {
84 return;
85 }
86
87 dispatch(submitComposeRequest());
88
89 api(getState).post('/api/v1/statuses', {
90 status,
91 in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
92 media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
93 sensitive: getState().getIn(['compose', 'sensitive']),
94 spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
95 visibility: getState().getIn(['compose', 'privacy']),
96 }, {
97 headers: {
98 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
99 },
100 }).then(function (response) {
101 dispatch(submitComposeSuccess({ ...response.data }));
102
103 // To make the app more responsive, immediately get the status into the columns
104
105 const insertOrRefresh = (timelineId, refreshAction) => {
106 if (getState().getIn(['timelines', timelineId, 'online'])) {
107 dispatch(updateTimeline(timelineId, { ...response.data }));
108 } else if (getState().getIn(['timelines', timelineId, 'loaded'])) {
109 dispatch(refreshAction());
110 }
111 };
112
113 insertOrRefresh('home', refreshHomeTimeline);
114
115 if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
116 insertOrRefresh('community', refreshCommunityTimeline);
117 insertOrRefresh('public', refreshPublicTimeline);
118 }
119 }).catch(function (error) {
120 dispatch(submitComposeFail(error));
121 });
122 };
123 };
124
125 export function submitComposeRequest() {
126 return {
127 type: COMPOSE_SUBMIT_REQUEST,
128 };
129 };
130
131 export function submitComposeSuccess(status) {
132 return {
133 type: COMPOSE_SUBMIT_SUCCESS,
134 status: status,
135 };
136 };
137
138 export function submitComposeFail(error) {
139 return {
140 type: COMPOSE_SUBMIT_FAIL,
141 error: error,
142 };
143 };
144
145 export function uploadCompose(files) {
146 return function (dispatch, getState) {
147 if (getState().getIn(['compose', 'media_attachments']).size > 3) {
148 return;
149 }
150
151 dispatch(uploadComposeRequest());
152
153 let data = new FormData();
154 data.append('file', files[0]);
155
156 api(getState).post('/api/v1/media', data, {
157 onUploadProgress: function (e) {
158 dispatch(uploadComposeProgress(e.loaded, e.total));
159 },
160 }).then(function (response) {
161 dispatch(uploadComposeSuccess(response.data));
162 }).catch(function (error) {
163 dispatch(uploadComposeFail(error));
164 });
165 };
166 };
167
168 export function uploadComposeRequest() {
169 return {
170 type: COMPOSE_UPLOAD_REQUEST,
171 skipLoading: true,
172 };
173 };
174
175 export function uploadComposeProgress(loaded, total) {
176 return {
177 type: COMPOSE_UPLOAD_PROGRESS,
178 loaded: loaded,
179 total: total,
180 };
181 };
182
183 export function uploadComposeSuccess(media) {
184 return {
185 type: COMPOSE_UPLOAD_SUCCESS,
186 media: media,
187 skipLoading: true,
188 };
189 };
190
191 export function uploadComposeFail(error) {
192 return {
193 type: COMPOSE_UPLOAD_FAIL,
194 error: error,
195 skipLoading: true,
196 };
197 };
198
199 export function undoUploadCompose(media_id) {
200 return {
201 type: COMPOSE_UPLOAD_UNDO,
202 media_id: media_id,
203 };
204 };
205
206 export function clearComposeSuggestions() {
207 return {
208 type: COMPOSE_SUGGESTIONS_CLEAR,
209 };
210 };
211
212 export function fetchComposeSuggestions(token) {
213 return (dispatch, getState) => {
214 if (token[0] === ':') {
215 const results = emojiIndex.search(token.replace(':', ''), { maxResults: 3 });
216 dispatch(readyComposeSuggestionsEmojis(token, results));
217 return;
218 }
219
220 api(getState).get('/api/v1/accounts/search', {
221 params: {
222 q: token.slice(1),
223 resolve: false,
224 limit: 4,
225 },
226 }).then(response => {
227 dispatch(readyComposeSuggestionsAccounts(token, response.data));
228 });
229 };
230 };
231
232 export function readyComposeSuggestionsEmojis(token, emojis) {
233 return {
234 type: COMPOSE_SUGGESTIONS_READY,
235 token,
236 emojis,
237 };
238 };
239
240 export function readyComposeSuggestionsAccounts(token, accounts) {
241 return {
242 type: COMPOSE_SUGGESTIONS_READY,
243 token,
244 accounts,
245 };
246 };
247
248 export function selectComposeSuggestion(position, token, suggestion) {
249 return (dispatch, getState) => {
250 let completion, startPosition;
251
252 if (typeof suggestion === 'object' && suggestion.id) {
253 completion = suggestion.native || suggestion.colons;
254 startPosition = position - 1;
255 } else {
256 completion = getState().getIn(['accounts', suggestion, 'acct']);
257 startPosition = position;
258 }
259
260 dispatch({
261 type: COMPOSE_SUGGESTION_SELECT,
262 position: startPosition,
263 token,
264 completion,
265 });
266 };
267 };
268
269 export function mountCompose() {
270 return {
271 type: COMPOSE_MOUNT,
272 };
273 };
274
275 export function unmountCompose() {
276 return {
277 type: COMPOSE_UNMOUNT,
278 };
279 };
280
281 export function changeComposeSensitivity() {
282 return {
283 type: COMPOSE_SENSITIVITY_CHANGE,
284 };
285 };
286
287 export function changeComposeSpoilerness() {
288 return {
289 type: COMPOSE_SPOILERNESS_CHANGE,
290 };
291 };
292
293 export function changeComposeSpoilerText(text) {
294 return {
295 type: COMPOSE_SPOILER_TEXT_CHANGE,
296 text,
297 };
298 };
299
300 export function changeComposeVisibility(value) {
301 return {
302 type: COMPOSE_VISIBILITY_CHANGE,
303 value,
304 };
305 };
306
307 export function insertEmojiCompose(position, emoji) {
308 return {
309 type: COMPOSE_EMOJI_INSERT,
310 position,
311 emoji,
312 };
313 };
314
315 export function changeComposing(value) {
316 return {
317 type: COMPOSE_COMPOSING_CHANGE,
318 value,
319 };
320 }
This page took 0.132908 seconds and 4 git commands to generate.